Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / third_party / WebKit / Source / devtools / front_end / sources / JavaScriptSourceFrame.js
blobd8700aeb48c337fab9a3723295331e0d87aa1ce0
1 /*
2 * Copyright (C) 2011 Google Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
6 * met:
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.
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.
31 /**
32 * @constructor
33 * @extends {WebInspector.UISourceCodeFrame}
34 * @param {!WebInspector.SourcesPanel} scriptsPanel
35 * @param {!WebInspector.UISourceCode} uiSourceCode
37 WebInspector.JavaScriptSourceFrame = function(scriptsPanel, uiSourceCode)
39 this._scriptsPanel = scriptsPanel;
40 this._breakpointManager = WebInspector.breakpointManager;
41 this._uiSourceCode = uiSourceCode;
43 WebInspector.UISourceCodeFrame.call(this, uiSourceCode);
44 if (uiSourceCode.project().type() === WebInspector.projectTypes.Debugger)
45 this.element.classList.add("source-frame-debugger-script");
47 this._popoverHelper = new WebInspector.ObjectPopoverHelper(scriptsPanel.element,
48 this._getPopoverAnchor.bind(this), this._resolveObjectForPopover.bind(this), this._onHidePopover.bind(this), true);
50 this.textEditor.element.addEventListener("keydown", this._onKeyDown.bind(this), true);
52 this.textEditor.addEventListener(WebInspector.CodeMirrorTextEditor.Events.GutterClick, this._handleGutterClick.bind(this), this);
54 this._breakpointManager.addEventListener(WebInspector.BreakpointManager.Events.BreakpointAdded, this._breakpointAdded, this);
55 this._breakpointManager.addEventListener(WebInspector.BreakpointManager.Events.BreakpointRemoved, this._breakpointRemoved, this);
57 WebInspector.presentationConsoleMessageHelper.addConsoleMessageEventListener(WebInspector.PresentationConsoleMessageHelper.Events.ConsoleMessageAdded, this._uiSourceCode, this._consoleMessageAdded, this);
58 WebInspector.presentationConsoleMessageHelper.addConsoleMessageEventListener(WebInspector.PresentationConsoleMessageHelper.Events.ConsoleMessageRemoved, this._uiSourceCode, this._consoleMessageRemoved, this);
59 WebInspector.presentationConsoleMessageHelper.addConsoleMessageEventListener(WebInspector.PresentationConsoleMessageHelper.Events.ConsoleMessagesCleared, this._uiSourceCode, this._consoleMessagesCleared, this);
60 this._uiSourceCode.addEventListener(WebInspector.UISourceCode.Events.SourceMappingChanged, this._onSourceMappingChanged, this);
61 this._uiSourceCode.addEventListener(WebInspector.UISourceCode.Events.WorkingCopyChanged, this._workingCopyChanged, this);
62 this._uiSourceCode.addEventListener(WebInspector.UISourceCode.Events.WorkingCopyCommitted, this._workingCopyCommitted, this);
63 this._uiSourceCode.addEventListener(WebInspector.UISourceCode.Events.TitleChanged, this._showBlackboxInfobarIfNeeded, this);
65 /** @type {!Map.<!WebInspector.Target, !WebInspector.ResourceScriptFile>}*/
66 this._scriptFileForTarget = new Map();
67 this._registerShortcuts();
68 var targets = WebInspector.targetManager.targets();
69 for (var i = 0; i < targets.length; ++i) {
70 var scriptFile = WebInspector.debuggerWorkspaceBinding.scriptFile(uiSourceCode, targets[i]);
71 if (scriptFile)
72 this._updateScriptFile(targets[i]);
75 if (this._scriptFileForTarget.size || uiSourceCode.extension() === "js")
76 this._compiler = new WebInspector.JavaScriptCompiler(this);
78 WebInspector.moduleSetting("skipStackFramesPattern").addChangeListener(this._showBlackboxInfobarIfNeeded, this);
79 WebInspector.moduleSetting("skipContentScripts").addChangeListener(this._showBlackboxInfobarIfNeeded, this);
80 this._showBlackboxInfobarIfNeeded();
81 /** @type {!Map.<number, !Element>} */
82 this._valueWidgets = new Map();
85 WebInspector.JavaScriptSourceFrame.prototype = {
86 _updateInfobars: function()
88 this.attachInfobars([this._blackboxInfobar, this._divergedInfobar]);
91 _showDivergedInfobar: function()
93 if (this._uiSourceCode.contentType() !== WebInspector.resourceTypes.Script)
94 return;
96 if (this._divergedInfobar)
97 this._divergedInfobar.dispose();
99 var infobar = new WebInspector.UISourceCodeFrame.Infobar(WebInspector.Infobar.Type.Warning, WebInspector.UIString("Workspace mapping mismatch"));
100 this._divergedInfobar = infobar;
102 var fileURL = this._uiSourceCode.originURL();
103 infobar.createDetailsRowMessage(WebInspector.UIString("The content of this file on the file system:\u00a0")).appendChild(
104 WebInspector.linkifyURLAsNode(fileURL, fileURL, "source-frame-infobar-details-url", true));
106 var scriptURL = WebInspector.networkMapping.networkURL(this._uiSourceCode);
107 infobar.createDetailsRowMessage(WebInspector.UIString("does not match the loaded script:\u00a0")).appendChild(
108 WebInspector.linkifyURLAsNode(scriptURL, scriptURL, "source-frame-infobar-details-url", true));
110 infobar.createDetailsRowMessage();
111 infobar.createDetailsRowMessage(WebInspector.UIString("Possible solutions are:"));
113 if (WebInspector.moduleSetting("cacheDisabled").get())
114 infobar.createDetailsRowMessage(" - ").createTextChild(WebInspector.UIString("Reload inspected page"));
115 else
116 infobar.createDetailsRowMessage(" - ").createTextChild(WebInspector.UIString("Check \"Disable cache\" in settings and reload inspected page (recommended setup for authoring and debugging)"));
117 infobar.createDetailsRowMessage(" - ").createTextChild(WebInspector.UIString("Check that your file and script are both loaded from the correct source and their contents match"));
119 this._updateInfobars();
122 _hideDivergedInfobar: function()
124 if (!this._divergedInfobar)
125 return;
126 this._divergedInfobar.dispose();
127 delete this._divergedInfobar;
130 _showBlackboxInfobarIfNeeded: function()
132 var contentType = this._uiSourceCode.contentType();
133 if (contentType !== WebInspector.resourceTypes.Script && contentType !== WebInspector.resourceTypes.Document)
134 return;
135 var projectType = this._uiSourceCode.project().type();
136 if (projectType === WebInspector.projectTypes.Snippets)
137 return;
138 var networkURL = WebInspector.networkMapping.networkURL(this._uiSourceCode);
139 var url = projectType === WebInspector.projectTypes.Formatter ? this._uiSourceCode.originURL() : networkURL;
140 var isContentScript = projectType === WebInspector.projectTypes.ContentScripts;
141 if (!WebInspector.BlackboxSupport.isBlackboxed(url, isContentScript)) {
142 this._hideBlackboxInfobar();
143 return;
146 if (this._blackboxInfobar)
147 this._blackboxInfobar.dispose();
149 var infobar = new WebInspector.UISourceCodeFrame.Infobar(WebInspector.Infobar.Type.Warning, WebInspector.UIString("This script is blackboxed in debugger"));
150 this._blackboxInfobar = infobar;
152 infobar.createDetailsRowMessage(WebInspector.UIString("Debugger will skip stepping through this script, and will not stop on exceptions"));
153 infobar.createDetailsRowMessage();
154 infobar.createDetailsRowMessage(WebInspector.UIString("Possible ways to cancel this behavior are:"));
156 infobar.createDetailsRowMessage(" - ").createTextChild(WebInspector.UIString("Press \"%s\" button in settings", WebInspector.manageBlackboxingButtonLabel()));
157 var unblackboxLink = infobar.createDetailsRowMessage(" - ").createChild("span", "link");
158 unblackboxLink.textContent = WebInspector.UIString("Unblackbox this script");
159 unblackboxLink.addEventListener("click", unblackbox, false);
161 function unblackbox()
163 WebInspector.BlackboxSupport.unblackbox(url, isContentScript);
166 this._updateInfobars();
169 _hideBlackboxInfobar: function()
171 if (!this._blackboxInfobar)
172 return;
173 this._blackboxInfobar.dispose();
174 delete this._blackboxInfobar;
177 _registerShortcuts: function()
179 var shortcutKeys = WebInspector.ShortcutsScreen.SourcesPanelShortcuts;
180 for (var i = 0; i < shortcutKeys.EvaluateSelectionInConsole.length; ++i) {
181 var keyDescriptor = shortcutKeys.EvaluateSelectionInConsole[i];
182 this.addShortcut(keyDescriptor.key, this._evaluateSelectionInConsole.bind(this));
184 for (var i = 0; i < shortcutKeys.AddSelectionToWatch.length; ++i) {
185 var keyDescriptor = shortcutKeys.AddSelectionToWatch[i];
186 this.addShortcut(keyDescriptor.key, this._addCurrentSelectionToWatch.bind(this));
190 _addCurrentSelectionToWatch: function()
192 var textSelection = this.textEditor.selection();
193 if (textSelection && !textSelection.isEmpty())
194 this._innerAddToWatch(this.textEditor.copyRange(textSelection));
195 return true;
199 * @param {string} expression
201 _innerAddToWatch: function(expression)
203 this._scriptsPanel.addToWatch(expression);
207 * @return {boolean}
209 _evaluateSelectionInConsole: function()
211 var selection = this.textEditor.selection();
212 if (!selection || selection.isEmpty())
213 return true;
214 this._evaluateInConsole(this.textEditor.copyRange(selection));
215 return true;
219 * @param {string} expression
221 _evaluateInConsole: function(expression)
223 var currentExecutionContext = WebInspector.context.flavor(WebInspector.ExecutionContext);
224 if (currentExecutionContext)
225 WebInspector.ConsoleModel.evaluateCommandInConsole(currentExecutionContext, expression);
229 * @override
231 wasShown: function()
233 WebInspector.UISourceCodeFrame.prototype.wasShown.call(this);
234 if (this._executionLocation && this.loaded) {
235 // We need CodeMirrorTextEditor to be initialized prior to this call. @see crbug.com/499889
236 setImmediate(this._generateValuesInSource.bind(this));
241 * @override
243 willHide: function()
245 WebInspector.UISourceCodeFrame.prototype.willHide.call(this);
246 this._popoverHelper.hidePopover();
249 onUISourceCodeContentChanged: function()
251 this._removeAllBreakpoints();
252 WebInspector.UISourceCodeFrame.prototype.onUISourceCodeContentChanged.call(this);
255 onTextChanged: function(oldRange, newRange)
257 this._scriptsPanel.setIgnoreExecutionLineEvents(true);
258 WebInspector.UISourceCodeFrame.prototype.onTextChanged.call(this, oldRange, newRange);
259 this._scriptsPanel.setIgnoreExecutionLineEvents(false);
260 if (this._compiler)
261 this._compiler.scheduleCompile();
264 populateLineGutterContextMenu: function(contextMenu, lineNumber)
266 var uiLocation = new WebInspector.UILocation(this._uiSourceCode, lineNumber, 0);
267 this._scriptsPanel.appendUILocationItems(contextMenu, uiLocation);
268 var breakpoint = this._breakpointManager.findBreakpointOnLine(this._uiSourceCode, lineNumber);
269 if (!breakpoint) {
270 // This row doesn't have a breakpoint: We want to show Add Breakpoint and Add and Edit Breakpoint.
271 contextMenu.appendItem(WebInspector.UIString.capitalize("Add ^breakpoint"), this._createNewBreakpoint.bind(this, lineNumber, 0, "", true));
272 contextMenu.appendItem(WebInspector.UIString.capitalize("Add ^conditional ^breakpoint…"), this._editBreakpointCondition.bind(this, lineNumber));
273 } else {
274 // This row has a breakpoint, we want to show edit and remove breakpoint, and either disable or enable.
275 contextMenu.appendItem(WebInspector.UIString.capitalize("Remove ^breakpoint"), breakpoint.remove.bind(breakpoint));
276 contextMenu.appendItem(WebInspector.UIString.capitalize("Edit ^breakpoint…"), this._editBreakpointCondition.bind(this, lineNumber, breakpoint));
277 if (breakpoint.enabled())
278 contextMenu.appendItem(WebInspector.UIString.capitalize("Disable ^breakpoint"), breakpoint.setEnabled.bind(breakpoint, false));
279 else
280 contextMenu.appendItem(WebInspector.UIString.capitalize("Enable ^breakpoint"), breakpoint.setEnabled.bind(breakpoint, true));
284 populateTextAreaContextMenu: function(contextMenu, lineNumber, columnNumber)
286 var textSelection = this.textEditor.selection();
287 if (textSelection && !textSelection.isEmpty()) {
288 var selection = this.textEditor.copyRange(textSelection);
289 var addToWatchLabel = WebInspector.UIString.capitalize("Add to ^watch");
290 contextMenu.appendItem(addToWatchLabel, this._innerAddToWatch.bind(this, selection));
291 var evaluateLabel = WebInspector.UIString.capitalize("Evaluate in ^console");
292 contextMenu.appendItem(evaluateLabel, this._evaluateInConsole.bind(this, selection));
293 contextMenu.appendSeparator();
297 * @this {WebInspector.JavaScriptSourceFrame}
298 * @param {!WebInspector.ResourceScriptFile} scriptFile
300 function addSourceMapURL(scriptFile)
302 WebInspector.AddSourceMapURLDialog.show(this.element, addSourceMapURLDialogCallback.bind(null, scriptFile));
306 * @param {!WebInspector.ResourceScriptFile} scriptFile
307 * @param {string} url
309 function addSourceMapURLDialogCallback(scriptFile, url)
311 if (!url)
312 return;
313 scriptFile.addSourceMapURL(url);
316 WebInspector.UISourceCodeFrame.prototype.populateTextAreaContextMenu.call(this, contextMenu, lineNumber, columnNumber);
318 if (this._uiSourceCode.project().type() === WebInspector.projectTypes.Network && WebInspector.moduleSetting("jsSourceMapsEnabled").get()) {
319 if (this._scriptFileForTarget.size) {
320 var scriptFile = this._scriptFileForTarget.valuesArray()[0];
321 var addSourceMapURLLabel = WebInspector.UIString.capitalize("Add ^source ^map\u2026");
322 contextMenu.appendItem(addSourceMapURLLabel, addSourceMapURL.bind(this, scriptFile));
323 contextMenu.appendSeparator();
328 _workingCopyChanged: function(event)
330 if (this._supportsEnabledBreakpointsWhileEditing() || this._scriptFileForTarget.size)
331 return;
333 if (this._uiSourceCode.isDirty())
334 this._muteBreakpointsWhileEditing();
335 else
336 this._restoreBreakpointsAfterEditing();
339 _workingCopyCommitted: function(event)
341 if (this._supportsEnabledBreakpointsWhileEditing())
342 return;
344 if (!this._scriptFileForTarget.size) {
345 this._restoreBreakpointsAfterEditing();
346 return;
349 var liveEditError;
350 var liveEditErrorData;
351 var contextScript;
352 var succeededEdits = 0;
353 var failedEdits = 0;
356 * @this {WebInspector.JavaScriptSourceFrame}
357 * @param {?string} error
358 * @param {!DebuggerAgent.SetScriptSourceError=} errorData
359 * @param {!WebInspector.Script=} script
361 function liveEditCallback(error, errorData, script)
363 this._scriptsPanel.setIgnoreExecutionLineEvents(false);
364 if (error) {
365 liveEditError = error;
366 liveEditErrorData = errorData;
367 contextScript = script;
368 ++failedEdits;
369 } else {
370 ++succeededEdits;
373 if (succeededEdits + failedEdits !== scriptFiles.length)
374 return;
376 if (failedEdits)
377 logLiveEditError.call(this, liveEditError, liveEditErrorData, contextScript);
381 * @param {?string} error
382 * @param {!DebuggerAgent.SetScriptSourceError=} errorData
383 * @param {!WebInspector.Script=} contextScript
384 * @this {WebInspector.JavaScriptSourceFrame}
386 function logLiveEditError(error, errorData, contextScript)
388 var warningLevel = WebInspector.Console.MessageLevel.Warning;
389 if (!errorData) {
390 if (error)
391 WebInspector.console.addMessage(WebInspector.UIString("LiveEdit failed: %s", error), warningLevel);
392 return;
394 var compileError = errorData.compileError;
395 if (compileError) {
396 var messageText = WebInspector.UIString("LiveEdit compile failed: %s", compileError.message);
397 var message = new WebInspector.SourceFrameMessage(messageText, WebInspector.SourceFrameMessage.Level.Error, compileError.lineNumber - 1, compileError.columnNumber + 1);
398 this.addMessageToSource(message);
399 } else {
400 WebInspector.console.addMessage(WebInspector.UIString("Unknown LiveEdit error: %s; %s", JSON.stringify(errorData), error), warningLevel);
404 this._scriptsPanel.setIgnoreExecutionLineEvents(true);
405 this._hasCommittedLiveEdit = true;
406 var scriptFiles = this._scriptFileForTarget.valuesArray();
407 for (var i = 0; i < scriptFiles.length; ++i)
408 scriptFiles[i].commitLiveEdit(liveEditCallback.bind(this));
411 _didMergeToVM: function()
413 if (this._supportsEnabledBreakpointsWhileEditing())
414 return;
415 this._updateDivergedInfobar();
416 this._restoreBreakpointsIfConsistentScripts();
419 _didDivergeFromVM: function()
421 if (this._supportsEnabledBreakpointsWhileEditing())
422 return;
423 this._updateDivergedInfobar();
424 this._muteBreakpointsWhileEditing();
427 _muteBreakpointsWhileEditing: function()
429 if (this._muted)
430 return;
431 for (var lineNumber = 0; lineNumber < this._textEditor.linesCount; ++lineNumber) {
432 var breakpointDecoration = this._textEditor.getAttribute(lineNumber, "breakpoint");
433 if (!breakpointDecoration)
434 continue;
435 this._removeBreakpointDecoration(lineNumber);
436 this._addBreakpointDecoration(lineNumber, breakpointDecoration.columnNumber, breakpointDecoration.condition, breakpointDecoration.enabled, true);
438 this._muted = true;
441 _updateDivergedInfobar: function()
443 if (this._uiSourceCode.project().type() !== WebInspector.projectTypes.FileSystem) {
444 this._hideDivergedInfobar();
445 return;
448 var scriptFiles = this._scriptFileForTarget.valuesArray();
449 var hasDivergedScript = false;
450 for (var i = 0; i < scriptFiles.length; ++i)
451 hasDivergedScript = hasDivergedScript || scriptFiles[i].hasDivergedFromVM();
453 if (this._divergedInfobar) {
454 if (!hasDivergedScript || this._hasCommittedLiveEdit)
455 this._hideDivergedInfobar();
456 } else {
457 if (hasDivergedScript && !this._uiSourceCode.isDirty() && !this._hasCommittedLiveEdit)
458 this._showDivergedInfobar();
462 _supportsEnabledBreakpointsWhileEditing: function()
464 return this._uiSourceCode.project().type() === WebInspector.projectTypes.Snippets;
467 _restoreBreakpointsIfConsistentScripts: function()
469 var scriptFiles = this._scriptFileForTarget.valuesArray();
470 for (var i = 0; i < scriptFiles.length; ++i)
471 if (scriptFiles[i].hasDivergedFromVM() || scriptFiles[i].isMergingToVM())
472 return;
474 this._restoreBreakpointsAfterEditing();
477 _restoreBreakpointsAfterEditing: function()
479 delete this._muted;
480 var breakpoints = {};
481 // Save and remove muted breakpoint decorations.
482 for (var lineNumber = 0; lineNumber < this._textEditor.linesCount; ++lineNumber) {
483 var breakpointDecoration = this._textEditor.getAttribute(lineNumber, "breakpoint");
484 if (breakpointDecoration) {
485 breakpoints[lineNumber] = breakpointDecoration;
486 this._removeBreakpointDecoration(lineNumber);
490 // Remove all breakpoints.
491 this._removeAllBreakpoints();
493 // Restore all breakpoints from saved decorations.
494 for (var lineNumberString in breakpoints) {
495 var lineNumber = parseInt(lineNumberString, 10);
496 if (isNaN(lineNumber))
497 continue;
498 var breakpointDecoration = breakpoints[lineNumberString];
499 this._setBreakpoint(lineNumber, breakpointDecoration.columnNumber, breakpointDecoration.condition, breakpointDecoration.enabled);
503 _removeAllBreakpoints: function()
505 var breakpoints = this._breakpointManager.breakpointsForUISourceCode(this._uiSourceCode);
506 for (var i = 0; i < breakpoints.length; ++i)
507 breakpoints[i].remove();
511 * @param {string} tokenType
512 * @return {boolean}
514 _isIdentifier: function(tokenType)
516 return tokenType.startsWith("js-variable") || tokenType.startsWith("js-property") || tokenType == "js-def";
519 _getPopoverAnchor: function(element, event)
521 var target = WebInspector.context.flavor(WebInspector.Target);
522 var debuggerModel = WebInspector.DebuggerModel.fromTarget(target);
523 if (!debuggerModel || !debuggerModel.isPaused())
524 return;
526 var textPosition = this.textEditor.coordinatesToCursorPosition(event.x, event.y);
527 if (!textPosition)
528 return;
529 var mouseLine = textPosition.startLine;
530 var mouseColumn = textPosition.startColumn;
531 var textSelection = this.textEditor.selection().normalize();
532 if (textSelection && !textSelection.isEmpty()) {
533 if (textSelection.startLine !== textSelection.endLine || textSelection.startLine !== mouseLine || mouseColumn < textSelection.startColumn || mouseColumn > textSelection.endColumn)
534 return;
536 var leftCorner = this.textEditor.cursorPositionToCoordinates(textSelection.startLine, textSelection.startColumn);
537 var rightCorner = this.textEditor.cursorPositionToCoordinates(textSelection.endLine, textSelection.endColumn);
538 var anchorBox = new AnchorBox(leftCorner.x, leftCorner.y, rightCorner.x - leftCorner.x, leftCorner.height);
539 anchorBox.highlight = {
540 lineNumber: textSelection.startLine,
541 startColumn: textSelection.startColumn,
542 endColumn: textSelection.endColumn - 1
544 anchorBox.forSelection = true;
545 return anchorBox;
548 var token = this.textEditor.tokenAtTextPosition(textPosition.startLine, textPosition.startColumn);
549 if (!token || !token.type)
550 return;
551 var lineNumber = textPosition.startLine;
552 var line = this.textEditor.line(lineNumber);
553 var tokenContent = line.substring(token.startColumn, token.endColumn);
555 var isIdentifier = this._isIdentifier(token.type);
556 if (!isIdentifier && (token.type !== "js-keyword" || tokenContent !== "this"))
557 return;
559 var leftCorner = this.textEditor.cursorPositionToCoordinates(lineNumber, token.startColumn);
560 var rightCorner = this.textEditor.cursorPositionToCoordinates(lineNumber, token.endColumn - 1);
561 var anchorBox = new AnchorBox(leftCorner.x, leftCorner.y, rightCorner.x - leftCorner.x, leftCorner.height);
563 anchorBox.highlight = {
564 lineNumber: lineNumber,
565 startColumn: token.startColumn,
566 endColumn: token.endColumn - 1
569 return anchorBox;
572 _resolveObjectForPopover: function(anchorBox, showCallback, objectGroupName)
574 var target = WebInspector.context.flavor(WebInspector.Target);
575 var debuggerModel = WebInspector.DebuggerModel.fromTarget(target);
576 if (!debuggerModel || !debuggerModel.isPaused()) {
577 this._popoverHelper.hidePopover();
578 return;
580 var lineNumber = anchorBox.highlight.lineNumber;
581 var startHighlight = anchorBox.highlight.startColumn;
582 var endHighlight = anchorBox.highlight.endColumn;
583 var line = this.textEditor.line(lineNumber);
584 if (!anchorBox.forSelection) {
585 while (startHighlight > 1 && line.charAt(startHighlight - 1) === '.') {
586 var token = this.textEditor.tokenAtTextPosition(lineNumber, startHighlight - 2);
587 if (!token || !token.type) {
588 this._popoverHelper.hidePopover();
589 return;
591 startHighlight = token.startColumn;
594 var evaluationText = line.substring(startHighlight, endHighlight + 1);
595 var selectedCallFrame = debuggerModel.selectedCallFrame();
596 selectedCallFrame.evaluate(evaluationText, objectGroupName, false, true, false, false, showObjectPopover.bind(this));
599 * @param {?RuntimeAgent.RemoteObject} result
600 * @param {boolean=} wasThrown
601 * @this {WebInspector.JavaScriptSourceFrame}
603 function showObjectPopover(result, wasThrown)
605 var target = WebInspector.context.flavor(WebInspector.Target);
606 if (selectedCallFrame.target() != target || !debuggerModel.isPaused() || !result) {
607 this._popoverHelper.hidePopover();
608 return;
610 this._popoverAnchorBox = anchorBox;
611 showCallback(target.runtimeModel.createRemoteObject(result), wasThrown, this._popoverAnchorBox);
612 // Popover may have been removed by showCallback().
613 if (this._popoverAnchorBox) {
614 var highlightRange = new WebInspector.TextRange(lineNumber, startHighlight, lineNumber, endHighlight);
615 this._popoverAnchorBox._highlightDescriptor = this.textEditor.highlightRange(highlightRange, "source-frame-eval-expression");
620 _onHidePopover: function()
622 if (!this._popoverAnchorBox)
623 return;
624 if (this._popoverAnchorBox._highlightDescriptor)
625 this.textEditor.removeHighlight(this._popoverAnchorBox._highlightDescriptor);
626 delete this._popoverAnchorBox;
630 * @param {number} lineNumber
631 * @param {number} columnNumber
632 * @param {string} condition
633 * @param {boolean} enabled
634 * @param {boolean} mutedWhileEditing
636 _addBreakpointDecoration: function(lineNumber, columnNumber, condition, enabled, mutedWhileEditing)
638 var breakpoint = {
639 condition: condition,
640 enabled: enabled,
641 columnNumber: columnNumber
644 this.textEditor.setAttribute(lineNumber, "breakpoint", breakpoint);
646 var disabled = !enabled || mutedWhileEditing;
647 this.textEditor.addBreakpoint(lineNumber, disabled, !!condition);
650 _removeBreakpointDecoration: function(lineNumber)
652 this.textEditor.removeAttribute(lineNumber, "breakpoint");
653 this.textEditor.removeBreakpoint(lineNumber);
656 _onKeyDown: function(event)
658 if (event.keyIdentifier === "U+001B") { // Escape key
659 if (this._popoverHelper.isPopoverVisible()) {
660 this._popoverHelper.hidePopover();
661 event.consume();
667 * @param {number} lineNumber
668 * @param {!WebInspector.BreakpointManager.Breakpoint=} breakpoint
670 _editBreakpointCondition: function(lineNumber, breakpoint)
672 this._conditionElement = this._createConditionElement(lineNumber);
673 this.textEditor.addDecoration(lineNumber, this._conditionElement);
676 * @this {WebInspector.JavaScriptSourceFrame}
678 function finishEditing(committed, element, newText)
680 this.textEditor.removeDecoration(lineNumber, this._conditionElement);
681 delete this._conditionEditorElement;
682 delete this._conditionElement;
683 if (!committed)
684 return;
686 if (breakpoint)
687 breakpoint.setCondition(newText);
688 else
689 this._createNewBreakpoint(lineNumber, 0, newText, true);
692 var config = new WebInspector.InplaceEditor.Config(finishEditing.bind(this, true), finishEditing.bind(this, false));
693 WebInspector.InplaceEditor.startEditing(this._conditionEditorElement, config);
694 this._conditionEditorElement.value = breakpoint ? breakpoint.condition() : "";
695 this._conditionEditorElement.select();
698 _createConditionElement: function(lineNumber)
700 var conditionElement = createElementWithClass("div", "source-frame-breakpoint-condition");
702 var labelElement = conditionElement.createChild("label", "source-frame-breakpoint-message");
703 labelElement.htmlFor = "source-frame-breakpoint-condition";
704 labelElement.createTextChild(WebInspector.UIString("The breakpoint on line %d will stop only if this expression is true:", lineNumber + 1));
706 var editorElement = conditionElement.createChild("input", "monospace");
707 editorElement.id = "source-frame-breakpoint-condition";
708 editorElement.type = "text";
709 this._conditionEditorElement = editorElement;
711 return conditionElement;
715 * @param {!WebInspector.UILocation} uiLocation
717 setExecutionLocation: function(uiLocation)
719 this._executionLocation = uiLocation;
720 if (!this.loaded)
721 return;
723 this.textEditor.setExecutionLocation(uiLocation.lineNumber, uiLocation.columnNumber);
724 if (this.isShowing()) {
725 // We need CodeMirrorTextEditor to be initialized prior to this call. @see crbug.com/506566
726 setImmediate(this._generateValuesInSource.bind(this));
730 _generateValuesInSource: function()
732 if (!WebInspector.moduleSetting("inlineVariableValues").get())
733 return;
734 var executionContext = WebInspector.context.flavor(WebInspector.ExecutionContext);
735 if (!executionContext)
736 return;
737 var callFrame = executionContext.debuggerModel.selectedCallFrame();
738 if (!callFrame)
739 return;
741 var localScope = callFrame.localScope();
742 var functionLocation = callFrame.functionLocation();
743 if (localScope && functionLocation)
744 localScope.object().getAllProperties(false, this._prepareScopeVariables.bind(this, callFrame));
746 if (this._clearValueWidgetsTimer) {
747 clearTimeout(this._clearValueWidgetsTimer);
748 delete this._clearValueWidgetsTimer;
753 * @param {!WebInspector.DebuggerModel.CallFrame} callFrame
754 * @param {?Array.<!WebInspector.RemoteObjectProperty>} properties
755 * @param {?Array.<!WebInspector.RemoteObjectProperty>} internalProperties
757 _prepareScopeVariables: function(callFrame, properties, internalProperties)
759 if (!properties || !properties.length || properties.length > 500) {
760 this._clearValueWidgets();
761 return;
764 var functionUILocation = WebInspector.debuggerWorkspaceBinding.rawLocationToUILocation(/**@type {!WebInspector.DebuggerModel.Location} */ (callFrame.functionLocation()));
765 var executionUILocation = WebInspector.debuggerWorkspaceBinding.rawLocationToUILocation(callFrame.location());
766 if (functionUILocation.uiSourceCode !== this._uiSourceCode || executionUILocation.uiSourceCode !== this._uiSourceCode) {
767 this._clearValueWidgets();
768 return;
771 var fromLine = functionUILocation.lineNumber;
772 var fromColumn = functionUILocation.columnNumber;
773 var toLine = executionUILocation.lineNumber;
775 // Make sure we have a chance to update all existing widgets.
776 if (this._valueWidgets) {
777 for (var line of this._valueWidgets.keys())
778 toLine = Math.max(toLine, line + 1);
780 if (fromLine >= toLine || toLine - fromLine > 500) {
781 this._clearValueWidgets();
782 return;
785 var valuesMap = new Map();
786 for (var property of properties)
787 valuesMap.set(property.name, property.value);
789 /** @type {!Map.<number, !Set<string>>} */
790 var namesPerLine = new Map();
791 var tokenizer = new WebInspector.CodeMirrorUtils.TokenizerFactory().createTokenizer("text/javascript");
792 tokenizer(this.textEditor.line(fromLine).substring(fromColumn), processToken.bind(this, fromLine));
793 for (var i = fromLine + 1; i < toLine; ++i)
794 tokenizer(this.textEditor.line(i), processToken.bind(this, i));
797 * @param {number} lineNumber
798 * @param {string} tokenValue
799 * @param {?string} tokenType
800 * @param {number} column
801 * @param {number} newColumn
802 * @this {WebInspector.JavaScriptSourceFrame}
804 function processToken(lineNumber, tokenValue, tokenType, column, newColumn)
806 if (tokenType && this._isIdentifier(tokenType) && valuesMap.get(tokenValue)) {
807 var names = namesPerLine.get(lineNumber);
808 if (!names) {
809 names = new Set();
810 namesPerLine.set(lineNumber, names);
812 names.add(tokenValue);
815 this.textEditor.operation(this._renderDecorations.bind(this, valuesMap, namesPerLine, fromLine, toLine));
819 * @param {!Map.<string,!WebInspector.RemoteObject>} valuesMap
820 * @param {!Map.<number, !Set<string>>} namesPerLine
821 * @param {number} fromLine
822 * @param {number} toLine
824 _renderDecorations: function(valuesMap, namesPerLine, fromLine, toLine)
826 var formatter = new WebInspector.RemoteObjectPreviewFormatter();
827 for (var i = fromLine; i < toLine; ++i) {
828 var names = namesPerLine.get(i);
829 var oldWidget = this._valueWidgets.get(i);
830 if (!names) {
831 if (oldWidget) {
832 this._valueWidgets.delete(i);
833 this.textEditor.removeDecoration(i, oldWidget);
835 continue;
838 var widget = createElementWithClass("div", "text-editor-value-decoration");
839 var base = this.textEditor.cursorPositionToCoordinates(i, 0);
840 var offset = this.textEditor.cursorPositionToCoordinates(i, this.textEditor.line(i).length);
841 var codeMirrorLinesLeftPadding = 4;
842 var left = offset.x - base.x + codeMirrorLinesLeftPadding;
843 widget.style.left = left + "px";
844 widget.__nameToToken = new Map();
845 widget.__lineNumber = i;
847 var renderedNameCount = 0;
848 for (var name of names) {
849 if (renderedNameCount > 10)
850 break;
851 if (namesPerLine.get(i - 1) && namesPerLine.get(i - 1).has(name))
852 continue; // Only render name once in the given continuous block.
853 if (renderedNameCount)
854 widget.createTextChild(", ");
855 var nameValuePair = widget.createChild("span");
856 widget.__nameToToken.set(name, nameValuePair);
857 nameValuePair.createTextChild(name + " = ");
858 var value = valuesMap.get(name);
859 var propertyCount = value.preview ? value.preview.properties.length : 0;
860 var entryCount = value.preview && value.preview.entries ? value.preview.entries.length : 0;
861 if (value.preview && propertyCount + entryCount < 10)
862 formatter.appendObjectPreview(nameValuePair, value.preview);
863 else
864 nameValuePair.appendChild(WebInspector.ObjectPropertiesSection.createValueElement(value, false));
865 ++renderedNameCount;
868 var widgetChanged = true;
869 if (oldWidget) {
870 widgetChanged = false;
871 for (var name of widget.__nameToToken.keys()) {
872 var oldText = oldWidget.__nameToToken.get(name) ? oldWidget.__nameToToken.get(name).textContent : "";
873 var newText = widget.__nameToToken.get(name) ? widget.__nameToToken.get(name).textContent : "";
874 if (newText !== oldText) {
875 widgetChanged = true;
876 // value has changed, update it.
877 WebInspector.runCSSAnimationOnce(/** @type {!Element} */ (widget.__nameToToken.get(name)), "source-frame-value-update-highlight");
880 if (widgetChanged) {
881 this._valueWidgets.delete(i);
882 this.textEditor.removeDecoration(i, oldWidget);
885 if (widgetChanged) {
886 this._valueWidgets.set(i, widget);
887 this.textEditor.addDecoration(i, widget);
892 clearExecutionLine: function()
894 if (this.loaded && this._executionLocation)
895 this.textEditor.clearExecutionLine();
896 delete this._executionLocation;
897 this._clearValueWidgetsTimer = setTimeout(this._clearValueWidgets.bind(this), 1000);
900 _clearValueWidgets: function()
902 delete this._clearValueWidgetsTimer;
903 for (var line of this._valueWidgets.keys())
904 this.textEditor.removeDecoration(line, this._valueWidgets.get(line));
905 this._valueWidgets.clear();
909 * @return {boolean}
911 _shouldIgnoreExternalBreakpointEvents: function()
913 if (this._supportsEnabledBreakpointsWhileEditing())
914 return false;
915 if (this._muted)
916 return true;
917 var scriptFiles = this._scriptFileForTarget.valuesArray();
918 for (var i = 0; i < scriptFiles.length; ++i) {
919 if (scriptFiles[i].isDivergingFromVM() || scriptFiles[i].isMergingToVM())
920 return true;
922 return false;
925 _breakpointAdded: function(event)
927 var uiLocation = /** @type {!WebInspector.UILocation} */ (event.data.uiLocation);
928 if (uiLocation.uiSourceCode !== this._uiSourceCode)
929 return;
930 if (this._shouldIgnoreExternalBreakpointEvents())
931 return;
933 var breakpoint = /** @type {!WebInspector.BreakpointManager.Breakpoint} */ (event.data.breakpoint);
934 if (this.loaded)
935 this._addBreakpointDecoration(uiLocation.lineNumber, uiLocation.columnNumber, breakpoint.condition(), breakpoint.enabled(), false);
938 _breakpointRemoved: function(event)
940 var uiLocation = /** @type {!WebInspector.UILocation} */ (event.data.uiLocation);
941 if (uiLocation.uiSourceCode !== this._uiSourceCode)
942 return;
943 if (this._shouldIgnoreExternalBreakpointEvents())
944 return;
946 var remainingBreakpoint = this._breakpointManager.findBreakpointOnLine(this._uiSourceCode, uiLocation.lineNumber);
947 if (!remainingBreakpoint && this.loaded)
948 this._removeBreakpointDecoration(uiLocation.lineNumber);
951 _consoleMessageAdded: function(event)
953 var message = /** @type {!WebInspector.PresentationConsoleMessage} */ (event.data);
954 if (this.loaded)
955 this.addMessageToSource(this._sourceFrameMessage(message));
958 _consoleMessageRemoved: function(event)
960 var message = /** @type {!WebInspector.PresentationConsoleMessage} */ (event.data);
961 if (this.loaded)
962 this.removeMessageFromSource(this._sourceFrameMessage(message));
966 * @param {!WebInspector.PresentationConsoleMessage} message
967 * @return {!WebInspector.SourceFrameMessage}
969 _sourceFrameMessage: function(message)
971 return WebInspector.SourceFrameMessage.fromConsoleMessage(message.originalMessage, message.lineNumber(), message.columnNumber());
974 _consoleMessagesCleared: function(event)
976 this.clearMessages();
980 * @param {!WebInspector.Event} event
982 _onSourceMappingChanged: function(event)
984 var data = /** @type {{target: !WebInspector.Target}} */ (event.data);
985 this._updateScriptFile(data.target);
986 this._updateLinesWithoutMappingHighlight();
989 _updateLinesWithoutMappingHighlight: function()
991 var linesCount = this.textEditor.linesCount;
992 for (var i = 0; i < linesCount; ++i) {
993 var lineHasMapping = WebInspector.debuggerWorkspaceBinding.uiLineHasMapping(this._uiSourceCode, i);
994 if (!lineHasMapping)
995 this._hasLineWithoutMapping = true;
996 if (this._hasLineWithoutMapping)
997 this.textEditor.toggleLineClass(i, "cm-line-without-source-mapping", !lineHasMapping);
1002 * @param {!WebInspector.Target} target
1004 _updateScriptFile: function(target)
1006 var oldScriptFile = this._scriptFileForTarget.get(target);
1007 var newScriptFile = WebInspector.debuggerWorkspaceBinding.scriptFile(this._uiSourceCode, target);
1008 this._scriptFileForTarget.remove(target);
1009 if (oldScriptFile) {
1010 oldScriptFile.removeEventListener(WebInspector.ResourceScriptFile.Events.DidMergeToVM, this._didMergeToVM, this);
1011 oldScriptFile.removeEventListener(WebInspector.ResourceScriptFile.Events.DidDivergeFromVM, this._didDivergeFromVM, this);
1012 if (this._muted && !this._uiSourceCode.isDirty())
1013 this._restoreBreakpointsIfConsistentScripts();
1015 if (newScriptFile)
1016 this._scriptFileForTarget.set(target, newScriptFile);
1018 delete this._hasCommittedLiveEdit;
1019 this._updateDivergedInfobar();
1021 if (newScriptFile) {
1022 newScriptFile.addEventListener(WebInspector.ResourceScriptFile.Events.DidMergeToVM, this._didMergeToVM, this);
1023 newScriptFile.addEventListener(WebInspector.ResourceScriptFile.Events.DidDivergeFromVM, this._didDivergeFromVM, this);
1024 if (this.loaded)
1025 newScriptFile.checkMapping();
1029 onTextEditorContentLoaded: function()
1031 if (this._executionLocation)
1032 this.setExecutionLocation(this._executionLocation);
1034 var breakpointLocations = this._breakpointManager.breakpointLocationsForUISourceCode(this._uiSourceCode);
1035 for (var i = 0; i < breakpointLocations.length; ++i)
1036 this._breakpointAdded({data:breakpointLocations[i]});
1038 var messages = WebInspector.presentationConsoleMessageHelper.consoleMessages(this._uiSourceCode);
1039 for (var message of messages)
1040 this.addMessageToSource(this._sourceFrameMessage(message));
1042 var scriptFiles = this._scriptFileForTarget.valuesArray();
1043 for (var i = 0; i < scriptFiles.length; ++i)
1044 scriptFiles[i].checkMapping();
1046 this._updateLinesWithoutMappingHighlight();
1050 * @param {!WebInspector.Event} event
1052 _handleGutterClick: function(event)
1054 if (this._muted)
1055 return;
1057 var eventData = /** @type {!WebInspector.CodeMirrorTextEditor.GutterClickEventData} */ (event.data);
1058 var lineNumber = eventData.lineNumber;
1059 var eventObject = eventData.event;
1061 if (eventObject.button != 0 || eventObject.altKey || eventObject.ctrlKey || eventObject.metaKey)
1062 return;
1064 this._toggleBreakpoint(lineNumber, eventObject.shiftKey);
1065 eventObject.consume(true);
1069 * @param {number} lineNumber
1070 * @param {boolean} onlyDisable
1072 _toggleBreakpoint: function(lineNumber, onlyDisable)
1074 var breakpoint = this._breakpointManager.findBreakpointOnLine(this._uiSourceCode, lineNumber);
1075 if (breakpoint) {
1076 if (onlyDisable)
1077 breakpoint.setEnabled(!breakpoint.enabled());
1078 else
1079 breakpoint.remove();
1080 } else
1081 this._createNewBreakpoint(lineNumber, 0, "", true);
1085 * @param {number} lineNumber
1086 * @param {number} columnNumber
1087 * @param {string} condition
1088 * @param {boolean} enabled
1090 _createNewBreakpoint: function(lineNumber, columnNumber, condition, enabled)
1092 this._setBreakpoint(lineNumber, columnNumber, condition, enabled);
1093 WebInspector.userMetrics.ScriptsBreakpointSet.record();
1096 toggleBreakpointOnCurrentLine: function()
1098 if (this._muted)
1099 return;
1101 var selection = this.textEditor.selection();
1102 if (!selection)
1103 return;
1104 this._toggleBreakpoint(selection.startLine, false);
1108 * @param {number} lineNumber
1109 * @param {number} columnNumber
1110 * @param {string} condition
1111 * @param {boolean} enabled
1113 _setBreakpoint: function(lineNumber, columnNumber, condition, enabled)
1115 this._breakpointManager.setBreakpoint(this._uiSourceCode, lineNumber, columnNumber, condition, enabled);
1118 dispose: function()
1120 this._breakpointManager.removeEventListener(WebInspector.BreakpointManager.Events.BreakpointAdded, this._breakpointAdded, this);
1121 this._breakpointManager.removeEventListener(WebInspector.BreakpointManager.Events.BreakpointRemoved, this._breakpointRemoved, this);
1122 WebInspector.presentationConsoleMessageHelper.removeConsoleMessageEventListener(WebInspector.PresentationConsoleMessageHelper.Events.ConsoleMessageAdded, this._uiSourceCode, this._consoleMessageAdded, this);
1123 WebInspector.presentationConsoleMessageHelper.removeConsoleMessageEventListener(WebInspector.PresentationConsoleMessageHelper.Events.ConsoleMessageRemoved, this._uiSourceCode, this._consoleMessageRemoved, this);
1124 WebInspector.presentationConsoleMessageHelper.removeConsoleMessageEventListener(WebInspector.PresentationConsoleMessageHelper.Events.ConsoleMessagesCleared, this._uiSourceCode, this._consoleMessagesCleared, this);
1125 this._uiSourceCode.removeEventListener(WebInspector.UISourceCode.Events.SourceMappingChanged, this._onSourceMappingChanged, this);
1126 this._uiSourceCode.removeEventListener(WebInspector.UISourceCode.Events.WorkingCopyChanged, this._workingCopyChanged, this);
1127 this._uiSourceCode.removeEventListener(WebInspector.UISourceCode.Events.WorkingCopyCommitted, this._workingCopyCommitted, this);
1128 this._uiSourceCode.removeEventListener(WebInspector.UISourceCode.Events.TitleChanged, this._showBlackboxInfobarIfNeeded, this);
1129 WebInspector.moduleSetting("skipStackFramesPattern").removeChangeListener(this._showBlackboxInfobarIfNeeded, this);
1130 WebInspector.moduleSetting("skipContentScripts").removeChangeListener(this._showBlackboxInfobarIfNeeded, this);
1131 WebInspector.UISourceCodeFrame.prototype.dispose.call(this);
1134 __proto__: WebInspector.UISourceCodeFrame.prototype