JavaScriptSourceFrame.js 36 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900
  1. /*
  2. * Copyright (C) 2011 Google Inc. All rights reserved.
  3. *
  4. * Redistribution and use in source and binary forms, with or without
  5. * modification, are permitted provided that the following conditions are
  6. * met:
  7. *
  8. * * Redistributions of source code must retain the above copyright
  9. * notice, this list of conditions and the following disclaimer.
  10. * * Redistributions in binary form must reproduce the above
  11. * copyright notice, this list of conditions and the following disclaimer
  12. * in the documentation and/or other materials provided with the
  13. * distribution.
  14. * * Neither the name of Google Inc. nor the names of its
  15. * contributors may be used to endorse or promote products derived from
  16. * this software without specific prior written permission.
  17. *
  18. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  19. * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  20. * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  21. * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  22. * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  23. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  24. * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  25. * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  26. * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  27. * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  28. * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  29. */
  30. /**
  31. * @constructor
  32. * @extends {WebInspector.UISourceCodeFrame}
  33. * @param {WebInspector.ScriptsPanel} scriptsPanel
  34. * @param {WebInspector.UISourceCode} uiSourceCode
  35. */
  36. WebInspector.JavaScriptSourceFrame = function(scriptsPanel, uiSourceCode)
  37. {
  38. this._scriptsPanel = scriptsPanel;
  39. this._breakpointManager = WebInspector.breakpointManager;
  40. this._uiSourceCode = uiSourceCode;
  41. WebInspector.UISourceCodeFrame.call(this, uiSourceCode);
  42. if (uiSourceCode.project().type() === WebInspector.projectTypes.Debugger)
  43. this.element.addStyleClass("source-frame-debugger-script");
  44. this._popoverHelper = new WebInspector.ObjectPopoverHelper(this.textEditor.element,
  45. this._getPopoverAnchor.bind(this), this._resolveObjectForPopover.bind(this), this._onHidePopover.bind(this), true);
  46. this.textEditor.element.addEventListener("keydown", this._onKeyDown.bind(this), true);
  47. this.textEditor.addEventListener(WebInspector.TextEditor.Events.GutterClick, this._handleGutterClick.bind(this), this);
  48. this.textEditor.element.addEventListener("mousedown", this._onMouseDownAndClick.bind(this, true), true);
  49. this.textEditor.element.addEventListener("click", this._onMouseDownAndClick.bind(this, false), true);
  50. this._breakpointManager.addEventListener(WebInspector.BreakpointManager.Events.BreakpointAdded, this._breakpointAdded, this);
  51. this._breakpointManager.addEventListener(WebInspector.BreakpointManager.Events.BreakpointRemoved, this._breakpointRemoved, this);
  52. this._uiSourceCode.addEventListener(WebInspector.UISourceCode.Events.ConsoleMessageAdded, this._consoleMessageAdded, this);
  53. this._uiSourceCode.addEventListener(WebInspector.UISourceCode.Events.ConsoleMessageRemoved, this._consoleMessageRemoved, this);
  54. this._uiSourceCode.addEventListener(WebInspector.UISourceCode.Events.ConsoleMessagesCleared, this._consoleMessagesCleared, this);
  55. this._uiSourceCode.addEventListener(WebInspector.UISourceCode.Events.SourceMappingChanged, this._onSourceMappingChanged, this);
  56. this._uiSourceCode.addEventListener(WebInspector.UISourceCode.Events.WorkingCopyChanged, this._workingCopyChanged, this);
  57. this._uiSourceCode.addEventListener(WebInspector.UISourceCode.Events.WorkingCopyCommitted, this._workingCopyCommitted, this);
  58. this._registerShortcuts();
  59. this._updateScriptFile();
  60. }
  61. WebInspector.JavaScriptSourceFrame.prototype = {
  62. _registerShortcuts: function()
  63. {
  64. var modifiers = WebInspector.KeyboardShortcut.Modifiers;
  65. this.addShortcut(WebInspector.KeyboardShortcut.makeKey("e", modifiers.Shift | modifiers.Ctrl), this._evaluateSelectionInConsole.bind(this));
  66. },
  67. /**
  68. * @param {Event=} event
  69. * @return {boolean}
  70. */
  71. _evaluateSelectionInConsole: function(event)
  72. {
  73. var selection = this.textEditor.selection();
  74. if (!selection || selection.isEmpty())
  75. return false;
  76. WebInspector.evaluateInConsole(this.textEditor.copyRange(selection));
  77. return true;
  78. },
  79. // View events
  80. wasShown: function()
  81. {
  82. WebInspector.UISourceCodeFrame.prototype.wasShown.call(this);
  83. },
  84. willHide: function()
  85. {
  86. WebInspector.UISourceCodeFrame.prototype.willHide.call(this);
  87. this._popoverHelper.hidePopover();
  88. },
  89. onUISourceCodeContentChanged: function()
  90. {
  91. this._removeAllBreakpoints();
  92. WebInspector.UISourceCodeFrame.prototype.onUISourceCodeContentChanged.call(this);
  93. },
  94. populateLineGutterContextMenu: function(contextMenu, lineNumber)
  95. {
  96. contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Continue to here" : "Continue to Here"), this._continueToLine.bind(this, lineNumber));
  97. var breakpoint = this._breakpointManager.findBreakpoint(this._uiSourceCode, lineNumber);
  98. if (!breakpoint) {
  99. // This row doesn't have a breakpoint: We want to show Add Breakpoint and Add and Edit Breakpoint.
  100. contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Add breakpoint" : "Add Breakpoint"), this._setBreakpoint.bind(this, lineNumber, "", true));
  101. contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Add conditional breakpoint…" : "Add Conditional Breakpoint…"), this._editBreakpointCondition.bind(this, lineNumber));
  102. } else {
  103. // This row has a breakpoint, we want to show edit and remove breakpoint, and either disable or enable.
  104. contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Remove breakpoint" : "Remove Breakpoint"), breakpoint.remove.bind(breakpoint));
  105. contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Edit breakpoint…" : "Edit Breakpoint…"), this._editBreakpointCondition.bind(this, lineNumber, breakpoint));
  106. if (breakpoint.enabled())
  107. contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Disable breakpoint" : "Disable Breakpoint"), breakpoint.setEnabled.bind(breakpoint, false));
  108. else
  109. contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Enable breakpoint" : "Enable Breakpoint"), breakpoint.setEnabled.bind(breakpoint, true));
  110. }
  111. },
  112. populateTextAreaContextMenu: function(contextMenu, lineNumber)
  113. {
  114. var textSelection = this.textEditor.selection();
  115. if (textSelection && !textSelection.isEmpty()) {
  116. var selection = this.textEditor.copyRange(textSelection);
  117. var addToWatchLabel = WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Add to watch" : "Add to Watch");
  118. contextMenu.appendItem(addToWatchLabel, this._scriptsPanel.addToWatch.bind(this._scriptsPanel, selection));
  119. var evaluateLabel = WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Evaluate in console" : "Evaluate in Console");
  120. contextMenu.appendItem(evaluateLabel, WebInspector.evaluateInConsole.bind(WebInspector, selection));
  121. contextMenu.appendSeparator();
  122. } else if (!this._uiSourceCode.isEditable() && this._uiSourceCode.contentType() === WebInspector.resourceTypes.Script) {
  123. function liveEdit(event)
  124. {
  125. var liveEditUISourceCode = WebInspector.liveEditSupport.uiSourceCodeForLiveEdit(this._uiSourceCode);
  126. this._scriptsPanel.showUISourceCode(liveEditUISourceCode, lineNumber)
  127. }
  128. // FIXME: Change condition above to explicitly check that current uiSourceCode is created by default debugger mapping
  129. // and move the code adding this menu item to generic context menu provider for UISourceCode.
  130. var liveEditLabel = WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Live edit" : "Live Edit");
  131. contextMenu.appendItem(liveEditLabel, liveEdit.bind(this));
  132. contextMenu.appendSeparator();
  133. }
  134. WebInspector.UISourceCodeFrame.prototype.populateTextAreaContextMenu.call(this, contextMenu, lineNumber);
  135. },
  136. _workingCopyChanged: function(event)
  137. {
  138. if (this._supportsEnabledBreakpointsWhileEditing() || this._scriptFile)
  139. return;
  140. if (this._uiSourceCode.isDirty())
  141. this._muteBreakpointsWhileEditing();
  142. else
  143. this._restoreBreakpointsAfterEditing();
  144. },
  145. _workingCopyCommitted: function(event)
  146. {
  147. if (this._supportsEnabledBreakpointsWhileEditing() || this._scriptFile)
  148. return;
  149. this._restoreBreakpointsAfterEditing();
  150. },
  151. _didMergeToVM: function()
  152. {
  153. if (this._supportsEnabledBreakpointsWhileEditing())
  154. return;
  155. this._restoreBreakpointsAfterEditing();
  156. },
  157. _didDivergeFromVM: function()
  158. {
  159. if (this._supportsEnabledBreakpointsWhileEditing())
  160. return;
  161. this._muteBreakpointsWhileEditing();
  162. },
  163. _muteBreakpointsWhileEditing: function()
  164. {
  165. if (this._muted)
  166. return;
  167. for (var lineNumber = 0; lineNumber < this._textEditor.linesCount; ++lineNumber) {
  168. var breakpointDecoration = this._textEditor.getAttribute(lineNumber, "breakpoint");
  169. if (!breakpointDecoration)
  170. continue;
  171. this._removeBreakpointDecoration(lineNumber);
  172. this._addBreakpointDecoration(lineNumber, breakpointDecoration.condition, breakpointDecoration.enabled, true);
  173. }
  174. this._muted = true;
  175. },
  176. _supportsEnabledBreakpointsWhileEditing: function()
  177. {
  178. return this._uiSourceCode.project().type() === WebInspector.projectTypes.Snippets;
  179. },
  180. _restoreBreakpointsAfterEditing: function()
  181. {
  182. delete this._muted;
  183. var breakpoints = {};
  184. // Save and remove muted breakpoint decorations.
  185. for (var lineNumber = 0; lineNumber < this._textEditor.linesCount; ++lineNumber) {
  186. var breakpointDecoration = this._textEditor.getAttribute(lineNumber, "breakpoint");
  187. if (breakpointDecoration) {
  188. breakpoints[lineNumber] = breakpointDecoration;
  189. this._removeBreakpointDecoration(lineNumber);
  190. }
  191. }
  192. // Remove all breakpoints.
  193. this._removeAllBreakpoints();
  194. // Restore all breakpoints from saved decorations.
  195. for (var lineNumberString in breakpoints) {
  196. var lineNumber = parseInt(lineNumberString, 10);
  197. if (isNaN(lineNumber))
  198. continue;
  199. var breakpointDecoration = breakpoints[lineNumberString];
  200. this._setBreakpoint(lineNumber, breakpointDecoration.condition, breakpointDecoration.enabled);
  201. }
  202. },
  203. _removeAllBreakpoints: function()
  204. {
  205. var breakpoints = this._breakpointManager.breakpointsForUISourceCode(this._uiSourceCode);
  206. for (var i = 0; i < breakpoints.length; ++i)
  207. breakpoints[i].remove();
  208. },
  209. _getPopoverAnchor: function(element, event)
  210. {
  211. if (!WebInspector.debuggerModel.isPaused())
  212. return null;
  213. var textPosition = this.textEditor.coordinatesToCursorPosition(event.x, event.y);
  214. if (!textPosition)
  215. return null;
  216. var mouseLine = textPosition.startLine;
  217. var mouseColumn = textPosition.startColumn;
  218. var textSelection = this.textEditor.selection().normalize();
  219. if (textSelection && !textSelection.isEmpty()) {
  220. if (textSelection.startLine !== textSelection.endLine || textSelection.startLine !== mouseLine || mouseColumn < textSelection.startColumn || mouseColumn > textSelection.endColumn)
  221. return null;
  222. var leftCorner = this.textEditor.cursorPositionToCoordinates(textSelection.startLine, textSelection.startColumn);
  223. var rightCorner = this.textEditor.cursorPositionToCoordinates(textSelection.endLine, textSelection.endColumn);
  224. var anchorBox = new AnchorBox(leftCorner.x, leftCorner.y, rightCorner.x - leftCorner.x, leftCorner.height);
  225. anchorBox.highlight = {
  226. lineNumber: textSelection.startLine,
  227. startColumn: textSelection.startColumn,
  228. endColumn: textSelection.endColumn - 1
  229. };
  230. anchorBox.forSelection = true;
  231. return anchorBox;
  232. }
  233. var token = this.textEditor.tokenAtTextPosition(textPosition.startLine, textPosition.startColumn);
  234. if (!token)
  235. return null;
  236. var lineNumber = textPosition.startLine;
  237. var line = this.textEditor.line(lineNumber);
  238. var tokenContent = line.substring(token.startColumn, token.endColumn + 1);
  239. if (token.type !== "javascript-ident" && (token.type !== "javascript-keyword" || tokenContent !== "this"))
  240. return null;
  241. var leftCorner = this.textEditor.cursorPositionToCoordinates(lineNumber, token.startColumn);
  242. var rightCorner = this.textEditor.cursorPositionToCoordinates(lineNumber, token.endColumn + 1);
  243. var anchorBox = new AnchorBox(leftCorner.x, leftCorner.y, rightCorner.x - leftCorner.x, leftCorner.height);
  244. anchorBox.highlight = {
  245. lineNumber: lineNumber,
  246. startColumn: token.startColumn,
  247. endColumn: token.endColumn
  248. };
  249. return anchorBox;
  250. },
  251. _resolveObjectForPopover: function(anchorBox, showCallback, objectGroupName)
  252. {
  253. /**
  254. * @param {?RuntimeAgent.RemoteObject} result
  255. * @param {boolean=} wasThrown
  256. */
  257. function showObjectPopover(result, wasThrown)
  258. {
  259. if (!WebInspector.debuggerModel.isPaused()) {
  260. this._popoverHelper.hidePopover();
  261. return;
  262. }
  263. this._popoverAnchorBox = anchorBox;
  264. showCallback(WebInspector.RemoteObject.fromPayload(result), wasThrown, this._popoverAnchorBox);
  265. // Popover may have been removed by showCallback().
  266. if (this._popoverAnchorBox) {
  267. var highlightRange = new WebInspector.TextRange(lineNumber, startHighlight, lineNumber, endHighlight);
  268. this._popoverAnchorBox._highlightDescriptor = this.textEditor.highlightRange(highlightRange, "source-frame-eval-expression");
  269. }
  270. }
  271. if (!WebInspector.debuggerModel.isPaused()) {
  272. this._popoverHelper.hidePopover();
  273. return;
  274. }
  275. var lineNumber = anchorBox.highlight.lineNumber;
  276. var startHighlight = anchorBox.highlight.startColumn;
  277. var endHighlight = anchorBox.highlight.endColumn;
  278. var line = this.textEditor.line(lineNumber);
  279. if (!anchorBox.forSelection) {
  280. while (startHighlight > 1 && line.charAt(startHighlight - 1) === '.')
  281. startHighlight = this.textEditor.tokenAtTextPosition(lineNumber, startHighlight - 2).startColumn;
  282. }
  283. var evaluationText = line.substring(startHighlight, endHighlight + 1);
  284. var selectedCallFrame = WebInspector.debuggerModel.selectedCallFrame();
  285. selectedCallFrame.evaluate(evaluationText, objectGroupName, false, true, false, false, showObjectPopover.bind(this));
  286. },
  287. _onHidePopover: function()
  288. {
  289. if (!this._popoverAnchorBox)
  290. return;
  291. if (this._popoverAnchorBox._highlightDescriptor)
  292. this.textEditor.removeHighlight(this._popoverAnchorBox._highlightDescriptor);
  293. delete this._popoverAnchorBox;
  294. },
  295. /**
  296. * @param {number} lineNumber
  297. * @param {string} condition
  298. * @param {boolean} enabled
  299. * @param {boolean} mutedWhileEditing
  300. */
  301. _addBreakpointDecoration: function(lineNumber, condition, enabled, mutedWhileEditing)
  302. {
  303. var breakpoint = {
  304. condition: condition,
  305. enabled: enabled
  306. };
  307. this.textEditor.setAttribute(lineNumber, "breakpoint", breakpoint);
  308. var disabled = !enabled || mutedWhileEditing;
  309. this.textEditor.addBreakpoint(lineNumber, disabled, !!condition);
  310. },
  311. _removeBreakpointDecoration: function(lineNumber)
  312. {
  313. this.textEditor.removeAttribute(lineNumber, "breakpoint");
  314. this.textEditor.removeBreakpoint(lineNumber);
  315. },
  316. _onKeyDown: function(event)
  317. {
  318. if (event.keyIdentifier === "U+001B") { // Escape key
  319. if (this._popoverHelper.isPopoverVisible()) {
  320. this._popoverHelper.hidePopover();
  321. event.consume();
  322. return;
  323. }
  324. if (this._stepIntoMarkup && WebInspector.KeyboardShortcut.eventHasCtrlOrMeta(event)) {
  325. this._stepIntoMarkup.stoptIteratingSelection();
  326. event.consume();
  327. return;
  328. }
  329. }
  330. },
  331. /**
  332. * @param {number} lineNumber
  333. * @param {WebInspector.BreakpointManager.Breakpoint=} breakpoint
  334. */
  335. _editBreakpointCondition: function(lineNumber, breakpoint)
  336. {
  337. this._conditionElement = this._createConditionElement(lineNumber);
  338. this.textEditor.addDecoration(lineNumber, this._conditionElement);
  339. function finishEditing(committed, element, newText)
  340. {
  341. this.textEditor.removeDecoration(lineNumber, this._conditionElement);
  342. delete this._conditionEditorElement;
  343. delete this._conditionElement;
  344. if (!committed)
  345. return;
  346. if (breakpoint)
  347. breakpoint.setCondition(newText);
  348. else
  349. this._setBreakpoint(lineNumber, newText, true);
  350. }
  351. var config = new WebInspector.EditingConfig(finishEditing.bind(this, true), finishEditing.bind(this, false));
  352. WebInspector.startEditing(this._conditionEditorElement, config);
  353. this._conditionEditorElement.value = breakpoint ? breakpoint.condition() : "";
  354. this._conditionEditorElement.select();
  355. },
  356. _createConditionElement: function(lineNumber)
  357. {
  358. var conditionElement = document.createElement("div");
  359. conditionElement.className = "source-frame-breakpoint-condition";
  360. var labelElement = document.createElement("label");
  361. labelElement.className = "source-frame-breakpoint-message";
  362. labelElement.htmlFor = "source-frame-breakpoint-condition";
  363. labelElement.appendChild(document.createTextNode(WebInspector.UIString("The breakpoint on line %d will stop only if this expression is true:", lineNumber)));
  364. conditionElement.appendChild(labelElement);
  365. var editorElement = document.createElement("input");
  366. editorElement.id = "source-frame-breakpoint-condition";
  367. editorElement.className = "monospace";
  368. editorElement.type = "text";
  369. conditionElement.appendChild(editorElement);
  370. this._conditionEditorElement = editorElement;
  371. return conditionElement;
  372. },
  373. /**
  374. * @param {number} lineNumber
  375. * @param {WebInspector.DebuggerModel.CallFrame} callFrame
  376. */
  377. setExecutionLine: function(lineNumber, callFrame)
  378. {
  379. this._executionLineNumber = lineNumber;
  380. this._executionCallFrame = callFrame;
  381. if (this.loaded) {
  382. this.textEditor.setExecutionLine(lineNumber);
  383. if (WebInspector.experimentsSettings.stepIntoSelection.isEnabled()) {
  384. /**
  385. * @param {Array.<DebuggerAgent.Location>} locations
  386. */
  387. function locationsCallback(locations)
  388. {
  389. if (this._executionCallFrame !== callFrame || this._stepIntoMarkup)
  390. return;
  391. this._stepIntoMarkup = WebInspector.JavaScriptSourceFrame.StepIntoMarkup.create(this, locations);
  392. if (this._stepIntoMarkup)
  393. this._stepIntoMarkup.show();
  394. }
  395. callFrame.getStepIntoLocations(locationsCallback.bind(this));
  396. }
  397. }
  398. },
  399. clearExecutionLine: function()
  400. {
  401. if (this._stepIntoMarkup) {
  402. this._stepIntoMarkup.dispose();
  403. delete this._stepIntoMarkup;
  404. }
  405. if (this.loaded && typeof this._executionLineNumber === "number")
  406. this.textEditor.clearExecutionLine();
  407. delete this._executionLineNumber;
  408. delete this._executionCallFrame;
  409. },
  410. _lineNumberAfterEditing: function(lineNumber, oldRange, newRange)
  411. {
  412. var shiftOffset = lineNumber <= oldRange.startLine ? 0 : newRange.linesCount - oldRange.linesCount;
  413. // Special case of editing the line itself. We should decide whether the line number should move below or not.
  414. if (lineNumber === oldRange.startLine) {
  415. var whiteSpacesRegex = /^[\s\xA0]*$/;
  416. for (var i = 0; lineNumber + i <= newRange.endLine; ++i) {
  417. if (!whiteSpacesRegex.test(this.textEditor.line(lineNumber + i))) {
  418. shiftOffset = i;
  419. break;
  420. }
  421. }
  422. }
  423. var newLineNumber = Math.max(0, lineNumber + shiftOffset);
  424. if (oldRange.startLine < lineNumber && lineNumber < oldRange.endLine)
  425. newLineNumber = oldRange.startLine;
  426. return newLineNumber;
  427. },
  428. _onMouseDownAndClick: function(isMouseDown, event)
  429. {
  430. var markup = this._stepIntoMarkup;
  431. if (!markup)
  432. return;
  433. var index = markup.findItemByCoordinates(event.x, event.y);
  434. if (typeof index === "undefined")
  435. return;
  436. if (isMouseDown) {
  437. // Do not let text editor to spoil 'click' event that is coming for us.
  438. event.consume();
  439. } else {
  440. var rawLocation = markup.getRawPosition(index);
  441. this._scriptsPanel.doStepIntoSelection(rawLocation);
  442. }
  443. },
  444. /**
  445. * @return {boolean}
  446. */
  447. _shouldIgnoreExternalBreakpointEvents: function()
  448. {
  449. if (this._supportsEnabledBreakpointsWhileEditing())
  450. return false;
  451. if (this._muted)
  452. return true;
  453. return this._scriptFile && (this._scriptFile.isDivergingFromVM() || this._scriptFile.isMergingToVM());
  454. },
  455. _breakpointAdded: function(event)
  456. {
  457. var uiLocation = /** @type {WebInspector.UILocation} */ (event.data.uiLocation);
  458. if (uiLocation.uiSourceCode !== this._uiSourceCode)
  459. return;
  460. if (this._shouldIgnoreExternalBreakpointEvents())
  461. return;
  462. var breakpoint = /** @type {WebInspector.BreakpointManager.Breakpoint} */ (event.data.breakpoint);
  463. if (this.loaded)
  464. this._addBreakpointDecoration(uiLocation.lineNumber, breakpoint.condition(), breakpoint.enabled(), false);
  465. },
  466. _breakpointRemoved: function(event)
  467. {
  468. var uiLocation = /** @type {WebInspector.UILocation} */ (event.data.uiLocation);
  469. if (uiLocation.uiSourceCode !== this._uiSourceCode)
  470. return;
  471. if (this._shouldIgnoreExternalBreakpointEvents())
  472. return;
  473. var breakpoint = /** @type {WebInspector.BreakpointManager.Breakpoint} */ (event.data.breakpoint);
  474. var remainingBreakpoint = this._breakpointManager.findBreakpoint(this._uiSourceCode, uiLocation.lineNumber);
  475. if (!remainingBreakpoint && this.loaded)
  476. this._removeBreakpointDecoration(uiLocation.lineNumber);
  477. },
  478. _consoleMessageAdded: function(event)
  479. {
  480. var message = /** @type {WebInspector.PresentationConsoleMessage} */ (event.data);
  481. if (this.loaded)
  482. this.addMessageToSource(message.lineNumber, message.originalMessage);
  483. },
  484. _consoleMessageRemoved: function(event)
  485. {
  486. var message = /** @type {WebInspector.PresentationConsoleMessage} */ (event.data);
  487. if (this.loaded)
  488. this.removeMessageFromSource(message.lineNumber, message.originalMessage);
  489. },
  490. _consoleMessagesCleared: function(event)
  491. {
  492. this.clearMessages();
  493. },
  494. /**
  495. * @param {WebInspector.Event} event
  496. */
  497. _onSourceMappingChanged: function(event)
  498. {
  499. this._updateScriptFile();
  500. },
  501. _updateScriptFile: function()
  502. {
  503. if (this._scriptFile) {
  504. this._scriptFile.removeEventListener(WebInspector.ScriptFile.Events.DidMergeToVM, this._didMergeToVM, this);
  505. this._scriptFile.removeEventListener(WebInspector.ScriptFile.Events.DidDivergeFromVM, this._didDivergeFromVM, this);
  506. if (this._muted && !this._uiSourceCode.isDirty())
  507. this._restoreBreakpointsAfterEditing();
  508. }
  509. this._scriptFile = this._uiSourceCode.scriptFile();
  510. if (this._scriptFile) {
  511. this._scriptFile.addEventListener(WebInspector.ScriptFile.Events.DidMergeToVM, this._didMergeToVM, this);
  512. this._scriptFile.addEventListener(WebInspector.ScriptFile.Events.DidDivergeFromVM, this._didDivergeFromVM, this);
  513. if (this.loaded)
  514. this._scriptFile.checkMapping();
  515. }
  516. },
  517. onTextEditorContentLoaded: function()
  518. {
  519. if (typeof this._executionLineNumber === "number")
  520. this.setExecutionLine(this._executionLineNumber, this._executionCallFrame);
  521. var breakpointLocations = this._breakpointManager.breakpointLocationsForUISourceCode(this._uiSourceCode);
  522. for (var i = 0; i < breakpointLocations.length; ++i)
  523. this._breakpointAdded({data:breakpointLocations[i]});
  524. var messages = this._uiSourceCode.consoleMessages();
  525. for (var i = 0; i < messages.length; ++i) {
  526. var message = messages[i];
  527. this.addMessageToSource(message.lineNumber, message.originalMessage);
  528. }
  529. if (this._scriptFile)
  530. this._scriptFile.checkMapping();
  531. },
  532. /**
  533. * @param {Event} event
  534. */
  535. _handleGutterClick: function(event)
  536. {
  537. if (this._muted)
  538. return;
  539. var eventData = /** @type {WebInspector.TextEditor.GutterClickEventData} */ (event.data);
  540. var lineNumber = eventData.lineNumber;
  541. var eventObject = /** @type {Event} */ (eventData.event);
  542. if (eventObject.button != 0 || eventObject.altKey || eventObject.ctrlKey || eventObject.metaKey)
  543. return;
  544. this._toggleBreakpoint(lineNumber, eventObject.shiftKey);
  545. eventObject.consume(true);
  546. },
  547. /**
  548. * @param {number} lineNumber
  549. * @param {boolean} onlyDisable
  550. */
  551. _toggleBreakpoint: function(lineNumber, onlyDisable)
  552. {
  553. var breakpoint = this._breakpointManager.findBreakpoint(this._uiSourceCode, lineNumber);
  554. if (breakpoint) {
  555. if (onlyDisable)
  556. breakpoint.setEnabled(!breakpoint.enabled());
  557. else
  558. breakpoint.remove();
  559. } else
  560. this._setBreakpoint(lineNumber, "", true);
  561. },
  562. toggleBreakpointOnCurrentLine: function()
  563. {
  564. if (this._muted)
  565. return;
  566. var selection = this.textEditor.selection();
  567. if (!selection)
  568. return;
  569. this._toggleBreakpoint(selection.startLine, false);
  570. },
  571. /**
  572. * @param {number} lineNumber
  573. * @param {string} condition
  574. * @param {boolean} enabled
  575. */
  576. _setBreakpoint: function(lineNumber, condition, enabled)
  577. {
  578. this._breakpointManager.setBreakpoint(this._uiSourceCode, lineNumber, condition, enabled);
  579. WebInspector.notifications.dispatchEventToListeners(WebInspector.UserMetrics.UserAction, {
  580. action: WebInspector.UserMetrics.UserActionNames.SetBreakpoint,
  581. url: this._uiSourceCode.originURL(),
  582. line: lineNumber,
  583. enabled: enabled
  584. });
  585. },
  586. /**
  587. * @param {number} lineNumber
  588. */
  589. _continueToLine: function(lineNumber)
  590. {
  591. var rawLocation = /** @type {WebInspector.DebuggerModel.Location} */ (this._uiSourceCode.uiLocationToRawLocation(lineNumber, 0));
  592. this._scriptsPanel.continueToLocation(rawLocation);
  593. },
  594. /**
  595. * @return {WebInspector.JavaScriptSourceFrame.StepIntoMarkup|undefined}
  596. */
  597. stepIntoMarkup: function()
  598. {
  599. return this._stepIntoMarkup;
  600. },
  601. dispose: function()
  602. {
  603. this._breakpointManager.removeEventListener(WebInspector.BreakpointManager.Events.BreakpointAdded, this._breakpointAdded, this);
  604. this._breakpointManager.removeEventListener(WebInspector.BreakpointManager.Events.BreakpointRemoved, this._breakpointRemoved, this);
  605. this._uiSourceCode.removeEventListener(WebInspector.UISourceCode.Events.ConsoleMessageAdded, this._consoleMessageAdded, this);
  606. this._uiSourceCode.removeEventListener(WebInspector.UISourceCode.Events.ConsoleMessageRemoved, this._consoleMessageRemoved, this);
  607. this._uiSourceCode.removeEventListener(WebInspector.UISourceCode.Events.ConsoleMessagesCleared, this._consoleMessagesCleared, this);
  608. this._uiSourceCode.removeEventListener(WebInspector.UISourceCode.Events.SourceMappingChanged, this._onSourceMappingChanged, this);
  609. this._uiSourceCode.removeEventListener(WebInspector.UISourceCode.Events.WorkingCopyChanged, this._workingCopyChanged, this);
  610. this._uiSourceCode.removeEventListener(WebInspector.UISourceCode.Events.WorkingCopyCommitted, this._workingCopyCommitted, this);
  611. WebInspector.UISourceCodeFrame.prototype.dispose.call(this);
  612. },
  613. __proto__: WebInspector.UISourceCodeFrame.prototype
  614. }
  615. /**
  616. * @constructor
  617. * @param {Array.<DebuggerAgent.Location>} rawPositions
  618. * @param {Array.<WebInspector.TextRange>} editorRanges
  619. * @param {number} firstToExecute
  620. * @param {WebInspector.JavaScriptSourceFrame} sourceFrame
  621. */
  622. WebInspector.JavaScriptSourceFrame.StepIntoMarkup = function(rawPositions, editorRanges, firstToExecute, sourceFrame)
  623. {
  624. this._positions = rawPositions;
  625. this._editorRanges = editorRanges;
  626. this._highlightDescriptors = new Array(rawPositions.length);
  627. this._currentHighlight = undefined;
  628. this._firstToExecute = firstToExecute;
  629. this._currentSelection = undefined;
  630. this._sourceFrame = sourceFrame;
  631. };
  632. WebInspector.JavaScriptSourceFrame.StepIntoMarkup.prototype = {
  633. show: function()
  634. {
  635. var highlight = this._getVisibleHighlight();
  636. for (var i = 0; i < this._positions.length; ++i)
  637. this._highlightItem(i, i === highlight);
  638. this._shownVisibleHighlight = highlight;
  639. },
  640. startIteratingSelection: function()
  641. {
  642. this._currentSelection = this._positions.length
  643. this._redrawHighlight();
  644. },
  645. stopIteratingSelection: function()
  646. {
  647. this._currentSelection = undefined;
  648. this._redrawHighlight();
  649. },
  650. /**
  651. * @param {boolean} backward
  652. */
  653. iterateSelection: function(backward)
  654. {
  655. if (typeof this._currentSelection === "undefined")
  656. return;
  657. var nextSelection = backward ? this._currentSelection - 1 : this._currentSelection + 1;
  658. var modulo = this._positions.length + 1;
  659. nextSelection = (nextSelection + modulo) % modulo;
  660. this._currentSelection = nextSelection;
  661. this._redrawHighlight();
  662. },
  663. _redrawHighlight: function()
  664. {
  665. var visibleHighlight = this._getVisibleHighlight();
  666. if (this._shownVisibleHighlight === visibleHighlight)
  667. return;
  668. this._hideItemHighlight(this._shownVisibleHighlight);
  669. this._hideItemHighlight(visibleHighlight);
  670. this._highlightItem(this._shownVisibleHighlight, false);
  671. this._highlightItem(visibleHighlight, true);
  672. this._shownVisibleHighlight = visibleHighlight;
  673. },
  674. /**
  675. * @return {number}
  676. */
  677. _getVisibleHighlight: function()
  678. {
  679. return typeof this._currentSelection === "undefined" ? this._firstToExecute : this._currentSelection;
  680. },
  681. /**
  682. * @param {number} position
  683. * @param {boolean} selected
  684. */
  685. _highlightItem: function(position, selected)
  686. {
  687. if (position === this._positions.length)
  688. return;
  689. var styleName = selected ? "source-frame-stepin-mark-highlighted" : "source-frame-stepin-mark";
  690. var textEditor = this._sourceFrame.textEditor;
  691. var highlightDescriptor = textEditor.highlightRange(this._editorRanges[position], styleName);
  692. this._highlightDescriptors[position] = highlightDescriptor;
  693. },
  694. /**
  695. * @param {number} position
  696. */
  697. _hideItemHighlight: function(position)
  698. {
  699. if (position === this._positions.length)
  700. return;
  701. var highlightDescriptor = this._highlightDescriptors[position];
  702. console.assert(highlightDescriptor);
  703. var textEditor = this._sourceFrame.textEditor;
  704. textEditor.removeHighlight(highlightDescriptor);
  705. this._highlightDescriptors[position] = undefined;
  706. },
  707. dispose: function()
  708. {
  709. for (var i = 0; i < this._positions.length; ++i)
  710. this._hideItemHighlight(i);
  711. },
  712. /**
  713. * @param {number} x
  714. * @param {number} y
  715. * @return {number|undefined}
  716. */
  717. findItemByCoordinates: function(x, y)
  718. {
  719. var textPosition = this._sourceFrame.textEditor.coordinatesToCursorPosition(x, y);
  720. if (!textPosition)
  721. return;
  722. var ranges = this._editorRanges;
  723. for (var i = 0; i < ranges.length; ++i) {
  724. var nextRange = ranges[i];
  725. if (nextRange.startLine == textPosition.startLine && nextRange.startColumn <= textPosition.startColumn && nextRange.endColumn >= textPosition.startColumn)
  726. return i;
  727. }
  728. },
  729. /**
  730. * @return {number|undefined}
  731. */
  732. getSelectedItemIndex: function()
  733. {
  734. if (this._currentSelection === this._positions.length)
  735. return undefined;
  736. return this._currentSelection;
  737. },
  738. /**
  739. * @return {WebInspector.DebuggerModel.Location}
  740. */
  741. getRawPosition: function(position)
  742. {
  743. return /** @type {WebInspector.DebuggerModel.Location} */ (this._positions[position]);
  744. }
  745. };
  746. /**
  747. * @param {WebInspector.JavaScriptSourceFrame} sourceFrame
  748. * @param {Array.<DebuggerAgent.Location>} stepIntoRawLocations
  749. * @return {?WebInspector.JavaScriptSourceFrame.StepIntoMarkup}
  750. */
  751. WebInspector.JavaScriptSourceFrame.StepIntoMarkup.create = function(sourceFrame, stepIntoRawLocations)
  752. {
  753. if (!stepIntoRawLocations.length)
  754. return null;
  755. var firstToExecute = stepIntoRawLocations[0];
  756. stepIntoRawLocations.sort(WebInspector.JavaScriptSourceFrame.StepIntoMarkup._Comparator);
  757. var firstToExecuteIndex = stepIntoRawLocations.indexOf(firstToExecute);
  758. var textEditor = sourceFrame.textEditor;
  759. var uiRanges = [];
  760. for (var i = 0; i < stepIntoRawLocations.length; ++i) {
  761. var uiLocation = WebInspector.debuggerModel.rawLocationToUILocation(/** @type {WebInspector.DebuggerModel.Location} */ (stepIntoRawLocations[i]));
  762. var token = textEditor.tokenAtTextPosition(uiLocation.lineNumber, uiLocation.columnNumber);
  763. var startColumn;
  764. var endColumn;
  765. if (token) {
  766. startColumn = token.startColumn;
  767. endColumn = token.endColumn;
  768. } else {
  769. startColumn = uiLocation.columnNumber;
  770. endColumn = uiLocation.columnNumber;
  771. }
  772. var range = new WebInspector.TextRange(uiLocation.lineNumber, startColumn, uiLocation.lineNumber, endColumn);
  773. uiRanges.push(range);
  774. }
  775. return new WebInspector.JavaScriptSourceFrame.StepIntoMarkup(stepIntoRawLocations, uiRanges, firstToExecuteIndex, sourceFrame);
  776. };
  777. /**
  778. * @param {DebuggerAgent.Location} locationA
  779. * @param {DebuggerAgent.Location} locationB
  780. * @return {number}
  781. */
  782. WebInspector.JavaScriptSourceFrame.StepIntoMarkup._Comparator = function(locationA, locationB)
  783. {
  784. if (locationA.lineNumber === locationB.lineNumber)
  785. return locationA.columnNumber - locationB.columnNumber;
  786. else
  787. return locationA.lineNumber - locationB.lineNumber;
  788. };