| /* |
| * 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 = function(sourceCode) |
| { |
| console.assert(sourceCode instanceof WebInspector.SourceCode); |
| |
| this._sourceCode = sourceCode; |
| this._breakpointMap = {}; |
| this._issuesLineNumberMap = new Map; |
| this._widgetMap = new Map; |
| this._contentPopulated = false; |
| this._invalidLineNumbers = {0: true}; |
| this._ignoreContentDidChange = 0; |
| |
| WebInspector.TextEditor.call(this, null, null, this); |
| |
| this._typeTokenScrollHandler = null; |
| this._typeTokenAnnotator = null; |
| this._basicBlockAnnotator = null; |
| |
| this._isProbablyMinified = 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(WebInspector.SourceCodeTextEditor.StyleClassName); |
| |
| 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.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.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)); |
| |
| // FIXME: Cmd+L shorcut doesn't actually work. |
| new WebInspector.KeyboardShortcut(WebInspector.KeyboardShortcut.Modifier.Command, "L", this.showGoToLineDialog.bind(this), this.element); |
| new WebInspector.KeyboardShortcut(WebInspector.KeyboardShortcut.Modifier.Control, "G", this.showGoToLineDialog.bind(this), this.element); |
| }; |
| |
| // FIXME: Move to a WebInspector.Object subclass and we can remove this. |
| WebInspector.Object.deprecatedAddConstructorFunctions(WebInspector.SourceCodeTextEditor); |
| |
| WebInspector.SourceCodeTextEditor.StyleClassName = "source-code"; |
| 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.AutoFormatMinimumLineLength = 500; |
| WebInspector.SourceCodeTextEditor.WidgetContainsMultipleIssuesSymbol = Symbol("source-code-widget-contains-multiple-issues"); |
| |
| WebInspector.SourceCodeTextEditor.Event = { |
| ContentWillPopulate: "source-code-text-editor-content-will-populate", |
| ContentDidPopulate: "source-code-text-editor-content-did-populate" |
| }; |
| |
| WebInspector.SourceCodeTextEditor.prototype = { |
| constructor: WebInspector.SourceCodeTextEditor, |
| |
| // Public |
| |
| get sourceCode() |
| { |
| return this._sourceCode; |
| }, |
| |
| shown: function() |
| { |
| WebInspector.TextEditor.prototype.shown.call(this); |
| |
| if (WebInspector.showJavaScriptTypeInformationSetting.value) { |
| if (this._typeTokenAnnotator) |
| this._typeTokenAnnotator.resume(); |
| if (this._basicBlockAnnotator) |
| this._basicBlockAnnotator.resume(); |
| if (!this._typeTokenScrollHandler && (this._typeTokenAnnotator || this._basicBlockAnnotator)) |
| this._enableScrollEventsForTypeTokenAnnotator(); |
| } else { |
| if (this._typeTokenAnnotator || this._basicBlockAnnotator) |
| this._setTypeTokenAnnotatorEnabledState(false); |
| } |
| }, |
| |
| hidden: function() |
| { |
| WebInspector.TextEditor.prototype.hidden.call(this); |
| |
| this.tokenTrackingController.removeHighlightedRange(); |
| |
| this._dismissPopover(); |
| |
| this._dismissEditingController(true); |
| |
| if (this._typeTokenAnnotator) |
| this._typeTokenAnnotator.pause(); |
| if (this._basicBlockAnnotator) |
| this._basicBlockAnnotator.pause(); |
| }, |
| |
| close: function() |
| { |
| if (this._supportsDebugging) { |
| WebInspector.Breakpoint.removeEventListener(WebInspector.Breakpoint.Event.DisabledStateDidChange, this._breakpointStatusDidChange, this); |
| WebInspector.Breakpoint.removeEventListener(WebInspector.Breakpoint.Event.AutoContinueDidChange, this._breakpointStatusDidChange, this); |
| WebInspector.Breakpoint.removeEventListener(WebInspector.Breakpoint.Event.ResolvedStateDidChange, this._breakpointStatusDidChange, this); |
| WebInspector.Breakpoint.removeEventListener(WebInspector.Breakpoint.Event.LocationDidChange, this._updateBreakpointLocation, this); |
| |
| WebInspector.debuggerManager.removeEventListener(WebInspector.DebuggerManager.Event.BreakpointsEnabledDidChange, this._breakpointsEnabledDidChange, this); |
| WebInspector.debuggerManager.removeEventListener(WebInspector.DebuggerManager.Event.BreakpointAdded, this._breakpointAdded, this); |
| WebInspector.debuggerManager.removeEventListener(WebInspector.DebuggerManager.Event.BreakpointRemoved, this._breakpointRemoved, this); |
| WebInspector.debuggerManager.removeEventListener(WebInspector.DebuggerManager.Event.ActiveCallFrameDidChange, this._activeCallFrameDidChange, this); |
| |
| if (this._activeCallFrameSourceCodeLocation) { |
| this._activeCallFrameSourceCodeLocation.removeEventListener(WebInspector.SourceCodeLocation.Event.LocationChanged, this._activeCallFrameSourceCodeLocationChanged, this); |
| delete this._activeCallFrameSourceCodeLocation; |
| } |
| } |
| |
| WebInspector.issueManager.removeEventListener(WebInspector.IssueManager.Event.IssueWasAdded, this._issueWasAdded, this); |
| |
| WebInspector.notifications.removeEventListener(WebInspector.Notification.GlobalModifierKeysDidChange, this._updateTokenTrackingControllerState, this); |
| this._sourceCode.removeEventListener(WebInspector.SourceCode.Event.SourceMapAdded, this._sourceCodeSourceMapAdded, this); |
| }, |
| |
| canBeFormatted: function() |
| { |
| // 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 WebInspector.TextEditor.prototype.canBeFormatted.call(this); |
| }, |
| |
| canShowTypeAnnotations: function() |
| { |
| return !!this._typeTokenAnnotator; |
| }, |
| |
| customPerformSearch: function(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); |
| |
| // 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) |
| DebuggerAgent.searchInContent(this._sourceCode.id, query, false, false, searchResultCallback.bind(this)); |
| return true; |
| }, |
| |
| showGoToLineDialog: function() |
| { |
| if (!this._goToLineDialog) { |
| this._goToLineDialog = new WebInspector.GoToLineDialog; |
| this._goToLineDialog.delegate = this; |
| } |
| |
| this._goToLineDialog.present(this.element); |
| }, |
| |
| isGoToLineDialogValueValid: function(goToLineDialog, lineNumber) |
| { |
| return !isNaN(lineNumber) && lineNumber > 0 && lineNumber <= this.lineCount; |
| }, |
| |
| goToLineDialogValueWasValidated: function(goToLineDialog, lineNumber) |
| { |
| var position = new WebInspector.SourceCodePosition(lineNumber - 1, 0); |
| var range = new WebInspector.TextRange(lineNumber - 1, 0, lineNumber, 0); |
| this.revealPosition(position, range, false, true); |
| }, |
| |
| goToLineDialogWasDismissed: function() |
| { |
| this.focus(); |
| }, |
| |
| contentDidChange: function(replacedRanges, newRanges) |
| { |
| WebInspector.TextEditor.prototype.contentDidChange.call(this, replacedRanges, newRanges); |
| |
| if (this._ignoreContentDidChange > 0) |
| return; |
| |
| for (var range of newRanges) |
| this._updateEditableMarkers(range); |
| |
| if (this._typeTokenAnnotator || this._basicBlockAnnotator) { |
| this._setTypeTokenAnnotatorEnabledState(false); |
| this._typeTokenAnnotator = null; |
| this._basicBlockAnnotator = null; |
| } |
| }, |
| |
| toggleTypeAnnotations: function() |
| { |
| if (!this._typeTokenAnnotator) |
| return false; |
| |
| var newActivatedState = !this._typeTokenAnnotator.isActive(); |
| if (newActivatedState && this._isProbablyMinified && !this.formatted) |
| this.formatted = true; |
| |
| this._setTypeTokenAnnotatorEnabledState(newActivatedState); |
| return newActivatedState; |
| }, |
| |
| showPopoverForTypes: function(types, 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 section = new WebInspector.TypePropertiesSection(types); |
| section.expanded = true; |
| section.element.classList.add("body"); |
| content.appendChild(section.element); |
| |
| this._showPopover(content, bounds); |
| }, |
| |
| // Protected |
| |
| prettyPrint: function(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 shouldResumeTypeTokenAnnotator = this._typeTokenAnnotator && this._typeTokenAnnotator.isActive(); |
| var shouldResumeBasicBlockAnnotator = this._basicBlockAnnotator && this._basicBlockAnnotator.isActive(); |
| if (shouldResumeTypeTokenAnnotator || shouldResumeBasicBlockAnnotator) |
| this._setTypeTokenAnnotatorEnabledState(false); |
| |
| WebInspector.TextEditor.prototype.prettyPrint.call(this, pretty); |
| |
| if (pretty || !this._isProbablyMinified) { |
| if (shouldResumeTypeTokenAnnotator || shouldResumeBasicBlockAnnotator) |
| this._setTypeTokenAnnotatorEnabledState(true); |
| } else { |
| console.assert(!pretty && this._isProbablyMinified); |
| if (this._typeTokenAnnotator || this._basicBlockAnnotator) |
| this._setTypeTokenAnnotatorEnabledState(false); |
| } |
| }, |
| |
| // Private |
| |
| _unformattedLineInfoForEditorLineInfo: function(lineInfo) |
| { |
| if (this.formatterSourceMap) |
| return this.formatterSourceMap.formattedToOriginal(lineInfo.lineNumber, lineInfo.columnNumber); |
| return lineInfo; |
| }, |
| |
| _sourceCodeLocationForEditorPosition: function(position) |
| { |
| var lineInfo = {lineNumber: position.line, columnNumber: position.ch}; |
| var unformattedLineInfo = this._unformattedLineInfoForEditorLineInfo(lineInfo); |
| return this.sourceCode.createSourceCodeLocation(unformattedLineInfo.lineNumber, unformattedLineInfo.columnNumber); |
| }, |
| |
| _editorLineInfoForSourceCodeLocation: function(sourceCodeLocation) |
| { |
| if (this._sourceCode instanceof WebInspector.SourceMapResource) |
| return {lineNumber: sourceCodeLocation.displayLineNumber, columnNumber: sourceCodeLocation.displayColumnNumber}; |
| return {lineNumber: sourceCodeLocation.formattedLineNumber, columnNumber: sourceCodeLocation.formattedColumnNumber}; |
| }, |
| |
| _breakpointForEditorLineInfo: function(lineInfo) |
| { |
| if (!this._breakpointMap[lineInfo.lineNumber]) |
| return null; |
| return this._breakpointMap[lineInfo.lineNumber][lineInfo.columnNumber]; |
| }, |
| |
| _addBreakpointWithEditorLineInfo: function(breakpoint, lineInfo) |
| { |
| if (!this._breakpointMap[lineInfo.lineNumber]) |
| this._breakpointMap[lineInfo.lineNumber] = {}; |
| |
| this._breakpointMap[lineInfo.lineNumber][lineInfo.columnNumber] = breakpoint; |
| }, |
| |
| _removeBreakpointWithEditorLineInfo: function(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]; |
| }, |
| |
| _contentWillPopulate: function(content) |
| { |
| this.dispatchEventToListeners(WebInspector.SourceCodeTextEditor.Event.ContentWillPopulate); |
| |
| // We only do the rest of 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"; |
| |
| // Automatically format the content if it looks minified and it can be formatted. |
| console.assert(!this.formatted); |
| if (this.canBeFormatted()) { |
| var lastNewlineIndex = 0; |
| while (true) { |
| var nextNewlineIndex = content.indexOf("\n", lastNewlineIndex); |
| if (nextNewlineIndex === -1) { |
| if (content.length - lastNewlineIndex > WebInspector.SourceCodeTextEditor.AutoFormatMinimumLineLength) { |
| this.autoFormat = true; |
| this._isProbablyMinified = true; |
| } |
| break; |
| } |
| |
| if (nextNewlineIndex - lastNewlineIndex > WebInspector.SourceCodeTextEditor.AutoFormatMinimumLineLength) { |
| this.autoFormat = true; |
| this._isProbablyMinified = true; |
| break; |
| } |
| |
| lastNewlineIndex = nextNewlineIndex + 1; |
| } |
| } |
| }, |
| |
| _contentDidPopulate: function() |
| { |
| 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._updateEditableMarkers(); |
| }, |
| |
| _populateWithContent: function(content) |
| { |
| content = content || ""; |
| |
| this._contentWillPopulate(content); |
| this.string = content; |
| |
| this._makeTypeTokenAnnotator(); |
| this._makeBasicBlockAnnotator(); |
| |
| if (WebInspector.showJavaScriptTypeInformationSetting.value) { |
| if (this._basicBlockAnnotator || this._typeTokenAnnotator) |
| this._setTypeTokenAnnotatorEnabledState(true); |
| } |
| |
| this._contentDidPopulate(); |
| }, |
| |
| _contentAvailable: function(parameters) |
| { |
| // Return if resource is not available. |
| if (parameters.error) |
| return; |
| |
| var sourceCode = parameters.sourceCode; |
| var content = parameters.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: function(event) |
| { |
| this._updateBreakpointStatus(event.target); |
| }, |
| |
| _breakpointsEnabledDidChange: function() |
| { |
| console.assert(this._supportsDebugging); |
| |
| var breakpoints = WebInspector.debuggerManager.breakpointsForSourceCode(this._sourceCode); |
| for (var breakpoint of breakpoints) |
| this._updateBreakpointStatus(breakpoint); |
| }, |
| |
| _updateBreakpointStatus: function(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: function(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: function(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: function(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); |
| }, |
| |
| _activeCallFrameDidChange: function() |
| { |
| console.assert(this._supportsDebugging); |
| |
| if (this._activeCallFrameSourceCodeLocation) { |
| this._activeCallFrameSourceCodeLocation.removeEventListener(WebInspector.SourceCodeLocation.Event.LocationChanged, this._activeCallFrameSourceCodeLocationChanged, this); |
| delete this._activeCallFrameSourceCodeLocation; |
| } |
| |
| var activeCallFrame = WebInspector.debuggerManager.activeCallFrame; |
| if (!activeCallFrame || !this._matchesSourceCodeLocation(activeCallFrame.sourceCodeLocation)) { |
| this.executionLineNumber = NaN; |
| this.executionColumnNumber = 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). |
| |
| var lineInfo = this._editorLineInfoForSourceCodeLocation(activeCallFrame.sourceCodeLocation); |
| this.executionLineNumber = lineInfo.lineNumber; |
| this.executionColumnNumber = 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: function(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.executionLineNumber = lineInfo.lineNumber; |
| this.executionColumnNumber = lineInfo.columnNumber; |
| }, |
| |
| _populateWithInlineScriptContent: function() |
| { |
| 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; |
| |
| delete this._requestingScriptContent; |
| |
| // Abort if the full content populated while waiting for these async callbacks. |
| if (this._fullContentPopulated) |
| return; |
| |
| const scriptOpenTag = "<script>"; |
| const 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: function() |
| { |
| 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; |
| delete this._requestingScriptContent; |
| |
| // 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)); |
| }, |
| |
| _matchesSourceCodeLocation: function(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: function(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.url === this._sourceCode.url; |
| if (this._sourceCode instanceof WebInspector.Script) |
| return breakpoint.url === this._sourceCode.url || breakpoint.scriptIdentifier === this._sourceCode.id; |
| return false; |
| }, |
| |
| _matchesIssue: function(issue) |
| { |
| if (this._sourceCode instanceof WebInspector.Resource) |
| return issue.url === this._sourceCode.url; |
| // FIXME: Support issues for Scripts based on id, not only by URL. |
| if (this._sourceCode instanceof WebInspector.Script) |
| return issue.url === this._sourceCode.url; |
| return false; |
| }, |
| |
| _issueWasAdded: function(event) |
| { |
| var issue = event.data.issue; |
| if (!this._matchesIssue(issue)) |
| return; |
| |
| this._addIssue(issue); |
| }, |
| |
| _addIssue: function(issue) |
| { |
| // FIXME: Issue should have a SourceCodeLocation. |
| var sourceCodeLocation = this._sourceCode.createSourceCodeLocation(issue.lineNumber, issue.columnNumber); |
| 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.columnNumber === issue.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: function(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("issue-widget"); |
| widgetElement.classList.add("inline"); |
| widgetElement.addEventListener("click", this._handleWidgetClick.bind(this, widget, lineNumber)); |
| |
| this._widgetMap.set(lineNumber, widget); |
| |
| return widget; |
| }, |
| |
| _iconClassNameForIssueLevel: function(level) |
| { |
| if (level === WebInspector.IssueMessage.Level.Warning) |
| return "icon-warning"; |
| |
| console.assert(level === WebInspector.IssueMessage.Level.Error); |
| return "icon-error"; |
| }, |
| |
| _updateIssueWidgetForIssues: function(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: function(widget) |
| { |
| if (widget[WebInspector.SourceCodeTextEditor.WidgetContainsMultipleIssuesSymbol]) |
| 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: function(widget, lineNumber, event) |
| { |
| if (!this._isWidgetToggleable(widget)) |
| return; |
| |
| widget.widgetElement.classList.toggle("inline"); |
| |
| var lineNumberIssues = this._issuesLineNumberMap.get(lineNumber); |
| this._updateIssueWidgetForIssues(widget, lineNumberIssues); |
| }, |
| |
| _breakpointInfoForBreakpoint: function(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: function(textEditor) |
| { |
| return this._sourceCode.url; |
| }, |
| |
| textEditorShouldHideLineNumber: function(textEditor, lineNumber) |
| { |
| return lineNumber in this._invalidLineNumbers; |
| }, |
| |
| textEditorGutterContextMenu: function(textEditor, lineNumber, columnNumber, editorBreakpoints, event) |
| { |
| if (!this._supportsDebugging) |
| return; |
| |
| event.preventDefault(); |
| |
| var contextMenu = new WebInspector.ContextMenu(event); |
| |
| // Paused. Add Continue to Here option only if we have a script identifier for the location. |
| if (WebInspector.debuggerManager.paused) { |
| var editorLineInfo = {lineNumber, columnNumber}; |
| var unformattedLineInfo = this._unformattedLineInfoForEditorLineInfo(editorLineInfo); |
| var sourceCodeLocation = this._sourceCode.createSourceCodeLocation(unformattedLineInfo.lineNumber, unformattedLineInfo.columnNumber); |
| |
| if (sourceCodeLocation.sourceCode instanceof WebInspector.Script) |
| var script = sourceCodeLocation.sourceCode; |
| else if (sourceCodeLocation.sourceCode instanceof WebInspector.Resource) |
| var script = sourceCodeLocation.sourceCode.scriptForLocation(sourceCodeLocation); |
| |
| if (script) { |
| function continueToLocation() |
| { |
| WebInspector.debuggerManager.continueToLocation(script.id, sourceCodeLocation.lineNumber, sourceCodeLocation.columnNumber); |
| } |
| |
| contextMenu.appendItem(WebInspector.UIString("Continue to Here"), continueToLocation); |
| contextMenu.appendSeparator(); |
| } |
| } |
| |
| var breakpoints = []; |
| for (var i = 0; i < editorBreakpoints.length; ++i) { |
| var lineInfo = editorBreakpoints[i]; |
| var breakpoint = this._breakpointForEditorLineInfo(lineInfo); |
| console.assert(breakpoint); |
| if (breakpoint) |
| breakpoints.push(breakpoint); |
| } |
| |
| // No breakpoints. |
| if (!breakpoints.length) { |
| function addBreakpoint() |
| { |
| var data = this.textEditorBreakpointAdded(this, lineNumber, columnNumber); |
| this.setBreakpointInfoForLineAndColumn(data.lineNumber, data.columnNumber, data.breakpointInfo); |
| } |
| |
| contextMenu.appendItem(WebInspector.UIString("Add Breakpoint"), addBreakpoint.bind(this)); |
| contextMenu.show(); |
| return; |
| } |
| |
| // Single breakpoint. |
| if (breakpoints.length === 1) { |
| var breakpoint = breakpoints[0]; |
| function revealInSidebar() |
| { |
| WebInspector.debuggerSidebarPanel.show(); |
| var treeElement = WebInspector.debuggerSidebarPanel.treeElementForRepresentedObject(breakpoint); |
| if (treeElement) |
| treeElement.revealAndSelect(); |
| } |
| |
| breakpoint.appendContextMenuItems(contextMenu, event.target); |
| contextMenu.appendSeparator(); |
| contextMenu.appendItem(WebInspector.UIString("Reveal in Debugger Navigation Sidebar"), revealInSidebar); |
| contextMenu.show(); |
| return; |
| } |
| |
| // Multiple breakpoints. |
| var shouldDisable = false; |
| for (var i = 0; i < breakpoints.length; ++i) { |
| if (!breakpoints[i].disabled) { |
| shouldDisable = true; |
| break; |
| } |
| } |
| |
| function removeBreakpoints() |
| { |
| for (var i = 0; i < breakpoints.length; ++i) { |
| var breakpoint = breakpoints[i]; |
| if (WebInspector.debuggerManager.isBreakpointRemovable(breakpoint)) |
| WebInspector.debuggerManager.removeBreakpoint(breakpoint); |
| } |
| } |
| |
| function toggleBreakpoints() |
| { |
| for (var i = 0; i < breakpoints.length; ++i) |
| breakpoints[i].disabled = shouldDisable; |
| } |
| |
| if (shouldDisable) |
| contextMenu.appendItem(WebInspector.UIString("Disable Breakpoints"), toggleBreakpoints.bind(this)); |
| else |
| contextMenu.appendItem(WebInspector.UIString("Enable Breakpoints"), toggleBreakpoints.bind(this)); |
| contextMenu.appendItem(WebInspector.UIString("Delete Breakpoints"), removeBreakpoints.bind(this)); |
| contextMenu.show(); |
| }, |
| |
| textEditorBreakpointAdded: function(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; |
| |
| var shouldSkipEventDispatch = false; |
| var shouldSpeculativelyResolveBreakpoint = true; |
| WebInspector.debuggerManager.addBreakpoint(breakpoint, shouldSkipEventDispatch, shouldSpeculativelyResolveBreakpoint); |
| delete this._ignoreBreakpointAddedBreakpoint; |
| |
| // Return the more accurate location and breakpoint info. |
| return { |
| breakpointInfo: this._breakpointInfoForBreakpoint(breakpoint), |
| lineNumber: lineInfo.lineNumber, |
| columnNumber: lineInfo.columnNumber |
| }; |
| }, |
| |
| textEditorBreakpointRemoved: function(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); |
| delete this._ignoreBreakpointAddedBreakpoint; |
| }, |
| |
| textEditorBreakpointMoved: function(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); |
| delete this._ignoreLocationUpdateBreakpoint; |
| |
| 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: function(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: function(textEditor) |
| { |
| this._ignoreAllBreakpointLocationUpdates = true; |
| this._sourceCode.formatterSourceMap = this.formatterSourceMap; |
| delete this._ignoreAllBreakpointLocationUpdates; |
| |
| // 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(); |
| }, |
| |
| _clearWidgets: function() |
| { |
| for (var widget of this._widgetMap.values()) |
| widget.clear(); |
| |
| this._widgetMap.clear(); |
| }, |
| |
| _reinsertAllIssues: function() |
| { |
| this._issuesLineNumberMap.clear(); |
| this._clearWidgets(); |
| |
| var issues = WebInspector.issueManager.issuesForSourceCode(this._sourceCode); |
| for (var issue of issues) { |
| console.assert(this._matchesIssue(issue)); |
| this._addIssue(issue); |
| } |
| }, |
| |
| _debuggerDidPause: function(event) |
| { |
| this._updateTokenTrackingControllerState(); |
| if (this._typeTokenAnnotator && this._typeTokenAnnotator.isActive()) |
| this._typeTokenAnnotator.refresh(); |
| if (this._basicBlockAnnotator && this._basicBlockAnnotator.isActive()) |
| this._basicBlockAnnotator.refresh(); |
| }, |
| |
| _debuggerDidResume: function(event) |
| { |
| this._updateTokenTrackingControllerState(); |
| this._dismissPopover(); |
| if (this._typeTokenAnnotator && this._typeTokenAnnotator.isActive()) |
| this._typeTokenAnnotator.refresh(); |
| if (this._basicBlockAnnotator && this._basicBlockAnnotator.isActive()) |
| this._basicBlockAnnotator.refresh(); |
| }, |
| |
| _sourceCodeSourceMapAdded: function(event) |
| { |
| WebInspector.notifications.addEventListener(WebInspector.Notification.GlobalModifierKeysDidChange, this._updateTokenTrackingControllerState, this); |
| this._sourceCode.removeEventListener(WebInspector.SourceCode.Event.SourceMapAdded, this._sourceCodeSourceMapAdded, this); |
| |
| this._updateTokenTrackingControllerState(); |
| }, |
| |
| _updateTokenTrackingControllerState: function() |
| { |
| 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: function() |
| { |
| for (var marker of this.markers) { |
| if (marker.type === WebInspector.TextMarker.Type.Color) |
| return true; |
| } |
| return false; |
| }, |
| |
| // CodeMirrorTokenTrackingController Delegate |
| |
| tokenTrackingControllerCanReleaseHighlightedRange: function(tokenTrackingController, element) |
| { |
| if (!this._popover) |
| return true; |
| |
| if (!window.getSelection().isCollapsed && this._popover.element.contains(window.getSelection().anchorNode)) |
| return false; |
| |
| return true; |
| }, |
| |
| tokenTrackingControllerHighlightedRangeReleased: function(tokenTrackingController) |
| { |
| if (!this._mouseIsOverPopover) |
| this._dismissPopover(); |
| }, |
| |
| tokenTrackingControllerHighlightedRangeWasClicked: function(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.resourceSidebarPanel.showOriginalOrFormattedSourceCodeLocation(sourceCodeLocation); |
| else |
| WebInspector.resourceSidebarPanel.showSourceCodeLocation(sourceCodeLocation); |
| }, |
| |
| tokenTrackingControllerNewHighlightCandidate: function(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: function(tokenTrackingController, hoveredMarker) |
| { |
| this._dismissEditingController(); |
| }, |
| |
| _tokenTrackingControllerHighlightedJavaScriptExpression: function(candidate) |
| { |
| console.assert(candidate.expression); |
| |
| function populate(error, result, wasThrown) |
| { |
| if (error || wasThrown) |
| return; |
| |
| if (candidate !== this.tokenTrackingController.candidate) |
| return; |
| |
| var data = WebInspector.RemoteObject.fromPayload(result); |
| 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; |
| } |
| } |
| |
| if (WebInspector.debuggerManager.activeCallFrame) { |
| DebuggerAgent.evaluateOnCallFrame.invoke({callFrameId: WebInspector.debuggerManager.activeCallFrame.id, expression: candidate.expression, objectGroup: "popover", doNotPauseOnExceptionsAndMuteConsole: true}, populate.bind(this)); |
| return; |
| } |
| |
| // No call frame available. Use the main page's context. |
| RuntimeAgent.evaluate.invoke({expression: candidate.expression, objectGroup: "popover", doNotPauseOnExceptionsAndMuteConsole: true}, populate.bind(this)); |
| }, |
| |
| _tokenTrackingControllerHighlightedJavaScriptTypeInformation: function(candidate) |
| { |
| console.assert(candidate.expression); |
| |
| var sourceCode = this._sourceCode; |
| var sourceID = sourceCode.scripts[0].id; |
| var range = candidate.hoveredTokenRange; |
| var offset = this.currentPositionToOriginalOffset({line: range.start.line, ch: range.start.ch}); |
| |
| 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 types = allTypes[0]; |
| if (types.isValid) { |
| var popoverTitle = WebInspector.TypeTokenView.titleForPopover(WebInspector.TypeTokenView.TitleType.Variable, candidate.expression); |
| this.showPopoverForTypes(types, null, popoverTitle); |
| } |
| } |
| |
| RuntimeAgent.getRuntimeTypesForVariablesAtOffsets(allRequests, handler.bind(this)); |
| }, |
| |
| _showPopover: function(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: function(data) |
| { |
| var 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; |
| |
| var wrapper = document.createElement("div"); |
| wrapper.className = "body formatted-function"; |
| wrapper.textContent = data.description; |
| |
| var content = document.createElement("div"); |
| content.className = "function"; |
| |
| var title = content.appendChild(document.createElement("div")); |
| title.className = "title"; |
| title.textContent = response.name || response.inferredName || response.displayName || WebInspector.UIString("(anonymous function)"); |
| |
| content.appendChild(wrapper); |
| |
| this._showPopover(content); |
| } |
| DebuggerAgent.getFunctionDetails(data.objectId, didGetDetails.bind(this)); |
| }, |
| |
| _showPopoverForObject: function(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); |
| |
| // FIXME: If this is a variable, it would be nice to put the variable name in the PropertyPath. |
| var objectTree = new WebInspector.ObjectTreeView(data, WebInspector.ObjectTreeView.Mode.Properties, null); |
| objectTree.showOnlyProperties(); |
| objectTree.expand(); |
| |
| var bodyElement = content.appendChild(document.createElement("div")); |
| bodyElement.className = "body"; |
| bodyElement.appendChild(objectTree.element); |
| |
| this._showPopover(content); |
| }, |
| |
| _showPopoverWithFormattedValue: function(remoteObject) |
| { |
| var content = WebInspector.FormattedValue.createElementForRemoteObject(remoteObject); |
| this._showPopover(content); |
| }, |
| |
| willDismissPopover: function(popover) |
| { |
| this.tokenTrackingController.removeHighlightedRange(); |
| |
| RuntimeAgent.releaseObjectGroup("popover"); |
| }, |
| |
| _dismissPopover: function() |
| { |
| if (!this._popover) |
| return; |
| |
| this._popover.dismiss(); |
| |
| if (this._popoverEventListeners && this._popoverEventListenersAreRegistered) { |
| this._popoverEventListenersAreRegistered = false; |
| this._popoverEventListeners.unregister(); |
| } |
| }, |
| |
| _trackPopoverEvents: function() |
| { |
| 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: function(event) |
| { |
| this._mouseIsOverPopover = true; |
| }, |
| |
| _popoverMouseout: function(event) |
| { |
| this._mouseIsOverPopover = this._popover.element.contains(event.relatedTarget); |
| }, |
| |
| _updateEditableMarkers: function(range) |
| { |
| this.createColorMarkers(range); |
| this.createGradientMarkers(range); |
| |
| this._updateTokenTrackingControllerState(); |
| }, |
| |
| _tokenTrackingControllerHighlightedMarkedExpression: function(candidate, markers) |
| { |
| // Look for the outermost editable marker. |
| var editableMarker; |
| for (var marker of markers) { |
| if (!marker.range || (marker.type !== WebInspector.TextMarker.Type.Color && marker.type !== WebInspector.TextMarker.Type.Gradient)) |
| 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(); |
| delete this._editingController; |
| return; |
| } |
| } |
| |
| this._editingController.delegate = this; |
| this._editingController.presentHoverMenu(); |
| }, |
| |
| _dismissEditingController: function(discrete) |
| { |
| if (this._editingController) |
| this._editingController.dismissHoverMenu(discrete); |
| |
| this.tokenTrackingController.hoveredMarker = null; |
| delete this._editingController; |
| }, |
| |
| // CodeMirrorEditingController Delegate |
| |
| editingControllerDidStartEditing: function(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: function(editingController) |
| { |
| this._updateEditableMarkers(editingController.range); |
| |
| this._ignoreContentDidChange--; |
| |
| delete this._editingController; |
| }, |
| |
| _setTypeTokenAnnotatorEnabledState: function(shouldActivate) |
| { |
| console.assert(this._typeTokenAnnotator); |
| if (!this._typeTokenAnnotator) |
| return; |
| |
| if (shouldActivate) { |
| RuntimeAgent.enableTypeProfiler(); |
| |
| this._typeTokenAnnotator.reset(); |
| if (this._basicBlockAnnotator) { |
| console.assert(!this._basicBlockAnnotator.isActive()); |
| this._basicBlockAnnotator.reset(); |
| } |
| |
| if (!this._typeTokenScrollHandler) |
| this._enableScrollEventsForTypeTokenAnnotator(); |
| } else { |
| // Because we disable type profiling when exiting the inspector, there is no need to call |
| // RuntimeAgent.disableTypeProfiler() here. If we were to call it here, JavaScriptCore would |
| // compile out all the necessary type profiling information, so if a user were to quickly press then |
| // unpress the type profiling button, we wouldn't be able to re-show type information which would |
| // provide a confusing user experience. |
| |
| this._typeTokenAnnotator.clear(); |
| if (this._basicBlockAnnotator) |
| this._basicBlockAnnotator.clear(); |
| |
| if (this._typeTokenScrollHandler) |
| this._disableScrollEventsForTypeTokenAnnotator(); |
| } |
| |
| WebInspector.showJavaScriptTypeInformationSetting.value = shouldActivate; |
| |
| this._updateTokenTrackingControllerState(); |
| }, |
| |
| _getAssociatedScript: function() |
| { |
| var script = null; |
| // FIXME: This needs to me modified to work with HTML files with inline script tags. |
| if (this._sourceCode instanceof WebInspector.Script) |
| script = this._sourceCode; |
| else if (this._sourceCode instanceof WebInspector.Resource && this._sourceCode.type === WebInspector.Resource.Type.Script && this._sourceCode.scripts.length) |
| script = this._sourceCode.scripts[0]; |
| return script; |
| }, |
| |
| _makeTypeTokenAnnotator: function() |
| { |
| if (!RuntimeAgent.getRuntimeTypesForVariablesAtOffsets) |
| return; |
| |
| var script = this._getAssociatedScript(); |
| if (!script) |
| return; |
| |
| this._typeTokenAnnotator = new WebInspector.TypeTokenAnnotator(this, script); |
| }, |
| |
| _makeBasicBlockAnnotator: function() |
| { |
| if (!RuntimeAgent.getBasicBlocks) |
| return; |
| |
| var script = this._getAssociatedScript(); |
| if (!script) |
| return; |
| |
| this._basicBlockAnnotator = new WebInspector.BasicBlockAnnotator(this, script); |
| }, |
| |
| _enableScrollEventsForTypeTokenAnnotator: function() |
| { |
| // Pause updating type tokens while scrolling to prevent frame loss. |
| console.assert(!this._typeTokenScrollHandler); |
| this._typeTokenScrollHandler = this._makeTypeTokenScrollEventHandler(); |
| this.addScrollHandler(this._typeTokenScrollHandler); |
| }, |
| |
| _disableScrollEventsForTypeTokenAnnotator: function() |
| { |
| console.assert(this._typeTokenScrollHandler); |
| this.removeScrollHandler(this._typeTokenScrollHandler); |
| this._typeTokenScrollHandler = null; |
| }, |
| |
| _makeTypeTokenScrollEventHandler: function() |
| { |
| var timeoutIdentifier = null; |
| function scrollHandler() |
| { |
| if (timeoutIdentifier) |
| clearTimeout(timeoutIdentifier); |
| else { |
| if (this._typeTokenAnnotator) |
| this._typeTokenAnnotator.pause(); |
| if (this._basicBlockAnnotator) |
| this._basicBlockAnnotator.pause(); |
| } |
| |
| timeoutIdentifier = setTimeout(function() { |
| timeoutIdentifier = null; |
| if (this._typeTokenAnnotator) |
| this._typeTokenAnnotator.resume(); |
| if (this._basicBlockAnnotator) |
| this._basicBlockAnnotator.resume(); |
| }.bind(this), WebInspector.SourceCodeTextEditor.DurationToUpdateTypeTokensAfterScrolling); |
| } |
| |
| return scrollHandler.bind(this); |
| } |
| }; |
| |
| WebInspector.SourceCodeTextEditor.prototype.__proto__ = WebInspector.TextEditor.prototype; |