OLD | NEW |
1 /* | 1 /* |
2 * Copyright (C) 2011 Google Inc. All rights reserved. | 2 * Copyright (C) 2011 Google Inc. All rights reserved. |
3 * | 3 * |
4 * Redistribution and use in source and binary forms, with or without | 4 * Redistribution and use in source and binary forms, with or without |
5 * modification, are permitted provided that the following conditions are | 5 * modification, are permitted provided that the following conditions are |
6 * met: | 6 * met: |
7 * | 7 * |
8 * * Redistributions of source code must retain the above copyright | 8 * * Redistributions of source code must retain the above copyright |
9 * notice, this list of conditions and the following disclaimer. | 9 * notice, this list of conditions and the following disclaimer. |
10 * * Redistributions in binary form must reproduce the above | 10 * * Redistributions in binary form must reproduce the above |
(...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
44 if (uiSourceCode.project().type() === WebInspector.projectTypes.Debugger) | 44 if (uiSourceCode.project().type() === WebInspector.projectTypes.Debugger) |
45 this.element.classList.add("source-frame-debugger-script"); | 45 this.element.classList.add("source-frame-debugger-script"); |
46 | 46 |
47 this._popoverHelper = new WebInspector.ObjectPopoverHelper(this.textEditor.e
lement, | 47 this._popoverHelper = new WebInspector.ObjectPopoverHelper(this.textEditor.e
lement, |
48 this._getPopoverAnchor.bind(this), this._resolveObjectForPopover.bin
d(this), this._onHidePopover.bind(this), true); | 48 this._getPopoverAnchor.bind(this), this._resolveObjectForPopover.bin
d(this), this._onHidePopover.bind(this), true); |
49 | 49 |
50 this.textEditor.element.addEventListener("keydown", this._onKeyDown.bind(thi
s), true); | 50 this.textEditor.element.addEventListener("keydown", this._onKeyDown.bind(thi
s), true); |
51 | 51 |
52 this.textEditor.addEventListener(WebInspector.TextEditor.Events.GutterClick,
this._handleGutterClick.bind(this), this); | 52 this.textEditor.addEventListener(WebInspector.TextEditor.Events.GutterClick,
this._handleGutterClick.bind(this), this); |
53 | 53 |
54 this.textEditor.element.addEventListener("mousedown", this._onMouseDownAndCl
ick.bind(this, true), true); | |
55 this.textEditor.element.addEventListener("click", this._onMouseDownAndClick.
bind(this, false), true); | |
56 | |
57 | |
58 this._breakpointManager.addEventListener(WebInspector.BreakpointManager.Even
ts.BreakpointAdded, this._breakpointAdded, this); | 54 this._breakpointManager.addEventListener(WebInspector.BreakpointManager.Even
ts.BreakpointAdded, this._breakpointAdded, this); |
59 this._breakpointManager.addEventListener(WebInspector.BreakpointManager.Even
ts.BreakpointRemoved, this._breakpointRemoved, this); | 55 this._breakpointManager.addEventListener(WebInspector.BreakpointManager.Even
ts.BreakpointRemoved, this._breakpointRemoved, this); |
60 | 56 |
61 this._uiSourceCode.addEventListener(WebInspector.UISourceCode.Events.Console
MessageAdded, this._consoleMessageAdded, this); | 57 this._uiSourceCode.addEventListener(WebInspector.UISourceCode.Events.Console
MessageAdded, this._consoleMessageAdded, this); |
62 this._uiSourceCode.addEventListener(WebInspector.UISourceCode.Events.Console
MessageRemoved, this._consoleMessageRemoved, this); | 58 this._uiSourceCode.addEventListener(WebInspector.UISourceCode.Events.Console
MessageRemoved, this._consoleMessageRemoved, this); |
63 this._uiSourceCode.addEventListener(WebInspector.UISourceCode.Events.Console
MessagesCleared, this._consoleMessagesCleared, this); | 59 this._uiSourceCode.addEventListener(WebInspector.UISourceCode.Events.Console
MessagesCleared, this._consoleMessagesCleared, this); |
64 this._uiSourceCode.addEventListener(WebInspector.UISourceCode.Events.SourceM
appingChanged, this._onSourceMappingChanged, this); | 60 this._uiSourceCode.addEventListener(WebInspector.UISourceCode.Events.SourceM
appingChanged, this._onSourceMappingChanged, this); |
65 this._uiSourceCode.addEventListener(WebInspector.UISourceCode.Events.Working
CopyChanged, this._workingCopyChanged, this); | 61 this._uiSourceCode.addEventListener(WebInspector.UISourceCode.Events.Working
CopyChanged, this._workingCopyChanged, this); |
66 this._uiSourceCode.addEventListener(WebInspector.UISourceCode.Events.Working
CopyCommitted, this._workingCopyCommitted, this); | 62 this._uiSourceCode.addEventListener(WebInspector.UISourceCode.Events.Working
CopyCommitted, this._workingCopyCommitted, this); |
67 | 63 |
(...skipping 334 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
402 this.textEditor.removeAttribute(lineNumber, "breakpoint"); | 398 this.textEditor.removeAttribute(lineNumber, "breakpoint"); |
403 this.textEditor.removeBreakpoint(lineNumber); | 399 this.textEditor.removeBreakpoint(lineNumber); |
404 }, | 400 }, |
405 | 401 |
406 _onKeyDown: function(event) | 402 _onKeyDown: function(event) |
407 { | 403 { |
408 if (event.keyIdentifier === "U+001B") { // Escape key | 404 if (event.keyIdentifier === "U+001B") { // Escape key |
409 if (this._popoverHelper.isPopoverVisible()) { | 405 if (this._popoverHelper.isPopoverVisible()) { |
410 this._popoverHelper.hidePopover(); | 406 this._popoverHelper.hidePopover(); |
411 event.consume(); | 407 event.consume(); |
412 return; | |
413 } | |
414 if (this._stepIntoMarkup && WebInspector.KeyboardShortcut.eventHasCt
rlOrMeta(event)) { | |
415 this._stepIntoMarkup.stoptIteratingSelection(); | |
416 event.consume(); | |
417 return; | |
418 } | 408 } |
419 } | 409 } |
420 }, | 410 }, |
421 | 411 |
422 /** | 412 /** |
423 * @param {number} lineNumber | 413 * @param {number} lineNumber |
424 * @param {!WebInspector.BreakpointManager.Breakpoint=} breakpoint | 414 * @param {!WebInspector.BreakpointManager.Breakpoint=} breakpoint |
425 */ | 415 */ |
426 _editBreakpointCondition: function(lineNumber, breakpoint) | 416 _editBreakpointCondition: function(lineNumber, breakpoint) |
427 { | 417 { |
(...skipping 39 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
467 editorElement.className = "monospace"; | 457 editorElement.className = "monospace"; |
468 editorElement.type = "text"; | 458 editorElement.type = "text"; |
469 conditionElement.appendChild(editorElement); | 459 conditionElement.appendChild(editorElement); |
470 this._conditionEditorElement = editorElement; | 460 this._conditionEditorElement = editorElement; |
471 | 461 |
472 return conditionElement; | 462 return conditionElement; |
473 }, | 463 }, |
474 | 464 |
475 /** | 465 /** |
476 * @param {number} lineNumber | 466 * @param {number} lineNumber |
477 * @param {!WebInspector.DebuggerModel.CallFrame} callFrame | |
478 */ | 467 */ |
479 setExecutionLine: function(lineNumber, callFrame) | 468 setExecutionLine: function(lineNumber) |
480 { | 469 { |
481 this._executionLineNumber = lineNumber; | 470 this._executionLineNumber = lineNumber; |
482 this._executionCallFrame = callFrame; | 471 if (this.loaded) |
483 if (this.loaded) { | |
484 this.textEditor.setExecutionLine(lineNumber); | 472 this.textEditor.setExecutionLine(lineNumber); |
485 | |
486 if (WebInspector.experimentsSettings.stepIntoSelection.isEnabled()) | |
487 callFrame.getStepIntoLocations(locationsCallback.bind(this)); | |
488 } | |
489 | |
490 /** | |
491 * @param {!Array.<!DebuggerAgent.Location>} locations | |
492 * @this {WebInspector.JavaScriptSourceFrame} | |
493 */ | |
494 function locationsCallback(locations) | |
495 { | |
496 if (this._executionCallFrame !== callFrame || this._stepIntoMarkup) | |
497 return; | |
498 this._stepIntoMarkup = WebInspector.JavaScriptSourceFrame.StepIntoMa
rkup.create(this, locations); | |
499 if (this._stepIntoMarkup) | |
500 this._stepIntoMarkup.show(); | |
501 } | |
502 }, | 473 }, |
503 | 474 |
504 clearExecutionLine: function() | 475 clearExecutionLine: function() |
505 { | 476 { |
506 if (this._stepIntoMarkup) { | |
507 this._stepIntoMarkup.dispose(); | |
508 delete this._stepIntoMarkup; | |
509 } | |
510 | |
511 if (this.loaded && typeof this._executionLineNumber === "number") | 477 if (this.loaded && typeof this._executionLineNumber === "number") |
512 this.textEditor.clearExecutionLine(); | 478 this.textEditor.clearExecutionLine(); |
513 delete this._executionLineNumber; | 479 delete this._executionLineNumber; |
514 delete this._executionCallFrame; | |
515 }, | |
516 | |
517 _onMouseDownAndClick: function(isMouseDown, event) | |
518 { | |
519 var markup = this._stepIntoMarkup; | |
520 if (!markup) | |
521 return; | |
522 var index = markup.findItemByCoordinates(event.x, event.y); | |
523 if (typeof index === "undefined") | |
524 return; | |
525 | |
526 if (isMouseDown) { | |
527 // Do not let text editor to spoil 'click' event that is coming for
us. | |
528 event.consume(); | |
529 } else { | |
530 var rawLocation = markup.getRawPosition(index); | |
531 this._scriptsPanel.doStepIntoSelection(rawLocation); | |
532 } | |
533 }, | 480 }, |
534 | 481 |
535 /** | 482 /** |
536 * @return {boolean} | 483 * @return {boolean} |
537 */ | 484 */ |
538 _shouldIgnoreExternalBreakpointEvents: function() | 485 _shouldIgnoreExternalBreakpointEvents: function() |
539 { | 486 { |
540 if (this._supportsEnabledBreakpointsWhileEditing()) | 487 if (this._supportsEnabledBreakpointsWhileEditing()) |
541 return false; | 488 return false; |
542 if (this._muted) | 489 if (this._muted) |
(...skipping 69 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
612 this._scriptFile.addEventListener(WebInspector.ScriptFile.Events.Did
DivergeFromVM, this._didDivergeFromVM, this); | 559 this._scriptFile.addEventListener(WebInspector.ScriptFile.Events.Did
DivergeFromVM, this._didDivergeFromVM, this); |
613 | 560 |
614 if (this.loaded) | 561 if (this.loaded) |
615 this._scriptFile.checkMapping(); | 562 this._scriptFile.checkMapping(); |
616 } | 563 } |
617 }, | 564 }, |
618 | 565 |
619 onTextEditorContentLoaded: function() | 566 onTextEditorContentLoaded: function() |
620 { | 567 { |
621 if (typeof this._executionLineNumber === "number") | 568 if (typeof this._executionLineNumber === "number") |
622 this.setExecutionLine(this._executionLineNumber, this._executionCall
Frame); | 569 this.setExecutionLine(this._executionLineNumber); |
623 | 570 |
624 var breakpointLocations = this._breakpointManager.breakpointLocationsFor
UISourceCode(this._uiSourceCode); | 571 var breakpointLocations = this._breakpointManager.breakpointLocationsFor
UISourceCode(this._uiSourceCode); |
625 for (var i = 0; i < breakpointLocations.length; ++i) | 572 for (var i = 0; i < breakpointLocations.length; ++i) |
626 this._breakpointAdded({data:breakpointLocations[i]}); | 573 this._breakpointAdded({data:breakpointLocations[i]}); |
627 | 574 |
628 var messages = this._uiSourceCode.consoleMessages(); | 575 var messages = this._uiSourceCode.consoleMessages(); |
629 for (var i = 0; i < messages.length; ++i) { | 576 for (var i = 0; i < messages.length; ++i) { |
630 var message = messages[i]; | 577 var message = messages[i]; |
631 this.addMessageToSource(message.lineNumber, message.originalMessage)
; | 578 this.addMessageToSource(message.lineNumber, message.originalMessage)
; |
632 } | 579 } |
(...skipping 68 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
701 | 648 |
702 /** | 649 /** |
703 * @param {number} lineNumber | 650 * @param {number} lineNumber |
704 */ | 651 */ |
705 _continueToLine: function(lineNumber) | 652 _continueToLine: function(lineNumber) |
706 { | 653 { |
707 var rawLocation = /** @type {!WebInspector.DebuggerModel.Location} */ (t
his._uiSourceCode.uiLocationToRawLocation(lineNumber, 0)); | 654 var rawLocation = /** @type {!WebInspector.DebuggerModel.Location} */ (t
his._uiSourceCode.uiLocationToRawLocation(lineNumber, 0)); |
708 this._scriptsPanel.continueToLocation(rawLocation); | 655 this._scriptsPanel.continueToLocation(rawLocation); |
709 }, | 656 }, |
710 | 657 |
711 /** | |
712 * @return {!WebInspector.JavaScriptSourceFrame.StepIntoMarkup|undefined} | |
713 */ | |
714 stepIntoMarkup: function() | |
715 { | |
716 return this._stepIntoMarkup; | |
717 }, | |
718 | |
719 dispose: function() | 658 dispose: function() |
720 { | 659 { |
721 this._breakpointManager.removeEventListener(WebInspector.BreakpointManag
er.Events.BreakpointAdded, this._breakpointAdded, this); | 660 this._breakpointManager.removeEventListener(WebInspector.BreakpointManag
er.Events.BreakpointAdded, this._breakpointAdded, this); |
722 this._breakpointManager.removeEventListener(WebInspector.BreakpointManag
er.Events.BreakpointRemoved, this._breakpointRemoved, this); | 661 this._breakpointManager.removeEventListener(WebInspector.BreakpointManag
er.Events.BreakpointRemoved, this._breakpointRemoved, this); |
723 this._uiSourceCode.removeEventListener(WebInspector.UISourceCode.Events.
ConsoleMessageAdded, this._consoleMessageAdded, this); | 662 this._uiSourceCode.removeEventListener(WebInspector.UISourceCode.Events.
ConsoleMessageAdded, this._consoleMessageAdded, this); |
724 this._uiSourceCode.removeEventListener(WebInspector.UISourceCode.Events.
ConsoleMessageRemoved, this._consoleMessageRemoved, this); | 663 this._uiSourceCode.removeEventListener(WebInspector.UISourceCode.Events.
ConsoleMessageRemoved, this._consoleMessageRemoved, this); |
725 this._uiSourceCode.removeEventListener(WebInspector.UISourceCode.Events.
ConsoleMessagesCleared, this._consoleMessagesCleared, this); | 664 this._uiSourceCode.removeEventListener(WebInspector.UISourceCode.Events.
ConsoleMessagesCleared, this._consoleMessagesCleared, this); |
726 this._uiSourceCode.removeEventListener(WebInspector.UISourceCode.Events.
SourceMappingChanged, this._onSourceMappingChanged, this); | 665 this._uiSourceCode.removeEventListener(WebInspector.UISourceCode.Events.
SourceMappingChanged, this._onSourceMappingChanged, this); |
727 this._uiSourceCode.removeEventListener(WebInspector.UISourceCode.Events.
WorkingCopyChanged, this._workingCopyChanged, this); | 666 this._uiSourceCode.removeEventListener(WebInspector.UISourceCode.Events.
WorkingCopyChanged, this._workingCopyChanged, this); |
728 this._uiSourceCode.removeEventListener(WebInspector.UISourceCode.Events.
WorkingCopyCommitted, this._workingCopyCommitted, this); | 667 this._uiSourceCode.removeEventListener(WebInspector.UISourceCode.Events.
WorkingCopyCommitted, this._workingCopyCommitted, this); |
729 WebInspector.UISourceCodeFrame.prototype.dispose.call(this); | 668 WebInspector.UISourceCodeFrame.prototype.dispose.call(this); |
730 }, | 669 }, |
731 | 670 |
732 __proto__: WebInspector.UISourceCodeFrame.prototype | 671 __proto__: WebInspector.UISourceCodeFrame.prototype |
733 } | 672 } |
734 | |
735 /** | |
736 * @constructor | |
737 * @param {!Array.<!DebuggerAgent.Location>} rawPositions | |
738 * @param {!Array.<!WebInspector.TextRange>} editorRanges | |
739 * @param {number} firstToExecute | |
740 * @param {!WebInspector.JavaScriptSourceFrame} sourceFrame | |
741 */ | |
742 WebInspector.JavaScriptSourceFrame.StepIntoMarkup = function(rawPositions, edito
rRanges, firstToExecute, sourceFrame) | |
743 { | |
744 this._positions = rawPositions; | |
745 this._editorRanges = editorRanges; | |
746 this._highlightDescriptors = new Array(rawPositions.length); | |
747 this._currentHighlight = undefined; | |
748 this._firstToExecute = firstToExecute; | |
749 this._currentSelection = undefined; | |
750 this._sourceFrame = sourceFrame; | |
751 }; | |
752 | |
753 WebInspector.JavaScriptSourceFrame.StepIntoMarkup.prototype = { | |
754 show: function() | |
755 { | |
756 var highlight = this._getVisibleHighlight(); | |
757 for (var i = 0; i < this._positions.length; ++i) | |
758 this._highlightItem(i, i === highlight); | |
759 this._shownVisibleHighlight = highlight; | |
760 }, | |
761 | |
762 startIteratingSelection: function() | |
763 { | |
764 this._currentSelection = this._positions.length | |
765 this._redrawHighlight(); | |
766 }, | |
767 | |
768 stopIteratingSelection: function() | |
769 { | |
770 this._currentSelection = undefined; | |
771 this._redrawHighlight(); | |
772 }, | |
773 | |
774 /** | |
775 * @param {boolean} backward | |
776 */ | |
777 iterateSelection: function(backward) | |
778 { | |
779 if (typeof this._currentSelection === "undefined") | |
780 return; | |
781 var nextSelection = backward ? this._currentSelection - 1 : this._curren
tSelection + 1; | |
782 var modulo = this._positions.length + 1; | |
783 nextSelection = (nextSelection + modulo) % modulo; | |
784 this._currentSelection = nextSelection; | |
785 this._redrawHighlight(); | |
786 }, | |
787 | |
788 _redrawHighlight: function() | |
789 { | |
790 var visibleHighlight = this._getVisibleHighlight(); | |
791 if (this._shownVisibleHighlight === visibleHighlight) | |
792 return; | |
793 this._hideItemHighlight(this._shownVisibleHighlight); | |
794 this._hideItemHighlight(visibleHighlight); | |
795 this._highlightItem(this._shownVisibleHighlight, false); | |
796 this._highlightItem(visibleHighlight, true); | |
797 this._shownVisibleHighlight = visibleHighlight; | |
798 }, | |
799 | |
800 /** | |
801 * @return {number} | |
802 */ | |
803 _getVisibleHighlight: function() | |
804 { | |
805 return typeof this._currentSelection === "undefined" ? this._firstToExec
ute : this._currentSelection; | |
806 }, | |
807 | |
808 /** | |
809 * @param {number} position | |
810 * @param {boolean} selected | |
811 */ | |
812 _highlightItem: function(position, selected) | |
813 { | |
814 if (position === this._positions.length) | |
815 return; | |
816 var styleName = selected ? "source-frame-stepin-mark-highlighted" : "sou
rce-frame-stepin-mark"; | |
817 var textEditor = this._sourceFrame.textEditor; | |
818 var highlightDescriptor = textEditor.highlightRange(this._editorRanges[p
osition], styleName); | |
819 this._highlightDescriptors[position] = highlightDescriptor; | |
820 }, | |
821 | |
822 /** | |
823 * @param {number} position | |
824 */ | |
825 _hideItemHighlight: function(position) | |
826 { | |
827 if (position === this._positions.length) | |
828 return; | |
829 var highlightDescriptor = this._highlightDescriptors[position]; | |
830 console.assert(highlightDescriptor); | |
831 var textEditor = this._sourceFrame.textEditor; | |
832 textEditor.removeHighlight(highlightDescriptor); | |
833 this._highlightDescriptors[position] = undefined; | |
834 }, | |
835 | |
836 dispose: function() | |
837 { | |
838 for (var i = 0; i < this._positions.length; ++i) | |
839 this._hideItemHighlight(i); | |
840 }, | |
841 | |
842 /** | |
843 * @param {number} x | |
844 * @param {number} y | |
845 * @return {number|undefined} | |
846 */ | |
847 findItemByCoordinates: function(x, y) | |
848 { | |
849 var textPosition = this._sourceFrame.textEditor.coordinatesToCursorPosit
ion(x, y); | |
850 if (!textPosition) | |
851 return; | |
852 | |
853 var ranges = this._editorRanges; | |
854 | |
855 for (var i = 0; i < ranges.length; ++i) { | |
856 var nextRange = ranges[i]; | |
857 if (nextRange.startLine == textPosition.startLine && nextRange.startCo
lumn <= textPosition.startColumn && nextRange.endColumn >= textPosition.startCol
umn) | |
858 return i; | |
859 } | |
860 }, | |
861 | |
862 /** | |
863 * @return {number|undefined} | |
864 */ | |
865 getSelectedItemIndex: function() | |
866 { | |
867 if (this._currentSelection === this._positions.length) | |
868 return undefined; | |
869 return this._currentSelection; | |
870 }, | |
871 | |
872 /** | |
873 * @return {!WebInspector.DebuggerModel.Location} | |
874 */ | |
875 getRawPosition: function(position) | |
876 { | |
877 return /** @type {!WebInspector.DebuggerModel.Location} */ (this._positi
ons[position]); | |
878 } | |
879 | |
880 }; | |
881 | |
882 /** | |
883 * @param {!WebInspector.JavaScriptSourceFrame} sourceFrame | |
884 * @param {!Array.<!DebuggerAgent.Location>} stepIntoRawLocations | |
885 * @return {?WebInspector.JavaScriptSourceFrame.StepIntoMarkup} | |
886 */ | |
887 WebInspector.JavaScriptSourceFrame.StepIntoMarkup.create = function(sourceFrame,
stepIntoRawLocations) | |
888 { | |
889 if (!stepIntoRawLocations.length) | |
890 return null; | |
891 | |
892 var firstToExecute = stepIntoRawLocations[0]; | |
893 stepIntoRawLocations.sort(WebInspector.JavaScriptSourceFrame.StepIntoMarkup.
_Comparator); | |
894 var firstToExecuteIndex = stepIntoRawLocations.indexOf(firstToExecute); | |
895 | |
896 var textEditor = sourceFrame.textEditor; | |
897 var uiRanges = []; | |
898 for (var i = 0; i < stepIntoRawLocations.length; ++i) { | |
899 var uiLocation = WebInspector.debuggerModel.rawLocationToUILocation(/**
@type {!WebInspector.DebuggerModel.Location} */ (stepIntoRawLocations[i])); | |
900 | |
901 var token = textEditor.tokenAtTextPosition(uiLocation.lineNumber, uiLoca
tion.columnNumber); | |
902 var startColumn; | |
903 var endColumn; | |
904 if (token) { | |
905 startColumn = token.startColumn; | |
906 endColumn = token.endColumn; | |
907 } else { | |
908 startColumn = uiLocation.columnNumber; | |
909 endColumn = uiLocation.columnNumber; | |
910 } | |
911 var range = new WebInspector.TextRange(uiLocation.lineNumber, startColum
n, uiLocation.lineNumber, endColumn); | |
912 uiRanges.push(range); | |
913 } | |
914 | |
915 return new WebInspector.JavaScriptSourceFrame.StepIntoMarkup(stepIntoRawLoca
tions, uiRanges, firstToExecuteIndex, sourceFrame); | |
916 }; | |
917 | |
918 /** | |
919 * @param {!DebuggerAgent.Location} locationA | |
920 * @param {!DebuggerAgent.Location} locationB | |
921 * @return {number} | |
922 */ | |
923 WebInspector.JavaScriptSourceFrame.StepIntoMarkup._Comparator = function(locatio
nA, locationB) | |
924 { | |
925 if (locationA.lineNumber === locationB.lineNumber) | |
926 return locationA.columnNumber - locationB.columnNumber; | |
927 else | |
928 return locationA.lineNumber - locationB.lineNumber; | |
929 }; | |
OLD | NEW |