summaryrefslogtreecommitdiff
path: root/Source/WebInspectorUI/UserInterface/Views/SourceCodeTextEditor.js
diff options
context:
space:
mode:
authorLorry Tar Creator <lorry-tar-importer@lorry>2017-06-27 06:07:23 +0000
committerLorry Tar Creator <lorry-tar-importer@lorry>2017-06-27 06:07:23 +0000
commit1bf1084f2b10c3b47fd1a588d85d21ed0eb41d0c (patch)
tree46dcd36c86e7fbc6e5df36deb463b33e9967a6f7 /Source/WebInspectorUI/UserInterface/Views/SourceCodeTextEditor.js
parent32761a6cee1d0dee366b885b7b9c777e67885688 (diff)
downloadWebKitGtk-tarball-master.tar.gz
Diffstat (limited to 'Source/WebInspectorUI/UserInterface/Views/SourceCodeTextEditor.js')
-rw-r--r--Source/WebInspectorUI/UserInterface/Views/SourceCodeTextEditor.js2251
1 files changed, 2251 insertions, 0 deletions
diff --git a/Source/WebInspectorUI/UserInterface/Views/SourceCodeTextEditor.js b/Source/WebInspectorUI/UserInterface/Views/SourceCodeTextEditor.js
new file mode 100644
index 000000000..215bfbc5e
--- /dev/null
+++ b/Source/WebInspectorUI/UserInterface/Views/SourceCodeTextEditor.js
@@ -0,0 +1,2251 @@
+/*
+ * Copyright (C) 2013, 2015 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+WebInspector.SourceCodeTextEditor = class SourceCodeTextEditor extends WebInspector.TextEditor
+{
+ constructor(sourceCode)
+ {
+ console.assert(sourceCode instanceof WebInspector.SourceCode);
+
+ super();
+
+ this.delegate = this;
+
+ this._sourceCode = sourceCode;
+ this._breakpointMap = {};
+ this._issuesLineNumberMap = new Map;
+ this._widgetMap = new Map;
+ this._contentPopulated = false;
+ this._invalidLineNumbers = {0: true};
+ this._requestingScriptContent = false;
+ this._activeCallFrameSourceCodeLocation = null;
+
+ this._threadLineNumberMap = new Map; // line -> [targets]
+ this._threadWidgetMap = new Map; // line -> widget
+ this._threadTargetMap = new Map; // target -> line
+
+ this._typeTokenScrollHandler = null;
+ this._typeTokenAnnotator = null;
+ this._basicBlockAnnotator = null;
+ this._editingController = null;
+
+ this._autoFormat = false;
+ this._isProbablyMinified = false;
+
+ this._ignoreContentDidChange = 0;
+ this._ignoreLocationUpdateBreakpoint = null;
+ this._ignoreBreakpointAddedBreakpoint = null;
+ this._ignoreBreakpointRemovedBreakpoint = null;
+ this._ignoreAllBreakpointLocationUpdates = false;
+
+ // FIXME: Currently this just jumps between resources and related source map resources. It doesn't "jump to symbol" yet.
+ this._updateTokenTrackingControllerState();
+
+ this.element.classList.add("source-code");
+
+ if (this._supportsDebugging) {
+ WebInspector.Breakpoint.addEventListener(WebInspector.Breakpoint.Event.DisabledStateDidChange, this._breakpointStatusDidChange, this);
+ WebInspector.Breakpoint.addEventListener(WebInspector.Breakpoint.Event.AutoContinueDidChange, this._breakpointStatusDidChange, this);
+ WebInspector.Breakpoint.addEventListener(WebInspector.Breakpoint.Event.ResolvedStateDidChange, this._breakpointStatusDidChange, this);
+ WebInspector.Breakpoint.addEventListener(WebInspector.Breakpoint.Event.LocationDidChange, this._updateBreakpointLocation, this);
+
+ WebInspector.targetManager.addEventListener(WebInspector.TargetManager.Event.TargetAdded, this._targetAdded, this);
+ WebInspector.targetManager.addEventListener(WebInspector.TargetManager.Event.TargetRemoved, this._targetRemoved, this);
+
+ WebInspector.debuggerManager.addEventListener(WebInspector.DebuggerManager.Event.BreakpointsEnabledDidChange, this._breakpointsEnabledDidChange, this);
+ WebInspector.debuggerManager.addEventListener(WebInspector.DebuggerManager.Event.BreakpointAdded, this._breakpointAdded, this);
+ WebInspector.debuggerManager.addEventListener(WebInspector.DebuggerManager.Event.BreakpointRemoved, this._breakpointRemoved, this);
+ WebInspector.debuggerManager.addEventListener(WebInspector.DebuggerManager.Event.CallFramesDidChange, this._callFramesDidChange, this);
+ WebInspector.debuggerManager.addEventListener(WebInspector.DebuggerManager.Event.ActiveCallFrameDidChange, this._activeCallFrameDidChange, this);
+
+ WebInspector.debuggerManager.addEventListener(WebInspector.DebuggerManager.Event.Paused, this._debuggerDidPause, this);
+ WebInspector.debuggerManager.addEventListener(WebInspector.DebuggerManager.Event.Resumed, this._debuggerDidResume, this);
+ if (WebInspector.debuggerManager.activeCallFrame)
+ this._debuggerDidPause();
+
+ this._activeCallFrameDidChange();
+ }
+
+ WebInspector.issueManager.addEventListener(WebInspector.IssueManager.Event.IssueWasAdded, this._issueWasAdded, this);
+
+ if (this._sourceCode instanceof WebInspector.SourceMapResource || this._sourceCode.sourceMaps.length > 0)
+ WebInspector.notifications.addEventListener(WebInspector.Notification.GlobalModifierKeysDidChange, this._updateTokenTrackingControllerState, this);
+ else
+ this._sourceCode.addEventListener(WebInspector.SourceCode.Event.SourceMapAdded, this._sourceCodeSourceMapAdded, this);
+
+ sourceCode.requestContent().then(this._contentAvailable.bind(this));
+
+ new WebInspector.KeyboardShortcut(WebInspector.KeyboardShortcut.Modifier.Control, "G", this.showGoToLineDialog.bind(this), this.element);
+
+ WebInspector.logManager.addEventListener(WebInspector.LogManager.Event.Cleared, this._logCleared, this);
+ }
+
+ // Public
+
+ get sourceCode()
+ {
+ return this._sourceCode;
+ }
+
+ get target()
+ {
+ if (this._sourceCode instanceof WebInspector.SourceMapResource) {
+ if (this._sourceCode.sourceMap.originalSourceCode instanceof WebInspector.Script)
+ return this._sourceCode.sourceMap.originalSourceCode.target;
+ }
+
+ if (this._sourceCode instanceof WebInspector.Script)
+ return this._sourceCode.target;
+
+ return WebInspector.mainTarget;
+ }
+
+ shown()
+ {
+ super.shown();
+
+ if (WebInspector.showJavaScriptTypeInformationSetting.value) {
+ if (this._typeTokenAnnotator)
+ this._typeTokenAnnotator.resume();
+ if (!this._typeTokenScrollHandler && this._typeTokenAnnotator)
+ this._enableScrollEventsForTypeTokenAnnotator();
+ } else {
+ if (this._typeTokenAnnotator)
+ this._setTypeTokenAnnotatorEnabledState(false);
+ }
+
+ if (WebInspector.enableControlFlowProfilerSetting.value) {
+ if (this._basicBlockAnnotator)
+ this._basicBlockAnnotator.resume();
+
+ if (!this._controlFlowScrollHandler && this._basicBlockAnnotator)
+ this._enableScrollEventsForControlFlowAnnotator();
+ } else {
+ this._basicBlockAnnotatorEnabled = false;
+ }
+ }
+
+ hidden()
+ {
+ super.hidden();
+
+ this.tokenTrackingController.removeHighlightedRange();
+
+ this._dismissPopover();
+
+ this._dismissEditingController(true);
+
+ if (this._typeTokenAnnotator)
+ this._typeTokenAnnotator.pause();
+ if (this._basicBlockAnnotator)
+ this._basicBlockAnnotator.pause();
+ }
+
+ close()
+ {
+ super.close();
+
+ if (this._supportsDebugging) {
+ WebInspector.Breakpoint.removeEventListener(null, null, this);
+ WebInspector.debuggerManager.removeEventListener(null, null, this);
+ WebInspector.targetManager.removeEventListener(null, null, this);
+
+ if (this._activeCallFrameSourceCodeLocation) {
+ this._activeCallFrameSourceCodeLocation.removeEventListener(WebInspector.SourceCodeLocation.Event.LocationChanged, this._activeCallFrameSourceCodeLocationChanged, this);
+ this._activeCallFrameSourceCodeLocation = null;
+ }
+ }
+
+ WebInspector.issueManager.removeEventListener(WebInspector.IssueManager.Event.IssueWasAdded, this._issueWasAdded, this);
+
+ if (this._sourceCode instanceof WebInspector.SourceMapResource || this._sourceCode.sourceMaps.length > 0)
+ WebInspector.notifications.removeEventListener(WebInspector.Notification.GlobalModifierKeysDidChange, this._updateTokenTrackingControllerState, this);
+ else
+ this._sourceCode.removeEventListener(WebInspector.SourceCode.Event.SourceMapAdded, this._sourceCodeSourceMapAdded, this);
+ }
+
+ canBeFormatted()
+ {
+ // Currently we assume that source map resources are formatted how the author wants it.
+ // We could allow source map resources to be formatted, we would then need to make
+ // SourceCodeLocation watch listen for mappedResource's formatting changes, and keep
+ // a formatted location alongside the regular mapped location.
+ if (this._sourceCode instanceof WebInspector.SourceMapResource)
+ return false;
+
+ return super.canBeFormatted();
+ }
+
+ canShowTypeAnnotations()
+ {
+ // Type annotations for modified scripts are currently unsupported.
+ return !!this._getAssociatedScript() && !this.hasModified;
+ }
+
+ canShowCoverageHints()
+ {
+ // Code coverage hints for modified scripts are currently unsupported.
+ return !!this._getAssociatedScript() && !this.hasModified;
+ }
+
+ customPerformSearch(query)
+ {
+ function searchResultCallback(error, matches)
+ {
+ // Bail if the query changed since we started.
+ if (this.currentSearchQuery !== query)
+ return;
+
+ if (error || !matches || !matches.length) {
+ // Report zero matches.
+ this.dispatchEventToListeners(WebInspector.TextEditor.Event.NumberOfSearchResultsDidChange);
+ return;
+ }
+
+ var queryRegex = new RegExp(query.escapeForRegExp(), "gi");
+ var searchResults = [];
+
+ for (var i = 0; i < matches.length; ++i) {
+ var matchLineNumber = matches[i].lineNumber;
+ var line = this.line(matchLineNumber);
+ if (!line)
+ return;
+
+ // Reset the last index to reuse the regex on a new line.
+ queryRegex.lastIndex = 0;
+
+ // Search the line and mark the ranges.
+ var lineMatch = null;
+ while (queryRegex.lastIndex + query.length <= line.length && (lineMatch = queryRegex.exec(line))) {
+ var resultTextRange = new WebInspector.TextRange(matchLineNumber, lineMatch.index, matchLineNumber, queryRegex.lastIndex);
+ searchResults.push(resultTextRange);
+ }
+ }
+
+ this.addSearchResults(searchResults);
+
+ this.dispatchEventToListeners(WebInspector.TextEditor.Event.NumberOfSearchResultsDidChange);
+ }
+
+ if (this.hasEdits())
+ return false;
+
+ if (this._sourceCode instanceof WebInspector.SourceMapResource)
+ return false;
+
+ if (this._sourceCode instanceof WebInspector.Resource)
+ PageAgent.searchInResource(this._sourceCode.parentFrame.id, this._sourceCode.url, query, false, false, searchResultCallback.bind(this));
+ else if (this._sourceCode instanceof WebInspector.Script)
+ this._sourceCode.target.DebuggerAgent.searchInContent(this._sourceCode.id, query, false, false, searchResultCallback.bind(this));
+ return true;
+ }
+
+ showGoToLineDialog()
+ {
+ if (!this._goToLineDialog)
+ this._goToLineDialog = new WebInspector.GoToLineDialog(this);
+
+ this._goToLineDialog.present(this.element);
+ }
+
+ isDialogRepresentedObjectValid(goToLineDialog, lineNumber)
+ {
+ return !isNaN(lineNumber) && lineNumber > 0 && lineNumber <= this.lineCount;
+ }
+
+ dialogWasDismissed(goToLineDialog)
+ {
+ let lineNumber = goToLineDialog.representedObject;
+ let position = new WebInspector.SourceCodePosition(lineNumber - 1, 0);
+ let range = new WebInspector.TextRange(lineNumber - 1, 0, lineNumber, 0);
+
+ this.revealPosition(position, range, false, true);
+ this.focus();
+ }
+
+ contentDidChange(replacedRanges, newRanges)
+ {
+ super.contentDidChange(replacedRanges, newRanges);
+
+ if (this._ignoreContentDidChange > 0)
+ return;
+
+ for (var range of newRanges)
+ this._updateEditableMarkers(range);
+
+ if (this._basicBlockAnnotator) {
+ this._basicBlockAnnotatorEnabled = false;
+ this._basicBlockAnnotator = null;
+ }
+
+ if (this._typeTokenAnnotator) {
+ this._setTypeTokenAnnotatorEnabledState(false);
+ this._typeTokenAnnotator = null;
+ }
+ }
+
+ toggleTypeAnnotations()
+ {
+ if (!this._typeTokenAnnotator)
+ return false;
+
+ var newActivatedState = !this._typeTokenAnnotator.isActive();
+ if (newActivatedState && this._isProbablyMinified && !this.formatted) {
+ return this.updateFormattedState(true).then(() => {
+ this._setTypeTokenAnnotatorEnabledState(newActivatedState);
+ });
+ }
+
+ this._setTypeTokenAnnotatorEnabledState(newActivatedState);
+ return Promise.resolve();
+ }
+
+ toggleUnexecutedCodeHighlights()
+ {
+ if (!this._basicBlockAnnotator)
+ return false;
+
+ let newActivatedState = !this._basicBlockAnnotator.isActive();
+ if (newActivatedState && this._isProbablyMinified && !this.formatted) {
+ return this.updateFormattedState(true).then(() => {
+ this._basicBlockAnnotatorEnabled = newActivatedState;
+ });
+ }
+
+ this._basicBlockAnnotatorEnabled = newActivatedState;
+ return Promise.resolve();
+ }
+
+ showPopoverForTypes(typeDescription, bounds, title)
+ {
+ var content = document.createElement("div");
+ content.className = "object expandable";
+
+ var titleElement = document.createElement("div");
+ titleElement.className = "title";
+ titleElement.textContent = title;
+ content.appendChild(titleElement);
+
+ var bodyElement = content.appendChild(document.createElement("div"));
+ bodyElement.className = "body";
+
+ var typeTreeView = new WebInspector.TypeTreeView(typeDescription);
+ bodyElement.appendChild(typeTreeView.element);
+
+ this._showPopover(content, bounds);
+ }
+
+ // Protected
+
+ prettyPrint(pretty)
+ {
+ // The annotators must be cleared before pretty printing takes place and resumed
+ // after so that they clear their annotations in a known state and insert new annotations
+ // in the new state.
+
+ var shouldResumeBasicBlockAnnotator = this._basicBlockAnnotator && this._basicBlockAnnotator.isActive();
+ if (shouldResumeBasicBlockAnnotator)
+ this._basicBlockAnnotatorEnabled = false;
+
+ let shouldResumeTypeTokenAnnotator = this._typeTokenAnnotator && this._typeTokenAnnotator.isActive();
+ if (shouldResumeTypeTokenAnnotator)
+ this._setTypeTokenAnnotatorEnabledState(false);
+
+ return super.prettyPrint(pretty).then(() => {
+ if (pretty || !this._isProbablyMinified) {
+ if (shouldResumeBasicBlockAnnotator)
+ this._basicBlockAnnotatorEnabled = true;
+
+ if (shouldResumeTypeTokenAnnotator)
+ this._setTypeTokenAnnotatorEnabledState(true);
+ } else {
+ console.assert(!pretty && this._isProbablyMinified);
+ if (this._basicBlockAnnotator)
+ this._basicBlockAnnotatorEnabled = false;
+
+ this._setTypeTokenAnnotatorEnabledState(false);
+ }
+ });
+ }
+
+ // Private
+
+ _unformattedLineInfoForEditorLineInfo(lineInfo)
+ {
+ if (this.formatterSourceMap)
+ return this.formatterSourceMap.formattedToOriginal(lineInfo.lineNumber, lineInfo.columnNumber);
+ return lineInfo;
+ }
+
+ _sourceCodeLocationForEditorPosition(position)
+ {
+ var lineInfo = {lineNumber: position.line, columnNumber: position.ch};
+ var unformattedLineInfo = this._unformattedLineInfoForEditorLineInfo(lineInfo);
+ return this.sourceCode.createSourceCodeLocation(unformattedLineInfo.lineNumber, unformattedLineInfo.columnNumber);
+ }
+
+ _editorLineInfoForSourceCodeLocation(sourceCodeLocation)
+ {
+ if (this._sourceCode instanceof WebInspector.SourceMapResource)
+ return {lineNumber: sourceCodeLocation.displayLineNumber, columnNumber: sourceCodeLocation.displayColumnNumber};
+ return {lineNumber: sourceCodeLocation.formattedLineNumber, columnNumber: sourceCodeLocation.formattedColumnNumber};
+ }
+
+ _breakpointForEditorLineInfo(lineInfo)
+ {
+ if (!this._breakpointMap[lineInfo.lineNumber])
+ return null;
+ return this._breakpointMap[lineInfo.lineNumber][lineInfo.columnNumber];
+ }
+
+ _addBreakpointWithEditorLineInfo(breakpoint, lineInfo)
+ {
+ if (!this._breakpointMap[lineInfo.lineNumber])
+ this._breakpointMap[lineInfo.lineNumber] = {};
+
+ this._breakpointMap[lineInfo.lineNumber][lineInfo.columnNumber] = breakpoint;
+ }
+
+ _removeBreakpointWithEditorLineInfo(breakpoint, lineInfo)
+ {
+ console.assert(breakpoint === this._breakpointMap[lineInfo.lineNumber][lineInfo.columnNumber]);
+
+ delete this._breakpointMap[lineInfo.lineNumber][lineInfo.columnNumber];
+
+ if (isEmptyObject(this._breakpointMap[lineInfo.lineNumber]))
+ delete this._breakpointMap[lineInfo.lineNumber];
+ }
+
+ _populateWithContent(content)
+ {
+ content = content || "";
+
+ this._prepareEditorForInitialContent(content);
+
+ // If we can auto format, format the TextEditor before showing it.
+ if (this._autoFormat) {
+ console.assert(!this.formatted);
+ this._autoFormat = false;
+ this.deferReveal = true;
+ this.string = content;
+ this.deferReveal = false;
+ this.updateFormattedState(true).then(() => {
+ this._proceedPopulateWithContent(this.string);
+ });
+ return;
+ }
+
+ this._proceedPopulateWithContent(content);
+ }
+
+ _proceedPopulateWithContent(content)
+ {
+ this.dispatchEventToListeners(WebInspector.SourceCodeTextEditor.Event.ContentWillPopulate);
+
+ this.string = content;
+
+ this._createBasicBlockAnnotator();
+ if (WebInspector.enableControlFlowProfilerSetting.value && this._basicBlockAnnotator)
+ this._basicBlockAnnotatorEnabled = true;
+
+ this._createTypeTokenAnnotator();
+ if (WebInspector.showJavaScriptTypeInformationSetting.value)
+ this._setTypeTokenAnnotatorEnabledState(true);
+
+ this._contentDidPopulate();
+ }
+
+ _contentDidPopulate()
+ {
+ this._contentPopulated = true;
+
+ this.dispatchEventToListeners(WebInspector.SourceCodeTextEditor.Event.ContentDidPopulate);
+
+ // We add the issues each time content is populated. This is needed because lines might not exist
+ // if we tried added them before when the full content wasn't available. (When populating with
+ // partial script content this can be called multiple times.)
+
+ this._reinsertAllIssues();
+ this._reinsertAllThreadIndicators();
+
+ this._updateEditableMarkers();
+ }
+
+ _prepareEditorForInitialContent(content)
+ {
+ // Only do this work before the first populate.
+ if (this._contentPopulated)
+ return;
+
+ if (this._supportsDebugging) {
+ this._breakpointMap = {};
+
+ var breakpoints = WebInspector.debuggerManager.breakpointsForSourceCode(this._sourceCode);
+ for (var i = 0; i < breakpoints.length; ++i) {
+ var breakpoint = breakpoints[i];
+ console.assert(this._matchesBreakpoint(breakpoint));
+ var lineInfo = this._editorLineInfoForSourceCodeLocation(breakpoint.sourceCodeLocation);
+ this._addBreakpointWithEditorLineInfo(breakpoint, lineInfo);
+ this.setBreakpointInfoForLineAndColumn(lineInfo.lineNumber, lineInfo.columnNumber, this._breakpointInfoForBreakpoint(breakpoint));
+ }
+ }
+
+ if (this._sourceCode instanceof WebInspector.Resource)
+ this.mimeType = this._sourceCode.syntheticMIMEType;
+ else if (this._sourceCode instanceof WebInspector.Script)
+ this.mimeType = "text/javascript";
+
+ // Decide to automatically format the content if it looks minified and it can be formatted.
+ console.assert(!this.formatted);
+ if (this.canBeFormatted() && isTextLikelyMinified(content)) {
+ this._autoFormat = true;
+ this._isProbablyMinified = true;
+ }
+ }
+
+ _contentAvailable(parameters)
+ {
+ // Return if resource is not available.
+ if (parameters.error)
+ return;
+
+ var sourceCode = parameters.sourceCode;
+ var content = sourceCode.content;
+ var base64Encoded = parameters.base64Encoded;
+
+ console.assert(sourceCode === this._sourceCode);
+ console.assert(!base64Encoded);
+
+ // Abort if the full content populated while waiting for this async callback.
+ if (this._fullContentPopulated)
+ return;
+
+ this._fullContentPopulated = true;
+ this._invalidLineNumbers = {};
+
+ this._populateWithContent(content);
+ }
+
+ _breakpointStatusDidChange(event)
+ {
+ this._updateBreakpointStatus(event.target);
+ }
+
+ _breakpointsEnabledDidChange()
+ {
+ console.assert(this._supportsDebugging);
+
+ var breakpoints = WebInspector.debuggerManager.breakpointsForSourceCode(this._sourceCode);
+ for (var breakpoint of breakpoints)
+ this._updateBreakpointStatus(breakpoint);
+ }
+
+ _updateBreakpointStatus(breakpoint)
+ {
+ console.assert(this._supportsDebugging);
+
+ if (!this._contentPopulated)
+ return;
+
+ if (!this._matchesBreakpoint(breakpoint))
+ return;
+
+ var lineInfo = this._editorLineInfoForSourceCodeLocation(breakpoint.sourceCodeLocation);
+ this.setBreakpointInfoForLineAndColumn(lineInfo.lineNumber, lineInfo.columnNumber, this._breakpointInfoForBreakpoint(breakpoint));
+ }
+
+ _updateBreakpointLocation(event)
+ {
+ console.assert(this._supportsDebugging);
+
+ if (!this._contentPopulated)
+ return;
+
+ var breakpoint = event.target;
+ if (!this._matchesBreakpoint(breakpoint))
+ return;
+
+ if (this._ignoreAllBreakpointLocationUpdates)
+ return;
+
+ if (breakpoint === this._ignoreLocationUpdateBreakpoint)
+ return;
+
+ var sourceCodeLocation = breakpoint.sourceCodeLocation;
+
+ if (this._sourceCode instanceof WebInspector.SourceMapResource) {
+ // Update our breakpoint location if the display location changed.
+ if (sourceCodeLocation.displaySourceCode !== this._sourceCode)
+ return;
+ var oldLineInfo = {lineNumber: event.data.oldDisplayLineNumber, columnNumber: event.data.oldDisplayColumnNumber};
+ var newLineInfo = {lineNumber: sourceCodeLocation.displayLineNumber, columnNumber: sourceCodeLocation.displayColumnNumber};
+ } else {
+ // Update our breakpoint location if the original location changed.
+ if (sourceCodeLocation.sourceCode !== this._sourceCode)
+ return;
+ var oldLineInfo = {lineNumber: event.data.oldFormattedLineNumber, columnNumber: event.data.oldFormattedColumnNumber};
+ var newLineInfo = {lineNumber: sourceCodeLocation.formattedLineNumber, columnNumber: sourceCodeLocation.formattedColumnNumber};
+ }
+
+ var existingBreakpoint = this._breakpointForEditorLineInfo(oldLineInfo);
+ if (!existingBreakpoint)
+ return;
+
+ console.assert(breakpoint === existingBreakpoint);
+
+ this.setBreakpointInfoForLineAndColumn(oldLineInfo.lineNumber, oldLineInfo.columnNumber, null);
+ this.setBreakpointInfoForLineAndColumn(newLineInfo.lineNumber, newLineInfo.columnNumber, this._breakpointInfoForBreakpoint(breakpoint));
+
+ this._removeBreakpointWithEditorLineInfo(breakpoint, oldLineInfo);
+ this._addBreakpointWithEditorLineInfo(breakpoint, newLineInfo);
+ }
+
+ _breakpointAdded(event)
+ {
+ console.assert(this._supportsDebugging);
+
+ if (!this._contentPopulated)
+ return;
+
+ var breakpoint = event.data.breakpoint;
+ if (!this._matchesBreakpoint(breakpoint))
+ return;
+
+ if (breakpoint === this._ignoreBreakpointAddedBreakpoint)
+ return;
+
+ var lineInfo = this._editorLineInfoForSourceCodeLocation(breakpoint.sourceCodeLocation);
+ this._addBreakpointWithEditorLineInfo(breakpoint, lineInfo);
+ this.setBreakpointInfoForLineAndColumn(lineInfo.lineNumber, lineInfo.columnNumber, this._breakpointInfoForBreakpoint(breakpoint));
+ }
+
+ _breakpointRemoved(event)
+ {
+ console.assert(this._supportsDebugging);
+
+ if (!this._contentPopulated)
+ return;
+
+ var breakpoint = event.data.breakpoint;
+ if (!this._matchesBreakpoint(breakpoint))
+ return;
+
+ if (breakpoint === this._ignoreBreakpointRemovedBreakpoint)
+ return;
+
+ var lineInfo = this._editorLineInfoForSourceCodeLocation(breakpoint.sourceCodeLocation);
+ this._removeBreakpointWithEditorLineInfo(breakpoint, lineInfo);
+ this.setBreakpointInfoForLineAndColumn(lineInfo.lineNumber, lineInfo.columnNumber, null);
+ }
+
+ _targetAdded(event)
+ {
+ if (WebInspector.targets.size === 2)
+ this._reinsertAllThreadIndicators();
+ }
+
+ _targetRemoved(event)
+ {
+ if (WebInspector.targets.size === 1) {
+ // Back to one thread, remove thread indicators.
+ this._reinsertAllThreadIndicators();
+ return;
+ }
+
+ let target = event.data.target;
+ this._removeThreadIndicatorForTarget(target);
+ }
+
+ _callFramesDidChange(event)
+ {
+ if (WebInspector.targets.size === 1)
+ return;
+
+ let target = event.data.target;
+ this._removeThreadIndicatorForTarget(target);
+ this._addThreadIndicatorForTarget(target);
+ }
+
+ _addThreadIndicatorForTarget(target)
+ {
+ let targetData = WebInspector.debuggerManager.dataForTarget(target);
+ let topCallFrame = targetData.callFrames[0];
+ if (!topCallFrame)
+ return;
+
+ let sourceCodeLocation = topCallFrame.sourceCodeLocation;
+ console.assert(sourceCodeLocation, "Expected source code location to place thread indicator.");
+ if (!sourceCodeLocation)
+ return;
+
+ if (!this._looselyMatchesSourceCodeLocation(sourceCodeLocation))
+ return;
+
+ let lineNumberWithIndicator = sourceCodeLocation.formattedLineNumber;
+ this._threadTargetMap.set(target, lineNumberWithIndicator);
+
+ let threads = this._threadLineNumberMap.get(lineNumberWithIndicator);
+ if (!threads) {
+ threads = [];
+ this._threadLineNumberMap.set(lineNumberWithIndicator, threads);
+ }
+ threads.push(target);
+
+ let widget = this._threadIndicatorWidgetForLine(target, lineNumberWithIndicator);
+ this._updateThreadIndicatorWidget(widget, threads);
+
+ this.addStyleClassToLine(lineNumberWithIndicator, "thread-indicator");
+ }
+
+ _removeThreadIndicatorForTarget(target)
+ {
+ let lineNumberWithIndicator = this._threadTargetMap.take(target);
+ if (lineNumberWithIndicator === undefined)
+ return;
+
+ let threads = this._threadLineNumberMap.get(lineNumberWithIndicator);
+ threads.remove(target);
+ if (threads.length) {
+ let widget = this._threadWidgetMap.get(lineNumberWithIndicator);
+ this._updateThreadIndicatorWidget(widget, threads);
+ return;
+ }
+
+ this._threadLineNumberMap.delete(lineNumberWithIndicator);
+
+ let widget = this._threadWidgetMap.take(lineNumberWithIndicator);
+ if (widget)
+ widget.clear();
+
+ this.removeStyleClassFromLine(lineNumberWithIndicator, "thread-indicator");
+ }
+
+ _threadIndicatorWidgetForLine(target, lineNumber)
+ {
+ let widget = this._threadWidgetMap.get(lineNumber);
+ if (widget)
+ return widget;
+
+ widget = this.createWidgetForLine(lineNumber);
+ if (!widget)
+ return null;
+
+ let widgetElement = widget.widgetElement;
+ widgetElement.classList.add("line-indicator-widget", "thread-widget", "inline");
+ widgetElement.addEventListener("click", this._handleThreadIndicatorWidgetClick.bind(this, widget, lineNumber));
+
+ this._threadWidgetMap.set(lineNumber, widget);
+
+ return widget;
+ }
+
+ _updateThreadIndicatorWidget(widget, threads)
+ {
+ if (!widget)
+ return;
+
+ console.assert(WebInspector.targets.size > 1);
+
+ let widgetElement = widget.widgetElement;
+ widgetElement.removeChildren();
+
+ widget[WebInspector.SourceCodeTextEditor.WidgetContainsMultipleThreadsSymbol] = threads.length > 1;
+
+ if (widgetElement.classList.contains("inline") || threads.length === 1) {
+ let arrowElement = widgetElement.appendChild(document.createElement("span"));
+ arrowElement.className = "arrow";
+
+ let textElement = widgetElement.appendChild(document.createElement("span"));
+ textElement.className = "text";
+ textElement.textContent = threads.length === 1 ? threads[0].displayName : WebInspector.UIString("%d Threads").format(threads.length);
+ } else {
+ for (let target of threads) {
+ let textElement = widgetElement.appendChild(document.createElement("span"));
+ textElement.className = "text";
+ textElement.textContent = target.displayName;
+
+ widgetElement.appendChild(document.createElement("br"));
+ }
+ }
+
+ widget.update();
+ }
+
+ _handleThreadIndicatorWidgetClick(widget, lineNumber, event)
+ {
+ if (!this._isWidgetToggleable(widget))
+ return;
+
+ widget.widgetElement.classList.toggle("inline");
+
+ let threads = this._threadLineNumberMap.get(lineNumber);
+ this._updateThreadIndicatorWidget(widget, threads);
+ }
+
+ _activeCallFrameDidChange()
+ {
+ console.assert(this._supportsDebugging);
+
+ if (this._activeCallFrameSourceCodeLocation) {
+ this._activeCallFrameSourceCodeLocation.removeEventListener(WebInspector.SourceCodeLocation.Event.LocationChanged, this._activeCallFrameSourceCodeLocationChanged, this);
+ this._activeCallFrameSourceCodeLocation = null;
+ }
+
+ let activeCallFrame = WebInspector.debuggerManager.activeCallFrame;
+ if (!activeCallFrame || !this._matchesSourceCodeLocation(activeCallFrame.sourceCodeLocation)) {
+ this.setExecutionLineAndColumn(NaN, NaN);
+ return;
+ }
+
+ this._dismissPopover();
+
+ this._activeCallFrameSourceCodeLocation = activeCallFrame.sourceCodeLocation;
+ this._activeCallFrameSourceCodeLocation.addEventListener(WebInspector.SourceCodeLocation.Event.LocationChanged, this._activeCallFrameSourceCodeLocationChanged, this);
+
+ // Don't return early if the line number didn't change. The execution state still
+ // could have changed (e.g. continuing in a loop with a breakpoint inside).
+
+ let lineInfo = this._editorLineInfoForSourceCodeLocation(activeCallFrame.sourceCodeLocation);
+ this.setExecutionLineAndColumn(lineInfo.lineNumber, lineInfo.columnNumber);
+
+ // If we have full content or this source code isn't a Resource we can return early.
+ // Script source code populates from the request started in the constructor.
+ if (this._fullContentPopulated || !(this._sourceCode instanceof WebInspector.Resource) || this._requestingScriptContent)
+ return;
+
+ // Since we are paused in the debugger we need to show some content, and since the Resource
+ // content hasn't populated yet we need to populate with content from the Scripts by URL.
+ // Document resources will attempt to populate the scripts as inline (in <script> tags.)
+ // Other resources are assumed to be full scripts (JavaScript resources).
+ if (this._sourceCode.type === WebInspector.Resource.Type.Document)
+ this._populateWithInlineScriptContent();
+ else
+ this._populateWithScriptContent();
+ }
+
+ _activeCallFrameSourceCodeLocationChanged(event)
+ {
+ console.assert(!isNaN(this.executionLineNumber));
+ if (isNaN(this.executionLineNumber))
+ return;
+
+ console.assert(WebInspector.debuggerManager.activeCallFrame);
+ console.assert(this._activeCallFrameSourceCodeLocation === WebInspector.debuggerManager.activeCallFrame.sourceCodeLocation);
+
+ var lineInfo = this._editorLineInfoForSourceCodeLocation(this._activeCallFrameSourceCodeLocation);
+ this.setExecutionLineAndColumn(lineInfo.lineNumber, lineInfo.columnNumber);
+ }
+
+ _populateWithInlineScriptContent()
+ {
+ console.assert(this._sourceCode instanceof WebInspector.Resource);
+ console.assert(!this._fullContentPopulated);
+ console.assert(!this._requestingScriptContent);
+
+ var scripts = this._sourceCode.scripts;
+ console.assert(scripts.length);
+ if (!scripts.length)
+ return;
+
+ var pendingRequestCount = scripts.length;
+
+ // If the number of scripts hasn't change since the last populate, then there is nothing to do.
+ if (this._inlineScriptContentPopulated === pendingRequestCount)
+ return;
+
+ this._inlineScriptContentPopulated = pendingRequestCount;
+
+ function scriptContentAvailable(parameters)
+ {
+ // Return early if we are still waiting for content from other scripts.
+ if (--pendingRequestCount)
+ return;
+
+ this._requestingScriptContent = false;
+
+ // Abort if the full content populated while waiting for these async callbacks.
+ if (this._fullContentPopulated)
+ return;
+
+ var scriptOpenTag = "<script>";
+ var scriptCloseTag = "</script>";
+
+ var content = "";
+ var lineNumber = 0;
+ var columnNumber = 0;
+
+ this._invalidLineNumbers = {};
+
+ for (var i = 0; i < scripts.length; ++i) {
+ // Fill the line gap with newline characters.
+ for (var newLinesCount = scripts[i].range.startLine - lineNumber; newLinesCount > 0; --newLinesCount) {
+ if (!columnNumber)
+ this._invalidLineNumbers[scripts[i].range.startLine - newLinesCount] = true;
+ columnNumber = 0;
+ content += "\n";
+ }
+
+ // Fill the column gap with space characters.
+ for (var spacesCount = scripts[i].range.startColumn - columnNumber - scriptOpenTag.length; spacesCount > 0; --spacesCount)
+ content += " ";
+
+ // Add script tags and content.
+ content += scriptOpenTag;
+ content += scripts[i].content;
+ content += scriptCloseTag;
+
+ lineNumber = scripts[i].range.endLine;
+ columnNumber = scripts[i].range.endColumn + scriptCloseTag.length;
+ }
+
+ this._populateWithContent(content);
+ }
+
+ this._requestingScriptContent = true;
+
+ var boundScriptContentAvailable = scriptContentAvailable.bind(this);
+ for (var i = 0; i < scripts.length; ++i)
+ scripts[i].requestContent().then(boundScriptContentAvailable);
+ }
+
+ _populateWithScriptContent()
+ {
+ console.assert(this._sourceCode instanceof WebInspector.Resource);
+ console.assert(!this._fullContentPopulated);
+ console.assert(!this._requestingScriptContent);
+
+ // We can assume this resource only has one script that starts at line/column 0.
+ var scripts = this._sourceCode.scripts;
+ console.assert(scripts.length === 1);
+ if (!scripts.length)
+ return;
+
+ console.assert(scripts[0].range.startLine === 0);
+ console.assert(scripts[0].range.startColumn === 0);
+
+ function scriptContentAvailable(parameters)
+ {
+ var content = parameters.content;
+ this._requestingScriptContent = false;
+
+ // Abort if the full content populated while waiting for this async callback.
+ if (this._fullContentPopulated)
+ return;
+
+ // This is the full content.
+ this._fullContentPopulated = true;
+
+ this._populateWithContent(content);
+ }
+
+ this._requestingScriptContent = true;
+
+ scripts[0].requestContent().then(scriptContentAvailable.bind(this));
+ }
+
+ _looselyMatchesSourceCodeLocation(sourceCodeLocation)
+ {
+ if (this._sourceCode instanceof WebInspector.SourceMapResource)
+ return sourceCodeLocation.displaySourceCode === this._sourceCode;
+ if (this._sourceCode instanceof WebInspector.Resource || this._sourceCode instanceof WebInspector.Script)
+ return sourceCodeLocation.sourceCode.url === this._sourceCode.url;
+ return false;
+ }
+
+ _matchesSourceCodeLocation(sourceCodeLocation)
+ {
+ if (this._sourceCode instanceof WebInspector.SourceMapResource)
+ return sourceCodeLocation.displaySourceCode === this._sourceCode;
+ if (this._sourceCode instanceof WebInspector.Resource)
+ return sourceCodeLocation.sourceCode.url === this._sourceCode.url;
+ if (this._sourceCode instanceof WebInspector.Script)
+ return sourceCodeLocation.sourceCode === this._sourceCode;
+ return false;
+ }
+
+ _matchesBreakpoint(breakpoint)
+ {
+ console.assert(this._supportsDebugging);
+ if (this._sourceCode instanceof WebInspector.SourceMapResource)
+ return breakpoint.sourceCodeLocation.displaySourceCode === this._sourceCode;
+ if (this._sourceCode instanceof WebInspector.Resource)
+ return breakpoint.contentIdentifier === this._sourceCode.contentIdentifier;
+ if (this._sourceCode instanceof WebInspector.Script)
+ return breakpoint.contentIdentifier === this._sourceCode.contentIdentifier || breakpoint.scriptIdentifier === this._sourceCode.id;
+ return false;
+ }
+
+ _issueWasAdded(event)
+ {
+ var issue = event.data.issue;
+ if (!WebInspector.IssueManager.issueMatchSourceCode(issue, this._sourceCode))
+ return;
+
+ this._addIssue(issue);
+ }
+
+ _addIssue(issue)
+ {
+ var sourceCodeLocation = issue.sourceCodeLocation;
+ console.assert(sourceCodeLocation, "Expected source code location to place issue.");
+ if (!sourceCodeLocation)
+ return;
+
+ var lineNumber = sourceCodeLocation.formattedLineNumber;
+
+ var lineNumberIssues = this._issuesLineNumberMap.get(lineNumber);
+ if (!lineNumberIssues) {
+ lineNumberIssues = [];
+ this._issuesLineNumberMap.set(lineNumber, lineNumberIssues);
+ }
+
+ // Avoid displaying duplicate issues on the same line.
+ for (var existingIssue of lineNumberIssues) {
+ if (existingIssue.sourceCodeLocation.columnNumber === sourceCodeLocation.columnNumber && existingIssue.text === issue.text)
+ return;
+ }
+
+ lineNumberIssues.push(issue);
+
+ if (issue.level === WebInspector.IssueMessage.Level.Error)
+ this.addStyleClassToLine(lineNumber, WebInspector.SourceCodeTextEditor.LineErrorStyleClassName);
+ else if (issue.level === WebInspector.IssueMessage.Level.Warning)
+ this.addStyleClassToLine(lineNumber, WebInspector.SourceCodeTextEditor.LineWarningStyleClassName);
+ else
+ console.error("Unknown issue level");
+
+ var widget = this._issueWidgetForLine(lineNumber);
+ if (widget) {
+ if (issue.level === WebInspector.IssueMessage.Level.Error)
+ widget.widgetElement.classList.add(WebInspector.SourceCodeTextEditor.LineErrorStyleClassName);
+ else if (issue.level === WebInspector.IssueMessage.Level.Warning)
+ widget.widgetElement.classList.add(WebInspector.SourceCodeTextEditor.LineWarningStyleClassName);
+
+ this._updateIssueWidgetForIssues(widget, lineNumberIssues);
+ }
+ }
+
+ _issueWidgetForLine(lineNumber)
+ {
+ var widget = this._widgetMap.get(lineNumber);
+ if (widget)
+ return widget;
+
+ widget = this.createWidgetForLine(lineNumber);
+ if (!widget)
+ return null;
+
+ var widgetElement = widget.widgetElement;
+ widgetElement.classList.add("line-indicator-widget", "issue-widget", "inline");
+ widgetElement.addEventListener("click", this._handleWidgetClick.bind(this, widget, lineNumber));
+
+ this._widgetMap.set(lineNumber, widget);
+
+ return widget;
+ }
+
+ _iconClassNameForIssueLevel(level)
+ {
+ if (level === WebInspector.IssueMessage.Level.Warning)
+ return "icon-warning";
+
+ console.assert(level === WebInspector.IssueMessage.Level.Error);
+ return "icon-error";
+ }
+
+ _updateIssueWidgetForIssues(widget, issues)
+ {
+ var widgetElement = widget.widgetElement;
+ widgetElement.removeChildren();
+
+ if (widgetElement.classList.contains("inline") || issues.length === 1) {
+ var arrowElement = widgetElement.appendChild(document.createElement("span"));
+ arrowElement.className = "arrow";
+
+ var iconElement = widgetElement.appendChild(document.createElement("span"));
+ iconElement.className = "icon";
+
+ var textElement = widgetElement.appendChild(document.createElement("span"));
+ textElement.className = "text";
+
+ if (issues.length === 1) {
+ iconElement.classList.add(this._iconClassNameForIssueLevel(issues[0].level));
+ textElement.textContent = issues[0].text;
+ } else {
+ var errorsCount = 0;
+ var warningsCount = 0;
+ for (var issue of issues) {
+ if (issue.level === WebInspector.IssueMessage.Level.Error)
+ ++errorsCount;
+ else if (issue.level === WebInspector.IssueMessage.Level.Warning)
+ ++warningsCount;
+ }
+
+ if (warningsCount && errorsCount) {
+ iconElement.classList.add(this._iconClassNameForIssueLevel(issue.level));
+ textElement.textContent = WebInspector.UIString("%d Errors, %d Warnings").format(errorsCount, warningsCount);
+ } else if (errorsCount) {
+ iconElement.classList.add(this._iconClassNameForIssueLevel(issue.level));
+ textElement.textContent = WebInspector.UIString("%d Errors").format(errorsCount);
+ } else if (warningsCount) {
+ iconElement.classList.add(this._iconClassNameForIssueLevel(issue.level));
+ textElement.textContent = WebInspector.UIString("%d Warnings").format(warningsCount);
+ }
+
+ widget[WebInspector.SourceCodeTextEditor.WidgetContainsMultipleIssuesSymbol] = true;
+ }
+ } else {
+ for (var issue of issues) {
+ var iconElement = widgetElement.appendChild(document.createElement("span"));
+ iconElement.className = "icon";
+ iconElement.classList.add(this._iconClassNameForIssueLevel(issue.level));
+
+ var textElement = widgetElement.appendChild(document.createElement("span"));
+ textElement.className = "text";
+ textElement.textContent = issue.text;
+
+ widgetElement.appendChild(document.createElement("br"));
+ }
+ }
+
+ widget.update();
+ }
+
+ _isWidgetToggleable(widget)
+ {
+ if (widget[WebInspector.SourceCodeTextEditor.WidgetContainsMultipleIssuesSymbol])
+ return true;
+
+ if (widget[WebInspector.SourceCodeTextEditor.WidgetContainsMultipleThreadsSymbol])
+ return true;
+
+ if (!widget.widgetElement.classList.contains("inline"))
+ return true;
+
+ var textElement = widget.widgetElement.lastChild;
+ if (textElement.offsetWidth !== textElement.scrollWidth)
+ return true;
+
+ return false;
+ }
+
+ _handleWidgetClick(widget, lineNumber, event)
+ {
+ if (!this._isWidgetToggleable(widget))
+ return;
+
+ widget.widgetElement.classList.toggle("inline");
+
+ var lineNumberIssues = this._issuesLineNumberMap.get(lineNumber);
+ this._updateIssueWidgetForIssues(widget, lineNumberIssues);
+ }
+
+ _breakpointInfoForBreakpoint(breakpoint)
+ {
+ return {resolved: breakpoint.resolved, disabled: breakpoint.disabled, autoContinue: breakpoint.autoContinue};
+ }
+
+ get _supportsDebugging()
+ {
+ if (this._sourceCode instanceof WebInspector.Resource)
+ return this._sourceCode.type === WebInspector.Resource.Type.Document || this._sourceCode.type === WebInspector.Resource.Type.Script;
+ if (this._sourceCode instanceof WebInspector.Script)
+ return true;
+ return false;
+ }
+
+ // TextEditor Delegate
+
+ textEditorBaseURL(textEditor)
+ {
+ return this._sourceCode.url;
+ }
+
+ textEditorScriptSourceType(textEditor)
+ {
+ let script = this._getAssociatedScript();
+ return script ? script.sourceType : WebInspector.Script.SourceType.Program;
+ }
+
+ textEditorShouldHideLineNumber(textEditor, lineNumber)
+ {
+ return lineNumber in this._invalidLineNumbers;
+ }
+
+ textEditorGutterContextMenu(textEditor, lineNumber, columnNumber, editorBreakpoints, event)
+ {
+ if (!this._supportsDebugging)
+ return;
+
+ event.preventDefault();
+
+ let addBreakpoint = () => {
+ let data = this.textEditorBreakpointAdded(this, lineNumber, columnNumber);
+ this.setBreakpointInfoForLineAndColumn(data.lineNumber, data.columnNumber, data.breakpointInfo);
+ };
+
+ let contextMenu = WebInspector.ContextMenu.createFromEvent(event);
+
+ // Paused. Add Continue to Here option only if we have a script identifier for the location.
+ if (WebInspector.debuggerManager.paused) {
+ let editorLineInfo = {lineNumber, columnNumber};
+ let unformattedLineInfo = this._unformattedLineInfoForEditorLineInfo(editorLineInfo);
+ let sourceCodeLocation = this._sourceCode.createSourceCodeLocation(unformattedLineInfo.lineNumber, unformattedLineInfo.columnNumber);
+
+ let script;
+ if (sourceCodeLocation.sourceCode instanceof WebInspector.Script)
+ script = sourceCodeLocation.sourceCode;
+ else if (sourceCodeLocation.sourceCode instanceof WebInspector.Resource)
+ script = sourceCodeLocation.sourceCode.scriptForLocation(sourceCodeLocation);
+
+ if (script) {
+ contextMenu.appendItem(WebInspector.UIString("Continue to Here"), () => {
+ WebInspector.debuggerManager.continueToLocation(script, sourceCodeLocation.lineNumber, sourceCodeLocation.columnNumber);
+ });
+ contextMenu.appendSeparator();
+ }
+ }
+
+ let breakpoints = [];
+ for (let lineInfo of editorBreakpoints) {
+ let breakpoint = this._breakpointForEditorLineInfo(lineInfo);
+ console.assert(breakpoint);
+ if (breakpoint)
+ breakpoints.push(breakpoint);
+ }
+
+ // No breakpoints.
+ if (!breakpoints.length) {
+ contextMenu.appendItem(WebInspector.UIString("Add Breakpoint"), addBreakpoint.bind(this));
+ return;
+ }
+
+ // Single breakpoint.
+ if (breakpoints.length === 1) {
+ WebInspector.breakpointPopoverController.appendContextMenuItems(contextMenu, breakpoints[0], event.target);
+
+ if (!WebInspector.isShowingDebuggerTab()) {
+ contextMenu.appendSeparator();
+ contextMenu.appendItem(WebInspector.UIString("Reveal in Debugger Tab"), () => {
+ WebInspector.showDebuggerTab(breakpoints[0]);
+ });
+ }
+
+ return;
+ }
+
+ // Multiple breakpoints.
+ let removeBreakpoints = () => {
+ for (let breakpoint of breakpoints) {
+ if (WebInspector.debuggerManager.isBreakpointRemovable(breakpoint))
+ WebInspector.debuggerManager.removeBreakpoint(breakpoint);
+ }
+ };
+
+ let shouldDisable = breakpoints.some((breakpoint) => !breakpoint.disabled);
+ let toggleBreakpoints = (shouldDisable) => {
+ for (let breakpoint of breakpoints)
+ breakpoint.disabled = shouldDisable;
+ };
+
+ if (shouldDisable)
+ contextMenu.appendItem(WebInspector.UIString("Disable Breakpoints"), toggleBreakpoints);
+ else
+ contextMenu.appendItem(WebInspector.UIString("Enable Breakpoints"), toggleBreakpoints);
+ contextMenu.appendItem(WebInspector.UIString("Delete Breakpoints"), removeBreakpoints);
+ }
+
+ textEditorBreakpointAdded(textEditor, lineNumber, columnNumber)
+ {
+ if (!this._supportsDebugging)
+ return null;
+
+ var editorLineInfo = {lineNumber, columnNumber};
+ var unformattedLineInfo = this._unformattedLineInfoForEditorLineInfo(editorLineInfo);
+ var sourceCodeLocation = this._sourceCode.createSourceCodeLocation(unformattedLineInfo.lineNumber, unformattedLineInfo.columnNumber);
+ var breakpoint = new WebInspector.Breakpoint(sourceCodeLocation);
+
+ var lineInfo = this._editorLineInfoForSourceCodeLocation(breakpoint.sourceCodeLocation);
+ this._addBreakpointWithEditorLineInfo(breakpoint, lineInfo);
+
+ this._ignoreBreakpointAddedBreakpoint = breakpoint;
+ const shouldSpeculativelyResolveBreakpoint = true;
+ WebInspector.debuggerManager.addBreakpoint(breakpoint, shouldSpeculativelyResolveBreakpoint);
+ this._ignoreBreakpointAddedBreakpoint = null;
+
+ // Return the more accurate location and breakpoint info.
+ return {
+ breakpointInfo: this._breakpointInfoForBreakpoint(breakpoint),
+ lineNumber: lineInfo.lineNumber,
+ columnNumber: lineInfo.columnNumber
+ };
+ }
+
+ textEditorBreakpointRemoved(textEditor, lineNumber, columnNumber)
+ {
+ console.assert(this._supportsDebugging);
+ if (!this._supportsDebugging)
+ return;
+
+ var lineInfo = {lineNumber, columnNumber};
+ var breakpoint = this._breakpointForEditorLineInfo(lineInfo);
+ console.assert(breakpoint);
+ if (!breakpoint)
+ return;
+
+ this._removeBreakpointWithEditorLineInfo(breakpoint, lineInfo);
+
+ this._ignoreBreakpointRemovedBreakpoint = breakpoint;
+ WebInspector.debuggerManager.removeBreakpoint(breakpoint);
+ this._ignoreBreakpointRemovedBreakpoint = null;
+ }
+
+ textEditorBreakpointMoved(textEditor, oldLineNumber, oldColumnNumber, newLineNumber, newColumnNumber)
+ {
+ console.assert(this._supportsDebugging);
+ if (!this._supportsDebugging)
+ return;
+
+ var oldLineInfo = {lineNumber: oldLineNumber, columnNumber: oldColumnNumber};
+ var breakpoint = this._breakpointForEditorLineInfo(oldLineInfo);
+ console.assert(breakpoint);
+ if (!breakpoint)
+ return;
+
+ this._removeBreakpointWithEditorLineInfo(breakpoint, oldLineInfo);
+
+ var newLineInfo = {lineNumber: newLineNumber, columnNumber: newColumnNumber};
+ var unformattedNewLineInfo = this._unformattedLineInfoForEditorLineInfo(newLineInfo);
+ this._ignoreLocationUpdateBreakpoint = breakpoint;
+ breakpoint.sourceCodeLocation.update(this._sourceCode, unformattedNewLineInfo.lineNumber, unformattedNewLineInfo.columnNumber);
+ this._ignoreLocationUpdateBreakpoint = null;
+
+ var accurateNewLineInfo = this._editorLineInfoForSourceCodeLocation(breakpoint.sourceCodeLocation);
+ this._addBreakpointWithEditorLineInfo(breakpoint, accurateNewLineInfo);
+
+ if (accurateNewLineInfo.lineNumber !== newLineInfo.lineNumber || accurateNewLineInfo.columnNumber !== newLineInfo.columnNumber)
+ this.updateBreakpointLineAndColumn(newLineInfo.lineNumber, newLineInfo.columnNumber, accurateNewLineInfo.lineNumber, accurateNewLineInfo.columnNumber);
+ }
+
+ textEditorBreakpointClicked(textEditor, lineNumber, columnNumber)
+ {
+ console.assert(this._supportsDebugging);
+ if (!this._supportsDebugging)
+ return;
+
+ var breakpoint = this._breakpointForEditorLineInfo({lineNumber, columnNumber});
+ console.assert(breakpoint);
+ if (!breakpoint)
+ return;
+
+ breakpoint.cycleToNextMode();
+ }
+
+ textEditorUpdatedFormatting(textEditor)
+ {
+ this._ignoreAllBreakpointLocationUpdates = true;
+ this._sourceCode.formatterSourceMap = this.formatterSourceMap;
+ this._ignoreAllBreakpointLocationUpdates = false;
+
+ // Always put the source map on both the Script and Resource if both exist. For example,
+ // if this SourceCode is a Resource, then there might also be a Script. In the debugger,
+ // the backend identifies call frames with Script line and column information, and the
+ // Script needs the formatter source map to produce the proper display line and column.
+ if (this._sourceCode instanceof WebInspector.Resource && !(this._sourceCode instanceof WebInspector.SourceMapResource)) {
+ var scripts = this._sourceCode.scripts;
+ for (var i = 0; i < scripts.length; ++i)
+ scripts[i].formatterSourceMap = this.formatterSourceMap;
+ } else if (this._sourceCode instanceof WebInspector.Script) {
+ if (this._sourceCode.resource)
+ this._sourceCode.resource.formatterSourceMap = this.formatterSourceMap;
+ }
+
+ // Some breakpoints / issues may have moved, some might not have. Just go through
+ // and remove and reinsert all the breakpoints / issues.
+
+ var oldBreakpointMap = this._breakpointMap;
+ this._breakpointMap = {};
+
+ for (var lineNumber in oldBreakpointMap) {
+ for (var columnNumber in oldBreakpointMap[lineNumber]) {
+ var breakpoint = oldBreakpointMap[lineNumber][columnNumber];
+ var newLineInfo = this._editorLineInfoForSourceCodeLocation(breakpoint.sourceCodeLocation);
+ this._addBreakpointWithEditorLineInfo(breakpoint, newLineInfo);
+ this.setBreakpointInfoForLineAndColumn(lineNumber, columnNumber, null);
+ this.setBreakpointInfoForLineAndColumn(newLineInfo.lineNumber, newLineInfo.columnNumber, this._breakpointInfoForBreakpoint(breakpoint));
+ }
+ }
+
+ this._reinsertAllIssues();
+ this._reinsertAllThreadIndicators();
+ }
+
+ textEditorExecutionHighlightRange(offset, position, characterAtOffset, callback)
+ {
+ let script = this._getAssociatedScript(position);
+ if (!script) {
+ callback(null);
+ return;
+ }
+
+ // If this is an inline script, then convert to offset within the inline script.
+ let adjustment = script.range.startOffset || 0;
+ offset = offset - adjustment;
+
+ // When returning offsets, convert to offsets within the SourceCode being viewed.
+ function convertRangeOffsetsToSourceCodeOffsets(range) {
+ return range.map((offset) => offset + adjustment);
+ }
+
+ script.requestScriptSyntaxTree((syntaxTree) => {
+ let nodes = syntaxTree.containersOfOffset(offset);
+ if (!nodes.length) {
+ callback(null);
+ return;
+ }
+
+ // Find a node starting at this offset.
+ // Avoid highlighting the entire program if this is the start of the first statement.
+ // Special case the assignment expression inside of a for..of and for..in to highlight a larger range.
+ for (let node of nodes) {
+ let startOffset = node.range[0];
+ if (startOffset === offset && node.type !== WebInspector.ScriptSyntaxTree.NodeType.Program) {
+ callback(convertRangeOffsetsToSourceCodeOffsets(node.range));
+ return;
+ }
+ if (node.type === WebInspector.ScriptSyntaxTree.NodeType.ForInStatement || node.type === WebInspector.ScriptSyntaxTree.NodeType.ForOfStatement) {
+ if (node.left.range[0] === offset) {
+ callback(convertRangeOffsetsToSourceCodeOffsets([node.left.range[0], node.right.range[1]]));
+ return;
+ }
+ }
+ if (startOffset > offset)
+ break;
+ }
+
+ // Find a node ending at this offset. (Leaving a block).
+ // We check this after ensuring nothing starts with this offset,
+ // as that would be more important.
+ for (let node of nodes) {
+ let startOffset = node.range[0];
+ let endOffset = node.range[1];
+ if (endOffset === offset) {
+ if (node.type === WebInspector.ScriptSyntaxTree.NodeType.BlockStatement) {
+ // Closing brace of a block, only highlight the closing brace character.
+ callback(convertRangeOffsetsToSourceCodeOffsets([offset - 1, offset]));
+ return;
+ }
+ }
+ if (startOffset > offset)
+ break;
+ }
+
+ // Find the best container node for this expression.
+ // Sort by the tightest bounds so we can walk from specific to general nodes.
+ nodes.sort((a, b) => {
+ let aLength = a.range[1] - a.range[0];
+ let bLength = b.range[1] - b.range[0];
+ return aLength - bLength;
+ });
+
+ let characterAtOffsetIsDotOrBracket = characterAtOffset === "." || characterAtOffset === "[";
+
+ for (let i = 0; i < nodes.length; ++i) {
+ let node = nodes[i];
+
+ // In a function call.
+ if (node.type === WebInspector.ScriptSyntaxTree.NodeType.CallExpression
+ || node.type === WebInspector.ScriptSyntaxTree.NodeType.NewExpression
+ || node.type === WebInspector.ScriptSyntaxTree.NodeType.ThrowStatement) {
+ callback(convertRangeOffsetsToSourceCodeOffsets(node.range));
+ return;
+ }
+
+ // In the middle of a member expression we want to highlight the best
+ // member expression range. We can end up in the middle when we are
+ // paused inside of a getter and select the parent call frame. For
+ // these cases we may be at a '.' or '[' and we can find the best member
+ // expression from there.
+ //
+ // Examples:
+ //
+ // foo*.x.y.z => inside x looking at parent call frame => |foo.x|.y.z
+ // foo.x*.y.z => inside y looking at parent call frame => |foo.x.y|.z
+ //
+ // foo*["x"]["y"]["z"] => inside x looking at parent call frame => |foo["x"]|["y"]["z"]
+ // foo["x"]*["y"]["z"] => inside y looking at parent call frame => |foo["x"]["y"]|["z"]
+ //
+ if (node.type === WebInspector.ScriptSyntaxTree.NodeType.ThisExpression
+ || (characterAtOffsetIsDotOrBracket && (node.type === WebInspector.ScriptSyntaxTree.NodeType.Identifier || node.type === WebInspector.ScriptSyntaxTree.NodeType.MemberExpression))) {
+ let memberExpressionNode = null;
+ for (let j = i + 1; j < nodes.length; ++j) {
+ let nextNode = nodes[j];
+ if (nextNode.type === WebInspector.ScriptSyntaxTree.NodeType.MemberExpression) {
+ memberExpressionNode = nextNode;
+ if (offset === memberExpressionNode.range[1])
+ continue;
+ }
+ break;
+ }
+
+ if (memberExpressionNode) {
+ callback(convertRangeOffsetsToSourceCodeOffsets(memberExpressionNode.range));
+ return;
+ }
+
+ callback(convertRangeOffsetsToSourceCodeOffsets(node.range));
+ return;
+ }
+ }
+
+ // No matches, just highlight the line.
+ callback(null);
+ });
+ }
+
+ _clearIssueWidgets()
+ {
+ for (var widget of this._widgetMap.values())
+ widget.clear();
+
+ this._widgetMap.clear();
+ }
+
+ _reinsertAllIssues()
+ {
+ this._issuesLineNumberMap.clear();
+ this._clearIssueWidgets();
+
+ let issues = WebInspector.issueManager.issuesForSourceCode(this._sourceCode);
+ for (let issue of issues)
+ this._addIssue(issue);
+ }
+
+ _reinsertAllThreadIndicators()
+ {
+ // Clear line styles.
+ for (let lineNumber of this._threadLineNumberMap.keys())
+ this.removeStyleClassFromLine(lineNumber, "thread-indicator");
+ this._threadLineNumberMap.clear();
+
+ // Clear widgets.
+ for (let widget of this._threadWidgetMap.values())
+ widget.clear();
+ this._threadWidgetMap.clear();
+
+ // Clear other maps.
+ this._threadTargetMap.clear();
+
+ if (WebInspector.targets.size > 1) {
+ for (let target of WebInspector.targets)
+ this._addThreadIndicatorForTarget(target);
+ }
+ }
+
+ _debuggerDidPause(event)
+ {
+ this._updateTokenTrackingControllerState();
+ if (this._typeTokenAnnotator && this._typeTokenAnnotator.isActive())
+ this._typeTokenAnnotator.refresh();
+ if (this._basicBlockAnnotator && this._basicBlockAnnotator.isActive())
+ this._basicBlockAnnotator.refresh();
+ }
+
+ _debuggerDidResume(event)
+ {
+ this._updateTokenTrackingControllerState();
+ this._dismissPopover();
+ if (this._typeTokenAnnotator && this._typeTokenAnnotator.isActive())
+ this._typeTokenAnnotator.refresh();
+ if (this._basicBlockAnnotator && this._basicBlockAnnotator.isActive())
+ this._basicBlockAnnotator.refresh();
+ }
+
+ _sourceCodeSourceMapAdded(event)
+ {
+ WebInspector.notifications.addEventListener(WebInspector.Notification.GlobalModifierKeysDidChange, this._updateTokenTrackingControllerState, this);
+ this._sourceCode.removeEventListener(WebInspector.SourceCode.Event.SourceMapAdded, this._sourceCodeSourceMapAdded, this);
+
+ this._updateTokenTrackingControllerState();
+ }
+
+ _updateTokenTrackingControllerState()
+ {
+ var mode = WebInspector.CodeMirrorTokenTrackingController.Mode.None;
+ if (WebInspector.debuggerManager.paused)
+ mode = WebInspector.CodeMirrorTokenTrackingController.Mode.JavaScriptExpression;
+ else if (this._typeTokenAnnotator && this._typeTokenAnnotator.isActive())
+ mode = WebInspector.CodeMirrorTokenTrackingController.Mode.JavaScriptTypeInformation;
+ else if (this._hasColorMarkers())
+ mode = WebInspector.CodeMirrorTokenTrackingController.Mode.MarkedTokens;
+ else if ((this._sourceCode instanceof WebInspector.SourceMapResource || this._sourceCode.sourceMaps.length !== 0) && WebInspector.modifierKeys.metaKey && !WebInspector.modifierKeys.altKey && !WebInspector.modifierKeys.shiftKey)
+ mode = WebInspector.CodeMirrorTokenTrackingController.Mode.NonSymbolTokens;
+
+ this.tokenTrackingController.enabled = mode !== WebInspector.CodeMirrorTokenTrackingController.Mode.None;
+
+ if (mode === this.tokenTrackingController.mode)
+ return;
+
+ switch (mode) {
+ case WebInspector.CodeMirrorTokenTrackingController.Mode.MarkedTokens:
+ this.tokenTrackingController.mouseOverDelayDuration = 0;
+ this.tokenTrackingController.mouseOutReleaseDelayDuration = 0;
+ break;
+ case WebInspector.CodeMirrorTokenTrackingController.Mode.NonSymbolTokens:
+ this.tokenTrackingController.mouseOverDelayDuration = 0;
+ this.tokenTrackingController.mouseOutReleaseDelayDuration = 0;
+ this.tokenTrackingController.classNameForHighlightedRange = WebInspector.CodeMirrorTokenTrackingController.JumpToSymbolHighlightStyleClassName;
+ this._dismissPopover();
+ break;
+ case WebInspector.CodeMirrorTokenTrackingController.Mode.JavaScriptExpression:
+ case WebInspector.CodeMirrorTokenTrackingController.Mode.JavaScriptTypeInformation:
+ this.tokenTrackingController.mouseOverDelayDuration = WebInspector.SourceCodeTextEditor.DurationToMouseOverTokenToMakeHoveredToken;
+ this.tokenTrackingController.mouseOutReleaseDelayDuration = WebInspector.SourceCodeTextEditor.DurationToMouseOutOfHoveredTokenToRelease;
+ this.tokenTrackingController.classNameForHighlightedRange = WebInspector.SourceCodeTextEditor.HoveredExpressionHighlightStyleClassName;
+ break;
+ }
+
+ this.tokenTrackingController.mode = mode;
+ }
+
+ _hasColorMarkers()
+ {
+ for (var marker of this.markers) {
+ if (marker.type === WebInspector.TextMarker.Type.Color)
+ return true;
+ }
+ return false;
+ }
+
+ // CodeMirrorTokenTrackingController Delegate
+
+ tokenTrackingControllerCanReleaseHighlightedRange(tokenTrackingController, element)
+ {
+ if (!this._popover)
+ return true;
+
+ if (!window.getSelection().isCollapsed && this._popover.element.contains(window.getSelection().anchorNode))
+ return false;
+
+ return true;
+ }
+
+ tokenTrackingControllerHighlightedRangeReleased(tokenTrackingController, forceHide = false)
+ {
+ if (forceHide || !this._mouseIsOverPopover)
+ this._dismissPopover();
+ }
+
+ tokenTrackingControllerHighlightedRangeWasClicked(tokenTrackingController)
+ {
+ if (this.tokenTrackingController.mode !== WebInspector.CodeMirrorTokenTrackingController.Mode.NonSymbolTokens)
+ return;
+
+ // Links are handled by TextEditor.
+ if (/\blink\b/.test(this.tokenTrackingController.candidate.hoveredToken.type))
+ return;
+
+ var sourceCodeLocation = this._sourceCodeLocationForEditorPosition(this.tokenTrackingController.candidate.hoveredTokenRange.start);
+ if (this.sourceCode instanceof WebInspector.SourceMapResource)
+ WebInspector.showOriginalOrFormattedSourceCodeLocation(sourceCodeLocation);
+ else
+ WebInspector.showSourceCodeLocation(sourceCodeLocation);
+ }
+
+ tokenTrackingControllerNewHighlightCandidate(tokenTrackingController, candidate)
+ {
+ if (this.tokenTrackingController.mode === WebInspector.CodeMirrorTokenTrackingController.Mode.NonSymbolTokens) {
+ this.tokenTrackingController.highlightRange(candidate.hoveredTokenRange);
+ return;
+ }
+
+ if (this.tokenTrackingController.mode === WebInspector.CodeMirrorTokenTrackingController.Mode.JavaScriptExpression) {
+ this._tokenTrackingControllerHighlightedJavaScriptExpression(candidate);
+ return;
+ }
+
+ if (this.tokenTrackingController.mode === WebInspector.CodeMirrorTokenTrackingController.Mode.JavaScriptTypeInformation) {
+ this._tokenTrackingControllerHighlightedJavaScriptTypeInformation(candidate);
+ return;
+ }
+
+ if (this.tokenTrackingController.mode === WebInspector.CodeMirrorTokenTrackingController.Mode.MarkedTokens) {
+ var markers = this.markersAtPosition(candidate.hoveredTokenRange.start);
+ if (markers.length > 0)
+ this._tokenTrackingControllerHighlightedMarkedExpression(candidate, markers);
+ else
+ this._dismissEditingController();
+ }
+ }
+
+ tokenTrackingControllerMouseOutOfHoveredMarker(tokenTrackingController, hoveredMarker)
+ {
+ this._dismissEditingController();
+ }
+
+ _tokenTrackingControllerHighlightedJavaScriptExpression(candidate)
+ {
+ console.assert(candidate.expression);
+
+ function populate(error, result, wasThrown)
+ {
+ if (error || wasThrown)
+ return;
+
+ if (candidate !== this.tokenTrackingController.candidate)
+ return;
+
+ let data = WebInspector.RemoteObject.fromPayload(result, this.target);
+ switch (data.type) {
+ case "function":
+ this._showPopoverForFunction(data);
+ break;
+ case "object":
+ if (data.subtype === "null" || data.subtype === "regexp")
+ this._showPopoverWithFormattedValue(data);
+ else
+ this._showPopoverForObject(data);
+ break;
+ case "string":
+ case "number":
+ case "boolean":
+ case "undefined":
+ case "symbol":
+ this._showPopoverWithFormattedValue(data);
+ break;
+ }
+ }
+
+ let target = WebInspector.debuggerManager.activeCallFrame ? WebInspector.debuggerManager.activeCallFrame.target : this.target;
+ let expression = appendWebInspectorSourceURL(candidate.expression);
+
+ if (WebInspector.debuggerManager.activeCallFrame) {
+ target.DebuggerAgent.evaluateOnCallFrame.invoke({callFrameId: WebInspector.debuggerManager.activeCallFrame.id, expression, objectGroup: "popover", doNotPauseOnExceptionsAndMuteConsole: true}, populate.bind(this), target.DebuggerAgent);
+ return;
+ }
+
+ // No call frame available. Use the SourceCode's page's context.
+ target.RuntimeAgent.evaluate.invoke({expression, objectGroup: "popover", doNotPauseOnExceptionsAndMuteConsole: true}, populate.bind(this), target.RuntimeAgent);
+ }
+
+ _tokenTrackingControllerHighlightedJavaScriptTypeInformation(candidate)
+ {
+ console.assert(candidate.expression);
+
+ var sourceCode = this._sourceCode;
+ var sourceID = sourceCode instanceof WebInspector.Script ? sourceCode.id : sourceCode.scripts[0].id;
+ var range = candidate.hoveredTokenRange;
+ var offset = this.currentPositionToOriginalOffset(range.start);
+
+ var allRequests = [{
+ typeInformationDescriptor: WebInspector.ScriptSyntaxTree.TypeProfilerSearchDescriptor.NormalExpression,
+ sourceID,
+ divot: offset
+ }];
+
+ function handler(error, allTypes) {
+ if (error)
+ return;
+
+ if (candidate !== this.tokenTrackingController.candidate)
+ return;
+
+ console.assert(allTypes.length === 1);
+ if (!allTypes.length)
+ return;
+
+ var typeDescription = WebInspector.TypeDescription.fromPayload(allTypes[0]);
+ if (typeDescription.valid) {
+ var popoverTitle = WebInspector.TypeTokenView.titleForPopover(WebInspector.TypeTokenView.TitleType.Variable, candidate.expression);
+ this.showPopoverForTypes(typeDescription, null, popoverTitle);
+ }
+ }
+
+ this.target.RuntimeAgent.getRuntimeTypesForVariablesAtOffsets(allRequests, handler.bind(this));
+ }
+
+ _showPopover(content, bounds)
+ {
+ console.assert(this.tokenTrackingController.candidate || bounds);
+
+ var shouldHighlightRange = false;
+ var candidate = this.tokenTrackingController.candidate;
+ // If bounds is falsey, this is a popover introduced from a hover event.
+ // Otherwise, this is called from TypeTokenAnnotator.
+ if (!bounds) {
+ if (!candidate)
+ return;
+
+ var rects = this.rectsForRange(candidate.hoveredTokenRange);
+ bounds = WebInspector.Rect.unionOfRects(rects);
+
+ shouldHighlightRange = true;
+ }
+
+ content.classList.add(WebInspector.SourceCodeTextEditor.PopoverDebuggerContentStyleClassName);
+
+ this._popover = this._popover || new WebInspector.Popover(this);
+ this._popover.presentNewContentWithFrame(content, bounds.pad(5), [WebInspector.RectEdge.MIN_Y, WebInspector.RectEdge.MAX_Y, WebInspector.RectEdge.MAX_X]);
+ if (shouldHighlightRange)
+ this.tokenTrackingController.highlightRange(candidate.expressionRange);
+
+ this._trackPopoverEvents();
+ }
+
+ _showPopoverForFunction(data)
+ {
+ let candidate = this.tokenTrackingController.candidate;
+
+ function didGetDetails(error, response)
+ {
+ if (error) {
+ console.error(error);
+ this._dismissPopover();
+ return;
+ }
+
+ // Nothing to do if the token has changed since the time we
+ // asked for the function details from the backend.
+ if (candidate !== this.tokenTrackingController.candidate)
+ return;
+
+ let content = document.createElement("div");
+ content.classList.add("function");
+
+ let title = document.createElement("div");
+ title.classList.add("title");
+ title.textContent = response.name || response.displayName || WebInspector.UIString("(anonymous function)");
+ content.appendChild(title);
+
+ let location = response.location;
+ let sourceCode = WebInspector.debuggerManager.scriptForIdentifier(location.scriptId, this.target);
+ let sourceCodeLocation = sourceCode.createSourceCodeLocation(location.lineNumber, location.columnNumber);
+ let functionSourceCodeLink = WebInspector.createSourceCodeLocationLink(sourceCodeLocation);
+ title.appendChild(functionSourceCodeLink);
+
+ let wrapper = document.createElement("div");
+ wrapper.classList.add("body");
+ content.appendChild(wrapper);
+
+ let codeMirror = WebInspector.CodeMirrorEditor.create(wrapper, {
+ mode: "text/javascript",
+ readOnly: "nocursor",
+ });
+ codeMirror.on("update", () => {
+ this._popover.update();
+ });
+
+ const isModule = false;
+ const indentString = WebInspector.indentString();
+ const includeSourceMapData = false;
+ let workerProxy = WebInspector.FormatterWorkerProxy.singleton();
+ workerProxy.formatJavaScript(data.description, isModule, indentString, includeSourceMapData, ({formattedText}) => {
+ codeMirror.setValue(formattedText || data.description);
+ });
+
+ this._showPopover(content);
+ }
+
+ data.target.DebuggerAgent.getFunctionDetails(data.objectId, didGetDetails.bind(this));
+ }
+
+ _showPopoverForObject(data)
+ {
+ var content = document.createElement("div");
+ content.className = "object expandable";
+
+ var titleElement = document.createElement("div");
+ titleElement.className = "title";
+ titleElement.textContent = data.description;
+ content.appendChild(titleElement);
+
+ if (data.subtype === "node") {
+ data.pushNodeToFrontend(function(nodeId) {
+ if (!nodeId)
+ return;
+
+ var domNode = WebInspector.domTreeManager.nodeForId(nodeId);
+ if (!domNode.ownerDocument)
+ return;
+
+ var goToButton = titleElement.appendChild(WebInspector.createGoToArrowButton());
+ goToButton.addEventListener("click", function() {
+ WebInspector.domTreeManager.inspectElement(nodeId);
+ });
+ });
+ }
+
+ // FIXME: If this is a variable, it would be nice to put the variable name in the PropertyPath.
+ var objectTree = new WebInspector.ObjectTreeView(data);
+ objectTree.showOnlyProperties();
+ objectTree.expand();
+
+ var bodyElement = content.appendChild(document.createElement("div"));
+ bodyElement.className = "body";
+ bodyElement.appendChild(objectTree.element);
+
+ // Show the popover once we have the first set of properties for the object.
+ var candidate = this.tokenTrackingController.candidate;
+ objectTree.addEventListener(WebInspector.ObjectTreeView.Event.Updated, function() {
+ if (candidate === this.tokenTrackingController.candidate)
+ this._showPopover(content);
+ objectTree.removeEventListener(null, null, this);
+ }, this);
+ }
+
+ _showPopoverWithFormattedValue(remoteObject)
+ {
+ var content = WebInspector.FormattedValue.createElementForRemoteObject(remoteObject);
+ this._showPopover(content);
+ }
+
+ willDismissPopover(popover)
+ {
+ this.tokenTrackingController.removeHighlightedRange();
+
+ this.target.RuntimeAgent.releaseObjectGroup("popover");
+ }
+
+ _dismissPopover()
+ {
+ if (!this._popover)
+ return;
+
+ this._popover.dismiss();
+
+ if (this._popoverEventListeners && this._popoverEventListenersAreRegistered) {
+ this._popoverEventListenersAreRegistered = false;
+ this._popoverEventListeners.unregister();
+ }
+ }
+
+ _trackPopoverEvents()
+ {
+ if (!this._popoverEventListeners)
+ this._popoverEventListeners = new WebInspector.EventListenerSet(this, "Popover listeners");
+ if (!this._popoverEventListenersAreRegistered) {
+ this._popoverEventListenersAreRegistered = true;
+ this._popoverEventListeners.register(this._popover.element, "mouseover", this._popoverMouseover);
+ this._popoverEventListeners.register(this._popover.element, "mouseout", this._popoverMouseout);
+ this._popoverEventListeners.install();
+ }
+ }
+
+ _popoverMouseover(event)
+ {
+ this._mouseIsOverPopover = true;
+ }
+
+ _popoverMouseout(event)
+ {
+ this._mouseIsOverPopover = this._popover.element.contains(event.relatedTarget);
+ }
+
+ _hasStyleSheetContents()
+ {
+ let mimeType = this.mimeType;
+ return mimeType === "text/css"
+ || mimeType === "text/x-less"
+ || mimeType === "text/x-sass"
+ || mimeType === "text/x-scss";
+ }
+
+ _updateEditableMarkers(range)
+ {
+ if (this._hasStyleSheetContents()) {
+ this.createColorMarkers(range);
+ this.createGradientMarkers(range);
+ this.createCubicBezierMarkers(range);
+ this.createSpringMarkers(range);
+ }
+
+ this._updateTokenTrackingControllerState();
+ }
+
+ _tokenTrackingControllerHighlightedMarkedExpression(candidate, markers)
+ {
+ // Look for the outermost editable marker.
+ var editableMarker;
+ for (var marker of markers) {
+ if (!marker.range || !Object.values(WebInspector.TextMarker.Type).includes(marker.type))
+ continue;
+
+ if (!editableMarker || (marker.range.startLine < editableMarker.range.startLine || (marker.range.startLine === editableMarker.range.startLine && marker.range.startColumn < editableMarker.range.startColumn)))
+ editableMarker = marker;
+ }
+
+ if (!editableMarker) {
+ this.tokenTrackingController.hoveredMarker = null;
+ return;
+ }
+
+ if (this.tokenTrackingController.hoveredMarker === editableMarker)
+ return;
+
+ this._dismissEditingController();
+
+ this.tokenTrackingController.hoveredMarker = editableMarker;
+
+ this._editingController = this.editingControllerForMarker(editableMarker);
+
+ if (marker.type === WebInspector.TextMarker.Type.Color) {
+ var color = this._editingController.value;
+ if (!color || !color.valid) {
+ editableMarker.clear();
+ this._editingController = null;
+ return;
+ }
+ }
+
+ this._editingController.delegate = this;
+ this._editingController.presentHoverMenu();
+ }
+
+ _dismissEditingController(discrete)
+ {
+ if (this._editingController)
+ this._editingController.dismissHoverMenu(discrete);
+
+ this.tokenTrackingController.hoveredMarker = null;
+ this._editingController = null;
+ }
+
+ // CodeMirrorEditingController Delegate
+
+ editingControllerDidStartEditing(editingController)
+ {
+ // We can pause the token tracking controller during editing, it will be reset
+ // to the expected state by calling _updateEditableMarkers() in the
+ // editingControllerDidFinishEditing delegate.
+ this.tokenTrackingController.enabled = false;
+
+ // We clear the marker since we'll reset it after editing.
+ editingController.marker.clear();
+
+ // We ignore content changes made as a result of color editing.
+ this._ignoreContentDidChange++;
+ }
+
+ editingControllerDidFinishEditing(editingController)
+ {
+ this._updateEditableMarkers(editingController.range);
+
+ this._ignoreContentDidChange--;
+
+ this._editingController = null;
+ }
+
+ _setTypeTokenAnnotatorEnabledState(shouldActivate)
+ {
+ if (!this._typeTokenAnnotator)
+ return;
+
+ if (shouldActivate) {
+ console.assert(this.visible, "Annotators should not be enabled if the TextEditor is not visible");
+
+ this._typeTokenAnnotator.reset();
+
+ if (!this._typeTokenScrollHandler)
+ this._enableScrollEventsForTypeTokenAnnotator();
+ } else {
+ this._typeTokenAnnotator.clear();
+
+ if (this._typeTokenScrollHandler)
+ this._disableScrollEventsForTypeTokenAnnotator();
+ }
+
+ WebInspector.showJavaScriptTypeInformationSetting.value = shouldActivate;
+
+ this._updateTokenTrackingControllerState();
+ }
+
+ set _basicBlockAnnotatorEnabled(shouldActivate)
+ {
+ if (!this._basicBlockAnnotator)
+ return;
+
+ if (shouldActivate) {
+ console.assert(this.visible, "Annotators should not be enabled if the TextEditor is not visible");
+
+ console.assert(!this._basicBlockAnnotator.isActive());
+ this._basicBlockAnnotator.reset();
+
+ if (!this._controlFlowScrollHandler)
+ this._enableScrollEventsForControlFlowAnnotator();
+ } else {
+ this._basicBlockAnnotator.clear();
+
+ if (this._controlFlowScrollHandler)
+ this._disableScrollEventsForControlFlowAnnotator();
+ }
+
+ WebInspector.enableControlFlowProfilerSetting.value = shouldActivate;
+ }
+
+ _getAssociatedScript(position)
+ {
+ let script = null;
+
+ if (this._sourceCode instanceof WebInspector.Script)
+ script = this._sourceCode;
+ else if (this._sourceCode instanceof WebInspector.Resource && this._sourceCode.scripts.length) {
+ if (this._sourceCode.type === WebInspector.Resource.Type.Script)
+ script = this._sourceCode.scripts[0];
+ else if (this._sourceCode.type === WebInspector.Resource.Type.Document && position) {
+ for (let inlineScript of this._sourceCode.scripts) {
+ if (inlineScript.range.contains(position.lineNumber, position.columnNumber)) {
+ if (isNaN(inlineScript.range.startOffset))
+ inlineScript.range.resolveOffsets(this._sourceCode.content);
+ script = inlineScript;
+ break;
+ }
+ }
+ }
+ }
+
+ return script;
+ }
+
+ _createTypeTokenAnnotator()
+ {
+ // COMPATIBILITY (iOS 8): Runtime.getRuntimeTypesForVariablesAtOffsets did not exist yet.
+ if (!RuntimeAgent.getRuntimeTypesForVariablesAtOffsets)
+ return;
+
+ var script = this._getAssociatedScript();
+ if (!script)
+ return;
+
+ this._typeTokenAnnotator = new WebInspector.TypeTokenAnnotator(this, script);
+ }
+
+ _createBasicBlockAnnotator()
+ {
+ // COMPATIBILITY (iOS 8): Runtime.getBasicBlocks did not exist yet.
+ if (!RuntimeAgent.getBasicBlocks)
+ return;
+
+ var script = this._getAssociatedScript();
+ if (!script)
+ return;
+
+ this._basicBlockAnnotator = new WebInspector.BasicBlockAnnotator(this, script);
+ }
+
+ _enableScrollEventsForTypeTokenAnnotator()
+ {
+ // Pause updating type tokens while scrolling to prevent frame loss.
+ console.assert(!this._typeTokenScrollHandler);
+ this._typeTokenScrollHandler = this._createTypeTokenScrollEventHandler();
+ this.addScrollHandler(this._typeTokenScrollHandler);
+ }
+
+ _enableScrollEventsForControlFlowAnnotator()
+ {
+ console.assert(!this._controlFlowScrollHandler);
+ this._controlFlowScrollHandler = this._createControlFlowScrollEventHandler();
+ this.addScrollHandler(this._controlFlowScrollHandler);
+ }
+
+ _disableScrollEventsForTypeTokenAnnotator()
+ {
+ console.assert(this._typeTokenScrollHandler);
+ this.removeScrollHandler(this._typeTokenScrollHandler);
+ this._typeTokenScrollHandler = null;
+ }
+
+ _disableScrollEventsForControlFlowAnnotator()
+ {
+ console.assert(this._controlFlowScrollHandler);
+ this.removeScrollHandler(this._controlFlowScrollHandler);
+ this._controlFlowScrollHandler = null;
+ }
+
+ _createTypeTokenScrollEventHandler()
+ {
+ let timeoutIdentifier = null;
+ let scrollHandler = () => {
+ if (timeoutIdentifier)
+ clearTimeout(timeoutIdentifier);
+ else {
+ if (this._typeTokenAnnotator)
+ this._typeTokenAnnotator.pause();
+ }
+
+ timeoutIdentifier = setTimeout(() => {
+ timeoutIdentifier = null;
+ if (this._typeTokenAnnotator)
+ this._typeTokenAnnotator.resume();
+ }, WebInspector.SourceCodeTextEditor.DurationToUpdateTypeTokensAfterScrolling);
+ };
+
+ return scrollHandler;
+ }
+
+ _createControlFlowScrollEventHandler()
+ {
+ let timeoutIdentifier = null;
+ let scrollHandler = () => {
+ if (timeoutIdentifier)
+ clearTimeout(timeoutIdentifier);
+ else if (this._basicBlockAnnotator)
+ this._basicBlockAnnotator.pause();
+
+ timeoutIdentifier = setTimeout(() => {
+ timeoutIdentifier = null;
+ if (this._basicBlockAnnotator)
+ this._basicBlockAnnotator.resume();
+ }, WebInspector.SourceCodeTextEditor.DurationToUpdateTypeTokensAfterScrolling);
+ };
+
+ return scrollHandler;
+ }
+
+ _logCleared(event)
+ {
+ for (let lineNumber of this._issuesLineNumberMap.keys()) {
+ this.removeStyleClassFromLine(lineNumber, WebInspector.SourceCodeTextEditor.LineErrorStyleClassName);
+ this.removeStyleClassFromLine(lineNumber, WebInspector.SourceCodeTextEditor.LineWarningStyleClassName);
+ }
+
+ this._issuesLineNumberMap.clear();
+ this._clearIssueWidgets();
+ }
+};
+
+WebInspector.SourceCodeTextEditor.LineErrorStyleClassName = "error";
+WebInspector.SourceCodeTextEditor.LineWarningStyleClassName = "warning";
+WebInspector.SourceCodeTextEditor.PopoverDebuggerContentStyleClassName = "debugger-popover-content";
+WebInspector.SourceCodeTextEditor.HoveredExpressionHighlightStyleClassName = "hovered-expression-highlight";
+WebInspector.SourceCodeTextEditor.DurationToMouseOverTokenToMakeHoveredToken = 500;
+WebInspector.SourceCodeTextEditor.DurationToMouseOutOfHoveredTokenToRelease = 1000;
+WebInspector.SourceCodeTextEditor.DurationToUpdateTypeTokensAfterScrolling = 100;
+WebInspector.SourceCodeTextEditor.WidgetContainsMultipleIssuesSymbol = Symbol("source-code-widget-contains-multiple-issues");
+WebInspector.SourceCodeTextEditor.WidgetContainsMultipleThreadsSymbol = Symbol("source-code-widget-contains-multiple-threads");
+
+WebInspector.SourceCodeTextEditor.Event = {
+ ContentWillPopulate: "source-code-text-editor-content-will-populate",
+ ContentDidPopulate: "source-code-text-editor-content-did-populate"
+};