Web Inspector: Improve debugger highlight in some exception cases
[WebKit-https.git] / Source / WebInspectorUI / UserInterface / Views / SourceCodeTextEditor.js
1 /*
2  * Copyright (C) 2013, 2015 Apple Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23  * THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 WebInspector.SourceCodeTextEditor = class SourceCodeTextEditor extends WebInspector.TextEditor
27 {
28     constructor(sourceCode)
29     {
30         console.assert(sourceCode instanceof WebInspector.SourceCode);
31
32         super();
33
34         this.delegate = this;
35
36         this._sourceCode = sourceCode;
37         this._breakpointMap = {};
38         this._issuesLineNumberMap = new Map;
39         this._widgetMap = new Map;
40         this._contentPopulated = false;
41         this._invalidLineNumbers = {0: true};
42         this._requestingScriptContent = false;
43         this._activeCallFrameSourceCodeLocation = null;
44
45         this._typeTokenScrollHandler = null;
46         this._typeTokenAnnotator = null;
47         this._basicBlockAnnotator = null;
48         this._editingController = null;
49
50         this._autoFormat = false;
51         this._isProbablyMinified = false;
52
53         this._ignoreContentDidChange = 0;
54         this._ignoreLocationUpdateBreakpoint = null;
55         this._ignoreBreakpointAddedBreakpoint = null;
56         this._ignoreBreakpointRemovedBreakpoint = null;
57         this._ignoreAllBreakpointLocationUpdates = false;
58
59         // FIXME: Currently this just jumps between resources and related source map resources. It doesn't "jump to symbol" yet.
60         this._updateTokenTrackingControllerState();
61
62         this.element.classList.add("source-code");
63
64         if (this._supportsDebugging) {
65             WebInspector.Breakpoint.addEventListener(WebInspector.Breakpoint.Event.DisabledStateDidChange, this._breakpointStatusDidChange, this);
66             WebInspector.Breakpoint.addEventListener(WebInspector.Breakpoint.Event.AutoContinueDidChange, this._breakpointStatusDidChange, this);
67             WebInspector.Breakpoint.addEventListener(WebInspector.Breakpoint.Event.ResolvedStateDidChange, this._breakpointStatusDidChange, this);
68             WebInspector.Breakpoint.addEventListener(WebInspector.Breakpoint.Event.LocationDidChange, this._updateBreakpointLocation, this);
69
70             WebInspector.debuggerManager.addEventListener(WebInspector.DebuggerManager.Event.BreakpointsEnabledDidChange, this._breakpointsEnabledDidChange, this);
71             WebInspector.debuggerManager.addEventListener(WebInspector.DebuggerManager.Event.BreakpointAdded, this._breakpointAdded, this);
72             WebInspector.debuggerManager.addEventListener(WebInspector.DebuggerManager.Event.BreakpointRemoved, this._breakpointRemoved, this);
73             WebInspector.debuggerManager.addEventListener(WebInspector.DebuggerManager.Event.ActiveCallFrameDidChange, this._activeCallFrameDidChange, this);
74
75             WebInspector.debuggerManager.addEventListener(WebInspector.DebuggerManager.Event.Paused, this._debuggerDidPause, this);
76             WebInspector.debuggerManager.addEventListener(WebInspector.DebuggerManager.Event.Resumed, this._debuggerDidResume, this);
77             if (WebInspector.debuggerManager.activeCallFrame)
78                 this._debuggerDidPause();
79
80             this._activeCallFrameDidChange();
81         }
82
83         WebInspector.issueManager.addEventListener(WebInspector.IssueManager.Event.IssueWasAdded, this._issueWasAdded, this);
84
85         if (this._sourceCode instanceof WebInspector.SourceMapResource || this._sourceCode.sourceMaps.length > 0)
86             WebInspector.notifications.addEventListener(WebInspector.Notification.GlobalModifierKeysDidChange, this._updateTokenTrackingControllerState, this);
87         else
88             this._sourceCode.addEventListener(WebInspector.SourceCode.Event.SourceMapAdded, this._sourceCodeSourceMapAdded, this);
89
90         sourceCode.requestContent().then(this._contentAvailable.bind(this));
91
92         new WebInspector.KeyboardShortcut(WebInspector.KeyboardShortcut.Modifier.Control, "G", this.showGoToLineDialog.bind(this), this.element);
93
94         WebInspector.logManager.addEventListener(WebInspector.LogManager.Event.Cleared, this._logCleared, this);
95     }
96
97     // Public
98
99     get sourceCode()
100     {
101         return this._sourceCode;
102     }
103
104     shown()
105     {
106         super.shown();
107
108         if (WebInspector.showJavaScriptTypeInformationSetting.value) {
109             if (this._typeTokenAnnotator)
110                 this._typeTokenAnnotator.resume();
111             if (!this._typeTokenScrollHandler && this._typeTokenAnnotator)
112                 this._enableScrollEventsForTypeTokenAnnotator();
113         } else {
114             if (this._typeTokenAnnotator)
115                 this._setTypeTokenAnnotatorEnabledState(false);
116         }
117
118         if (WebInspector.enableControlFlowProfilerSetting.value) {
119             if (this._basicBlockAnnotator)
120                 this._basicBlockAnnotator.resume();
121
122             if (!this._controlFlowScrollHandler && this._basicBlockAnnotator)
123                 this._enableScrollEventsForControlFlowAnnotator();
124         } else {
125             this._basicBlockAnnotatorEnabled = false;
126         }
127     }
128
129     hidden()
130     {
131         super.hidden();
132
133         this.tokenTrackingController.removeHighlightedRange();
134
135         this._dismissPopover();
136
137         this._dismissEditingController(true);
138
139         if (this._typeTokenAnnotator)
140             this._typeTokenAnnotator.pause();
141         if (this._basicBlockAnnotator)
142             this._basicBlockAnnotator.pause();
143     }
144
145     close()
146     {
147         super.close();
148
149         if (this._supportsDebugging) {
150             WebInspector.Breakpoint.removeEventListener(null, null, this);
151             WebInspector.debuggerManager.removeEventListener(null, null, this);
152
153             if (this._activeCallFrameSourceCodeLocation) {
154                 this._activeCallFrameSourceCodeLocation.removeEventListener(WebInspector.SourceCodeLocation.Event.LocationChanged, this._activeCallFrameSourceCodeLocationChanged, this);
155                 this._activeCallFrameSourceCodeLocation = null;
156             }
157         }
158
159         WebInspector.issueManager.removeEventListener(WebInspector.IssueManager.Event.IssueWasAdded, this._issueWasAdded, this);
160
161         if (this._sourceCode instanceof WebInspector.SourceMapResource || this._sourceCode.sourceMaps.length > 0)
162             WebInspector.notifications.removeEventListener(WebInspector.Notification.GlobalModifierKeysDidChange, this._updateTokenTrackingControllerState, this);
163         else
164             this._sourceCode.removeEventListener(WebInspector.SourceCode.Event.SourceMapAdded, this._sourceCodeSourceMapAdded, this);
165     }
166
167     canBeFormatted()
168     {
169         // Currently we assume that source map resources are formatted how the author wants it.
170         // We could allow source map resources to be formatted, we would then need to make
171         // SourceCodeLocation watch listen for mappedResource's formatting changes, and keep
172         // a formatted location alongside the regular mapped location.
173         if (this._sourceCode instanceof WebInspector.SourceMapResource)
174             return false;
175
176         return super.canBeFormatted();
177     }
178
179     canShowTypeAnnotations()
180     {
181         // Type annotations for modified scripts are currently unsupported.
182         return !!this._getAssociatedScript() && !this.hasModified;
183     }
184
185     canShowCoverageHints()
186     {
187         // Code coverage hints for modified scripts are currently unsupported.
188         return !!this._getAssociatedScript() && !this.hasModified;
189     }
190
191     customPerformSearch(query)
192     {
193         function searchResultCallback(error, matches)
194         {
195             // Bail if the query changed since we started.
196             if (this.currentSearchQuery !== query)
197                 return;
198
199             if (error || !matches || !matches.length) {
200                 // Report zero matches.
201                 this.dispatchEventToListeners(WebInspector.TextEditor.Event.NumberOfSearchResultsDidChange);
202                 return;
203             }
204
205             var queryRegex = new RegExp(query.escapeForRegExp(), "gi");
206             var searchResults = [];
207
208             for (var i = 0; i < matches.length; ++i) {
209                 var matchLineNumber = matches[i].lineNumber;
210                 var line = this.line(matchLineNumber);
211                 if (!line)
212                     return;
213
214                 // Reset the last index to reuse the regex on a new line.
215                 queryRegex.lastIndex = 0;
216
217                 // Search the line and mark the ranges.
218                 var lineMatch = null;
219                 while (queryRegex.lastIndex + query.length <= line.length && (lineMatch = queryRegex.exec(line))) {
220                     var resultTextRange = new WebInspector.TextRange(matchLineNumber, lineMatch.index, matchLineNumber, queryRegex.lastIndex);
221                     searchResults.push(resultTextRange);
222                 }
223             }
224
225             this.addSearchResults(searchResults);
226
227             this.dispatchEventToListeners(WebInspector.TextEditor.Event.NumberOfSearchResultsDidChange);
228         }
229
230         if (this.hasEdits())
231             return false;
232
233         if (this._sourceCode instanceof WebInspector.SourceMapResource)
234             return false;
235
236         if (this._sourceCode instanceof WebInspector.Resource)
237             PageAgent.searchInResource(this._sourceCode.parentFrame.id, this._sourceCode.url, query, false, false, searchResultCallback.bind(this));
238         else if (this._sourceCode instanceof WebInspector.Script)
239             DebuggerAgent.searchInContent(this._sourceCode.id, query, false, false, searchResultCallback.bind(this));
240         return true;
241     }
242
243     showGoToLineDialog()
244     {
245         if (!this._goToLineDialog)
246             this._goToLineDialog = new WebInspector.GoToLineDialog(this);
247
248         this._goToLineDialog.present(this.element);
249     }
250
251     isDialogRepresentedObjectValid(goToLineDialog, lineNumber)
252     {
253         return !isNaN(lineNumber) && lineNumber > 0 && lineNumber <= this.lineCount;
254     }
255
256     dialogWasDismissed(goToLineDialog)
257     {
258         let lineNumber = goToLineDialog.representedObject;
259         let position = new WebInspector.SourceCodePosition(lineNumber - 1, 0);
260         let range = new WebInspector.TextRange(lineNumber - 1, 0, lineNumber, 0);
261
262         this.revealPosition(position, range, false, true);
263         this.focus();
264     }
265
266     contentDidChange(replacedRanges, newRanges)
267     {
268         super.contentDidChange(replacedRanges, newRanges);
269
270         if (this._ignoreContentDidChange > 0)
271             return;
272
273         for (var range of newRanges)
274             this._updateEditableMarkers(range);
275
276         if (this._basicBlockAnnotator) {
277             this._basicBlockAnnotatorEnabled = false;
278             this._basicBlockAnnotator = null;
279         }
280
281         if (this._typeTokenAnnotator) {
282             this._setTypeTokenAnnotatorEnabledState(false);
283             this._typeTokenAnnotator = null;
284         }
285     }
286
287     toggleTypeAnnotations()
288     {
289         if (!this._typeTokenAnnotator)
290             return false;
291
292         var newActivatedState = !this._typeTokenAnnotator.isActive();
293         if (newActivatedState && this._isProbablyMinified && !this.formatted) {
294             return this.updateFormattedState(true).then(() => {
295                 this._setTypeTokenAnnotatorEnabledState(newActivatedState);
296             });
297         }
298
299         this._setTypeTokenAnnotatorEnabledState(newActivatedState);
300         return Promise.resolve();
301     }
302
303     toggleUnexecutedCodeHighlights()
304     {
305         if (!this._basicBlockAnnotator)
306             return false;
307
308         let newActivatedState = !this._basicBlockAnnotator.isActive();
309         if (newActivatedState && this._isProbablyMinified && !this.formatted) {
310             return this.updateFormattedState(true).then(() => {
311                 this._basicBlockAnnotatorEnabled = newActivatedState;
312             });
313         }
314
315         this._basicBlockAnnotatorEnabled = newActivatedState;
316         return Promise.resolve();
317     }
318
319     showPopoverForTypes(typeDescription, bounds, title)
320     {
321         var content = document.createElement("div");
322         content.className = "object expandable";
323
324         var titleElement = document.createElement("div");
325         titleElement.className = "title";
326         titleElement.textContent = title;
327         content.appendChild(titleElement);
328
329         var bodyElement = content.appendChild(document.createElement("div"));
330         bodyElement.className = "body";
331
332         var typeTreeView = new WebInspector.TypeTreeView(typeDescription);
333         bodyElement.appendChild(typeTreeView.element);
334
335         this._showPopover(content, bounds);
336     }
337
338     // Protected
339
340     prettyPrint(pretty)
341     {
342         // The annotators must be cleared before pretty printing takes place and resumed
343         // after so that they clear their annotations in a known state and insert new annotations
344         // in the new state.
345
346         var shouldResumeBasicBlockAnnotator = this._basicBlockAnnotator && this._basicBlockAnnotator.isActive();
347         if (shouldResumeBasicBlockAnnotator)
348             this._basicBlockAnnotatorEnabled = false;
349
350         let shouldResumeTypeTokenAnnotator = this._typeTokenAnnotator && this._typeTokenAnnotator.isActive();
351         if (shouldResumeTypeTokenAnnotator)
352             this._setTypeTokenAnnotatorEnabledState(false);
353
354         return super.prettyPrint(pretty).then(() => {
355             if (pretty || !this._isProbablyMinified) {
356                 if (shouldResumeBasicBlockAnnotator)
357                     this._basicBlockAnnotatorEnabled = true;
358
359                 if (shouldResumeTypeTokenAnnotator)
360                     this._setTypeTokenAnnotatorEnabledState(true);
361             } else {
362                 console.assert(!pretty && this._isProbablyMinified);
363                 if (this._basicBlockAnnotator)
364                     this._basicBlockAnnotatorEnabled = false;
365
366                 this._setTypeTokenAnnotatorEnabledState(false);
367             }
368         });
369     }
370
371     // Private
372
373     _unformattedLineInfoForEditorLineInfo(lineInfo)
374     {
375         if (this.formatterSourceMap)
376             return this.formatterSourceMap.formattedToOriginal(lineInfo.lineNumber, lineInfo.columnNumber);
377         return lineInfo;
378     }
379
380     _sourceCodeLocationForEditorPosition(position)
381     {
382         var lineInfo = {lineNumber: position.line, columnNumber: position.ch};
383         var unformattedLineInfo = this._unformattedLineInfoForEditorLineInfo(lineInfo);
384         return this.sourceCode.createSourceCodeLocation(unformattedLineInfo.lineNumber, unformattedLineInfo.columnNumber);
385     }
386
387     _editorLineInfoForSourceCodeLocation(sourceCodeLocation)
388     {
389         if (this._sourceCode instanceof WebInspector.SourceMapResource)
390             return {lineNumber: sourceCodeLocation.displayLineNumber, columnNumber: sourceCodeLocation.displayColumnNumber};
391         return {lineNumber: sourceCodeLocation.formattedLineNumber, columnNumber: sourceCodeLocation.formattedColumnNumber};
392     }
393
394     _breakpointForEditorLineInfo(lineInfo)
395     {
396         if (!this._breakpointMap[lineInfo.lineNumber])
397             return null;
398         return this._breakpointMap[lineInfo.lineNumber][lineInfo.columnNumber];
399     }
400
401     _addBreakpointWithEditorLineInfo(breakpoint, lineInfo)
402     {
403         if (!this._breakpointMap[lineInfo.lineNumber])
404             this._breakpointMap[lineInfo.lineNumber] = {};
405
406         this._breakpointMap[lineInfo.lineNumber][lineInfo.columnNumber] = breakpoint;
407     }
408
409     _removeBreakpointWithEditorLineInfo(breakpoint, lineInfo)
410     {
411         console.assert(breakpoint === this._breakpointMap[lineInfo.lineNumber][lineInfo.columnNumber]);
412
413         delete this._breakpointMap[lineInfo.lineNumber][lineInfo.columnNumber];
414
415         if (isEmptyObject(this._breakpointMap[lineInfo.lineNumber]))
416             delete this._breakpointMap[lineInfo.lineNumber];
417     }
418
419     _populateWithContent(content)
420     {
421         content = content || "";
422
423         this._prepareEditorForInitialContent(content);
424
425         // If we can auto format, format the TextEditor before showing it.
426         if (this._autoFormat) {
427             console.assert(!this.formatted);
428             this._autoFormat = false;
429             this.deferReveal = true;
430             this.string = content;
431             this.deferReveal = false;
432             this.updateFormattedState(true).then(() => {
433                 this._proceedPopulateWithContent(this.string);
434             });
435             return;
436         }
437
438         this._proceedPopulateWithContent(content);
439     }
440
441     _proceedPopulateWithContent(content)
442     {
443         this.dispatchEventToListeners(WebInspector.SourceCodeTextEditor.Event.ContentWillPopulate);
444
445         this.string = content;
446
447         this._createBasicBlockAnnotator();
448         if (WebInspector.enableControlFlowProfilerSetting.value && this._basicBlockAnnotator)
449             this._basicBlockAnnotatorEnabled = true;
450
451         this._createTypeTokenAnnotator();
452         if (WebInspector.showJavaScriptTypeInformationSetting.value)
453             this._setTypeTokenAnnotatorEnabledState(true);
454
455         this._contentDidPopulate();
456     }
457
458     _contentDidPopulate()
459     {
460         this._contentPopulated = true;
461
462         this.dispatchEventToListeners(WebInspector.SourceCodeTextEditor.Event.ContentDidPopulate);
463
464         // We add the issues each time content is populated. This is needed because lines might not exist
465         // if we tried added them before when the full content wasn't available. (When populating with
466         // partial script content this can be called multiple times.)
467
468         this._reinsertAllIssues();
469
470         this._updateEditableMarkers();
471     }
472
473     _prepareEditorForInitialContent(content)
474     {
475         // Only do this work before the first populate.
476         if (this._contentPopulated)
477             return;
478
479         if (this._supportsDebugging) {
480             this._breakpointMap = {};
481
482             var breakpoints = WebInspector.debuggerManager.breakpointsForSourceCode(this._sourceCode);
483             for (var i = 0; i < breakpoints.length; ++i) {
484                 var breakpoint = breakpoints[i];
485                 console.assert(this._matchesBreakpoint(breakpoint));
486                 var lineInfo = this._editorLineInfoForSourceCodeLocation(breakpoint.sourceCodeLocation);
487                 this._addBreakpointWithEditorLineInfo(breakpoint, lineInfo);
488                 this.setBreakpointInfoForLineAndColumn(lineInfo.lineNumber, lineInfo.columnNumber, this._breakpointInfoForBreakpoint(breakpoint));
489             }
490         }
491
492         if (this._sourceCode instanceof WebInspector.Resource)
493             this.mimeType = this._sourceCode.syntheticMIMEType;
494         else if (this._sourceCode instanceof WebInspector.Script)
495             this.mimeType = "text/javascript";
496
497         // Decide to automatically format the content if it looks minified and it can be formatted.
498         console.assert(!this.formatted);
499         if (this.canBeFormatted() && isTextLikelyMinified(content)) {
500             this._autoFormat = true;
501             this._isProbablyMinified = true;
502         }
503     }
504
505     _contentAvailable(parameters)
506     {
507         // Return if resource is not available.
508         if (parameters.error)
509             return;
510
511         var sourceCode = parameters.sourceCode;
512         var content = sourceCode.content;
513         var base64Encoded = parameters.base64Encoded;
514
515         console.assert(sourceCode === this._sourceCode);
516         console.assert(!base64Encoded);
517
518         // Abort if the full content populated while waiting for this async callback.
519         if (this._fullContentPopulated)
520             return;
521
522         this._fullContentPopulated = true;
523         this._invalidLineNumbers = {};
524
525         this._populateWithContent(content);
526     }
527
528     _breakpointStatusDidChange(event)
529     {
530         this._updateBreakpointStatus(event.target);
531     }
532
533     _breakpointsEnabledDidChange()
534     {
535         console.assert(this._supportsDebugging);
536
537         var breakpoints = WebInspector.debuggerManager.breakpointsForSourceCode(this._sourceCode);
538         for (var breakpoint of breakpoints)
539             this._updateBreakpointStatus(breakpoint);
540     }
541
542     _updateBreakpointStatus(breakpoint)
543     {
544         console.assert(this._supportsDebugging);
545
546         if (!this._contentPopulated)
547             return;
548
549         if (!this._matchesBreakpoint(breakpoint))
550             return;
551
552         var lineInfo = this._editorLineInfoForSourceCodeLocation(breakpoint.sourceCodeLocation);
553         this.setBreakpointInfoForLineAndColumn(lineInfo.lineNumber, lineInfo.columnNumber, this._breakpointInfoForBreakpoint(breakpoint));
554     }
555
556     _updateBreakpointLocation(event)
557     {
558         console.assert(this._supportsDebugging);
559
560         if (!this._contentPopulated)
561             return;
562
563         var breakpoint = event.target;
564         if (!this._matchesBreakpoint(breakpoint))
565             return;
566
567         if (this._ignoreAllBreakpointLocationUpdates)
568             return;
569
570         if (breakpoint === this._ignoreLocationUpdateBreakpoint)
571             return;
572
573         var sourceCodeLocation = breakpoint.sourceCodeLocation;
574
575         if (this._sourceCode instanceof WebInspector.SourceMapResource) {
576             // Update our breakpoint location if the display location changed.
577             if (sourceCodeLocation.displaySourceCode !== this._sourceCode)
578                 return;
579             var oldLineInfo = {lineNumber: event.data.oldDisplayLineNumber, columnNumber: event.data.oldDisplayColumnNumber};
580             var newLineInfo = {lineNumber: sourceCodeLocation.displayLineNumber, columnNumber: sourceCodeLocation.displayColumnNumber};
581         } else {
582             // Update our breakpoint location if the original location changed.
583             if (sourceCodeLocation.sourceCode !== this._sourceCode)
584                 return;
585             var oldLineInfo = {lineNumber: event.data.oldFormattedLineNumber, columnNumber: event.data.oldFormattedColumnNumber};
586             var newLineInfo = {lineNumber: sourceCodeLocation.formattedLineNumber, columnNumber: sourceCodeLocation.formattedColumnNumber};
587         }
588
589         var existingBreakpoint = this._breakpointForEditorLineInfo(oldLineInfo);
590         if (!existingBreakpoint)
591             return;
592
593         console.assert(breakpoint === existingBreakpoint);
594
595         this.setBreakpointInfoForLineAndColumn(oldLineInfo.lineNumber, oldLineInfo.columnNumber, null);
596         this.setBreakpointInfoForLineAndColumn(newLineInfo.lineNumber, newLineInfo.columnNumber, this._breakpointInfoForBreakpoint(breakpoint));
597
598         this._removeBreakpointWithEditorLineInfo(breakpoint, oldLineInfo);
599         this._addBreakpointWithEditorLineInfo(breakpoint, newLineInfo);
600     }
601
602     _breakpointAdded(event)
603     {
604         console.assert(this._supportsDebugging);
605
606         if (!this._contentPopulated)
607             return;
608
609         var breakpoint = event.data.breakpoint;
610         if (!this._matchesBreakpoint(breakpoint))
611             return;
612
613         if (breakpoint === this._ignoreBreakpointAddedBreakpoint)
614             return;
615
616         var lineInfo = this._editorLineInfoForSourceCodeLocation(breakpoint.sourceCodeLocation);
617         this._addBreakpointWithEditorLineInfo(breakpoint, lineInfo);
618         this.setBreakpointInfoForLineAndColumn(lineInfo.lineNumber, lineInfo.columnNumber, this._breakpointInfoForBreakpoint(breakpoint));
619     }
620
621     _breakpointRemoved(event)
622     {
623         console.assert(this._supportsDebugging);
624
625         if (!this._contentPopulated)
626             return;
627
628         var breakpoint = event.data.breakpoint;
629         if (!this._matchesBreakpoint(breakpoint))
630             return;
631
632         if (breakpoint === this._ignoreBreakpointRemovedBreakpoint)
633             return;
634
635         var lineInfo = this._editorLineInfoForSourceCodeLocation(breakpoint.sourceCodeLocation);
636         this._removeBreakpointWithEditorLineInfo(breakpoint, lineInfo);
637         this.setBreakpointInfoForLineAndColumn(lineInfo.lineNumber, lineInfo.columnNumber, null);
638     }
639
640     _activeCallFrameDidChange()
641     {
642         console.assert(this._supportsDebugging);
643
644         if (this._activeCallFrameSourceCodeLocation) {
645             this._activeCallFrameSourceCodeLocation.removeEventListener(WebInspector.SourceCodeLocation.Event.LocationChanged, this._activeCallFrameSourceCodeLocationChanged, this);
646             this._activeCallFrameSourceCodeLocation = null;
647         }
648
649         var activeCallFrame = WebInspector.debuggerManager.activeCallFrame;
650         if (!activeCallFrame || !this._matchesSourceCodeLocation(activeCallFrame.sourceCodeLocation)) {
651             this.setExecutionLineAndColumn(NaN, NaN);
652             return;
653         }
654
655         this._dismissPopover();
656
657         this._activeCallFrameSourceCodeLocation = activeCallFrame.sourceCodeLocation;
658         this._activeCallFrameSourceCodeLocation.addEventListener(WebInspector.SourceCodeLocation.Event.LocationChanged, this._activeCallFrameSourceCodeLocationChanged, this);
659
660         // Don't return early if the line number didn't change. The execution state still
661         // could have changed (e.g. continuing in a loop with a breakpoint inside).
662
663         var lineInfo = this._editorLineInfoForSourceCodeLocation(activeCallFrame.sourceCodeLocation);
664         this.setExecutionLineAndColumn(lineInfo.lineNumber, lineInfo.columnNumber);
665
666         // If we have full content or this source code isn't a Resource we can return early.
667         // Script source code populates from the request started in the constructor.
668         if (this._fullContentPopulated || !(this._sourceCode instanceof WebInspector.Resource) || this._requestingScriptContent)
669             return;
670
671         // Since we are paused in the debugger we need to show some content, and since the Resource
672         // content hasn't populated yet we need to populate with content from the Scripts by URL.
673         // Document resources will attempt to populate the scripts as inline (in <script> tags.)
674         // Other resources are assumed to be full scripts (JavaScript resources).
675         if (this._sourceCode.type === WebInspector.Resource.Type.Document)
676             this._populateWithInlineScriptContent();
677         else
678             this._populateWithScriptContent();
679     }
680
681     _activeCallFrameSourceCodeLocationChanged(event)
682     {
683         console.assert(!isNaN(this.executionLineNumber));
684         if (isNaN(this.executionLineNumber))
685             return;
686
687         console.assert(WebInspector.debuggerManager.activeCallFrame);
688         console.assert(this._activeCallFrameSourceCodeLocation === WebInspector.debuggerManager.activeCallFrame.sourceCodeLocation);
689
690         var lineInfo = this._editorLineInfoForSourceCodeLocation(this._activeCallFrameSourceCodeLocation);
691         this.setExecutionLineAndColumn(lineInfo.lineNumber, lineInfo.columnNumber);
692     }
693
694     _populateWithInlineScriptContent()
695     {
696         console.assert(this._sourceCode instanceof WebInspector.Resource);
697         console.assert(!this._fullContentPopulated);
698         console.assert(!this._requestingScriptContent);
699
700         var scripts = this._sourceCode.scripts;
701         console.assert(scripts.length);
702         if (!scripts.length)
703             return;
704
705         var pendingRequestCount = scripts.length;
706
707         // If the number of scripts hasn't change since the last populate, then there is nothing to do.
708         if (this._inlineScriptContentPopulated === pendingRequestCount)
709             return;
710
711         this._inlineScriptContentPopulated = pendingRequestCount;
712
713         function scriptContentAvailable(parameters)
714         {
715             // Return early if we are still waiting for content from other scripts.
716             if (--pendingRequestCount)
717                 return;
718
719             this._requestingScriptContent = false;
720
721             // Abort if the full content populated while waiting for these async callbacks.
722             if (this._fullContentPopulated)
723                 return;
724
725             var scriptOpenTag = "<script>";
726             var scriptCloseTag = "</script>";
727
728             var content = "";
729             var lineNumber = 0;
730             var columnNumber = 0;
731
732             this._invalidLineNumbers = {};
733
734             for (var i = 0; i < scripts.length; ++i) {
735                 // Fill the line gap with newline characters.
736                 for (var newLinesCount = scripts[i].range.startLine - lineNumber; newLinesCount > 0; --newLinesCount) {
737                     if (!columnNumber)
738                         this._invalidLineNumbers[scripts[i].range.startLine - newLinesCount] = true;
739                     columnNumber = 0;
740                     content += "\n";
741                 }
742
743                 // Fill the column gap with space characters.
744                 for (var spacesCount = scripts[i].range.startColumn - columnNumber - scriptOpenTag.length; spacesCount > 0; --spacesCount)
745                     content += " ";
746
747                 // Add script tags and content.
748                 content += scriptOpenTag;
749                 content += scripts[i].content;
750                 content += scriptCloseTag;
751
752                 lineNumber = scripts[i].range.endLine;
753                 columnNumber = scripts[i].range.endColumn + scriptCloseTag.length;
754             }
755
756             this._populateWithContent(content);
757         }
758
759         this._requestingScriptContent = true;
760
761         var boundScriptContentAvailable = scriptContentAvailable.bind(this);
762         for (var i = 0; i < scripts.length; ++i)
763             scripts[i].requestContent().then(boundScriptContentAvailable);
764     }
765
766     _populateWithScriptContent()
767     {
768         console.assert(this._sourceCode instanceof WebInspector.Resource);
769         console.assert(!this._fullContentPopulated);
770         console.assert(!this._requestingScriptContent);
771
772         // We can assume this resource only has one script that starts at line/column 0.
773         var scripts = this._sourceCode.scripts;
774         console.assert(scripts.length === 1);
775         if (!scripts.length)
776             return;
777
778         console.assert(scripts[0].range.startLine === 0);
779         console.assert(scripts[0].range.startColumn === 0);
780
781         function scriptContentAvailable(parameters)
782         {
783             var content = parameters.content;
784             this._requestingScriptContent = false;
785
786             // Abort if the full content populated while waiting for this async callback.
787             if (this._fullContentPopulated)
788                 return;
789
790             // This is the full content.
791             this._fullContentPopulated = true;
792
793             this._populateWithContent(content);
794         }
795
796         this._requestingScriptContent = true;
797
798         scripts[0].requestContent().then(scriptContentAvailable.bind(this));
799     }
800
801     _matchesSourceCodeLocation(sourceCodeLocation)
802     {
803         if (this._sourceCode instanceof WebInspector.SourceMapResource)
804             return sourceCodeLocation.displaySourceCode === this._sourceCode;
805         if (this._sourceCode instanceof WebInspector.Resource)
806             return sourceCodeLocation.sourceCode.url === this._sourceCode.url;
807         if (this._sourceCode instanceof WebInspector.Script)
808             return sourceCodeLocation.sourceCode === this._sourceCode;
809         return false;
810     }
811
812     _matchesBreakpoint(breakpoint)
813     {
814         console.assert(this._supportsDebugging);
815         if (this._sourceCode instanceof WebInspector.SourceMapResource)
816             return breakpoint.sourceCodeLocation.displaySourceCode === this._sourceCode;
817         if (this._sourceCode instanceof WebInspector.Resource)
818             return breakpoint.contentIdentifier === this._sourceCode.contentIdentifier;
819         if (this._sourceCode instanceof WebInspector.Script)
820             return breakpoint.contentIdentifier === this._sourceCode.contentIdentifier || breakpoint.scriptIdentifier === this._sourceCode.id;
821         return false;
822     }
823
824     _issueWasAdded(event)
825     {
826         var issue = event.data.issue;
827         if (!WebInspector.IssueManager.issueMatchSourceCode(issue, this._sourceCode))
828             return;
829
830         this._addIssue(issue);
831     }
832
833     _addIssue(issue)
834     {
835         var sourceCodeLocation = issue.sourceCodeLocation;
836         console.assert(sourceCodeLocation, "Expected source code location to place issue.");
837         if (!sourceCodeLocation)
838             return;
839
840         var lineNumber = sourceCodeLocation.formattedLineNumber;
841
842         var lineNumberIssues = this._issuesLineNumberMap.get(lineNumber);
843         if (!lineNumberIssues) {
844             lineNumberIssues = [];
845             this._issuesLineNumberMap.set(lineNumber, lineNumberIssues);
846         }
847
848         // Avoid displaying duplicate issues on the same line.
849         for (var existingIssue of lineNumberIssues) {
850             if (existingIssue.sourceCodeLocation.columnNumber === sourceCodeLocation.columnNumber && existingIssue.text === issue.text)
851                 return;
852         }
853
854         lineNumberIssues.push(issue);
855
856         if (issue.level === WebInspector.IssueMessage.Level.Error)
857             this.addStyleClassToLine(lineNumber, WebInspector.SourceCodeTextEditor.LineErrorStyleClassName);
858         else if (issue.level === WebInspector.IssueMessage.Level.Warning)
859             this.addStyleClassToLine(lineNumber, WebInspector.SourceCodeTextEditor.LineWarningStyleClassName);
860         else
861             console.error("Unknown issue level");
862
863         var widget = this._issueWidgetForLine(lineNumber);
864         if (widget) {
865             if (issue.level === WebInspector.IssueMessage.Level.Error)
866                 widget.widgetElement.classList.add(WebInspector.SourceCodeTextEditor.LineErrorStyleClassName);
867             else if (issue.level === WebInspector.IssueMessage.Level.Warning)
868                 widget.widgetElement.classList.add(WebInspector.SourceCodeTextEditor.LineWarningStyleClassName);
869
870             this._updateIssueWidgetForIssues(widget, lineNumberIssues);
871         }
872     }
873
874     _issueWidgetForLine(lineNumber)
875     {
876         var widget = this._widgetMap.get(lineNumber);
877         if (widget)
878             return widget;
879
880         widget = this.createWidgetForLine(lineNumber);
881         if (!widget)
882             return null;
883
884         var widgetElement = widget.widgetElement;
885         widgetElement.classList.add("issue-widget", "inline");
886         widgetElement.addEventListener("click", this._handleWidgetClick.bind(this, widget, lineNumber));
887
888         this._widgetMap.set(lineNumber, widget);
889
890         return widget;
891     }
892
893     _iconClassNameForIssueLevel(level)
894     {
895         if (level === WebInspector.IssueMessage.Level.Warning)
896             return "icon-warning";
897
898         console.assert(level === WebInspector.IssueMessage.Level.Error);
899         return "icon-error";
900     }
901
902     _updateIssueWidgetForIssues(widget, issues)
903     {
904         var widgetElement = widget.widgetElement;
905         widgetElement.removeChildren();
906
907         if (widgetElement.classList.contains("inline") || issues.length === 1) {
908             var arrowElement = widgetElement.appendChild(document.createElement("span"));
909             arrowElement.className = "arrow";
910
911             var iconElement = widgetElement.appendChild(document.createElement("span"));
912             iconElement.className = "icon";
913
914             var textElement = widgetElement.appendChild(document.createElement("span"));
915             textElement.className = "text";
916
917             if (issues.length === 1) {
918                 iconElement.classList.add(this._iconClassNameForIssueLevel(issues[0].level));
919                 textElement.textContent = issues[0].text;
920             } else {
921                 var errorsCount = 0;
922                 var warningsCount = 0;
923                 for (var issue of issues) {
924                     if (issue.level === WebInspector.IssueMessage.Level.Error)
925                         ++errorsCount;
926                     else if (issue.level === WebInspector.IssueMessage.Level.Warning)
927                         ++warningsCount;
928                 }
929
930                 if (warningsCount && errorsCount) {
931                     iconElement.classList.add(this._iconClassNameForIssueLevel(issue.level));
932                     textElement.textContent = WebInspector.UIString("%d Errors, %d Warnings").format(errorsCount, warningsCount);
933                 } else if (errorsCount) {
934                     iconElement.classList.add(this._iconClassNameForIssueLevel(issue.level));
935                     textElement.textContent = WebInspector.UIString("%d Errors").format(errorsCount);
936                 } else if (warningsCount) {
937                     iconElement.classList.add(this._iconClassNameForIssueLevel(issue.level));
938                     textElement.textContent = WebInspector.UIString("%d Warnings").format(warningsCount);
939                 }
940
941                 widget[WebInspector.SourceCodeTextEditor.WidgetContainsMultipleIssuesSymbol] = true;
942             }
943         } else {
944             for (var issue of issues) {
945                 var iconElement = widgetElement.appendChild(document.createElement("span"));
946                 iconElement.className = "icon";
947                 iconElement.classList.add(this._iconClassNameForIssueLevel(issue.level));
948
949                 var textElement = widgetElement.appendChild(document.createElement("span"));
950                 textElement.className = "text";
951                 textElement.textContent = issue.text;
952
953                 widgetElement.appendChild(document.createElement("br"));
954             }
955         }
956
957         widget.update();
958     }
959
960     _isWidgetToggleable(widget)
961     {
962         if (widget[WebInspector.SourceCodeTextEditor.WidgetContainsMultipleIssuesSymbol])
963             return true;
964
965         if (!widget.widgetElement.classList.contains("inline"))
966             return true;
967
968         var textElement = widget.widgetElement.lastChild;
969         if (textElement.offsetWidth !== textElement.scrollWidth)
970             return true;
971
972         return false;
973     }
974
975     _handleWidgetClick(widget, lineNumber, event)
976     {
977         if (!this._isWidgetToggleable(widget))
978             return;
979
980         widget.widgetElement.classList.toggle("inline");
981
982         var lineNumberIssues = this._issuesLineNumberMap.get(lineNumber);
983         this._updateIssueWidgetForIssues(widget, lineNumberIssues);
984     }
985
986     _breakpointInfoForBreakpoint(breakpoint)
987     {
988         return {resolved: breakpoint.resolved, disabled: breakpoint.disabled, autoContinue: breakpoint.autoContinue};
989     }
990
991     get _supportsDebugging()
992     {
993         if (this._sourceCode instanceof WebInspector.Resource)
994             return this._sourceCode.type === WebInspector.Resource.Type.Document || this._sourceCode.type === WebInspector.Resource.Type.Script;
995         if (this._sourceCode instanceof WebInspector.Script)
996             return true;
997         return false;
998     }
999
1000     // TextEditor Delegate
1001
1002     textEditorBaseURL(textEditor)
1003     {
1004         return this._sourceCode.url;
1005     }
1006
1007     textEditorShouldHideLineNumber(textEditor, lineNumber)
1008     {
1009         return lineNumber in this._invalidLineNumbers;
1010     }
1011
1012     textEditorGutterContextMenu(textEditor, lineNumber, columnNumber, editorBreakpoints, event)
1013     {
1014         if (!this._supportsDebugging)
1015             return;
1016
1017         event.preventDefault();
1018
1019         let addBreakpoint = () => {
1020             let data = this.textEditorBreakpointAdded(this, lineNumber, columnNumber);
1021             this.setBreakpointInfoForLineAndColumn(data.lineNumber, data.columnNumber, data.breakpointInfo);
1022         };
1023
1024         let contextMenu = WebInspector.ContextMenu.createFromEvent(event);
1025
1026         // Paused. Add Continue to Here option only if we have a script identifier for the location.
1027         if (WebInspector.debuggerManager.paused) {
1028             let editorLineInfo = {lineNumber, columnNumber};
1029             let unformattedLineInfo = this._unformattedLineInfoForEditorLineInfo(editorLineInfo);
1030             let sourceCodeLocation = this._sourceCode.createSourceCodeLocation(unformattedLineInfo.lineNumber, unformattedLineInfo.columnNumber);
1031
1032             let script;
1033             if (sourceCodeLocation.sourceCode instanceof WebInspector.Script)
1034                 script = sourceCodeLocation.sourceCode;
1035             else if (sourceCodeLocation.sourceCode instanceof WebInspector.Resource)
1036                 script = sourceCodeLocation.sourceCode.scriptForLocation(sourceCodeLocation);
1037
1038             if (script) {
1039                 contextMenu.appendItem(WebInspector.UIString("Continue to Here"), () => {
1040                     WebInspector.debuggerManager.continueToLocation(script.id, sourceCodeLocation.lineNumber, sourceCodeLocation.columnNumber);
1041                 });
1042                 contextMenu.appendSeparator();
1043             }
1044         }
1045
1046         let breakpoints = [];
1047         for (let lineInfo of editorBreakpoints) {
1048             let breakpoint = this._breakpointForEditorLineInfo(lineInfo);
1049             console.assert(breakpoint);
1050             if (breakpoint)
1051                 breakpoints.push(breakpoint);
1052         }
1053
1054         // No breakpoints.
1055         if (!breakpoints.length) {
1056             contextMenu.appendItem(WebInspector.UIString("Add Breakpoint"), addBreakpoint.bind(this));
1057             return;
1058         }
1059
1060         // Single breakpoint.
1061         if (breakpoints.length === 1) {
1062             WebInspector.breakpointPopoverController.appendContextMenuItems(contextMenu, breakpoints[0], event.target);
1063
1064             if (!WebInspector.isShowingDebuggerTab()) {
1065                 contextMenu.appendSeparator();
1066                 contextMenu.appendItem(WebInspector.UIString("Reveal in Debugger Tab"), () => {
1067                     WebInspector.showDebuggerTab(breakpoints[0]);
1068                 });
1069             }
1070
1071             return;
1072         }
1073
1074         // Multiple breakpoints.
1075         let removeBreakpoints = () => {
1076             for (let breakpoint of breakpoints) {
1077                 if (WebInspector.debuggerManager.isBreakpointRemovable(breakpoint))
1078                     WebInspector.debuggerManager.removeBreakpoint(breakpoint);
1079             }
1080         };
1081
1082         let shouldDisable = breakpoints.some((breakpoint) => !breakpoint.disabled);
1083         let toggleBreakpoints = (shouldDisable) => {
1084             for (let breakpoint of breakpoints)
1085                 breakpoint.disabled = shouldDisable;
1086         };
1087
1088         if (shouldDisable)
1089             contextMenu.appendItem(WebInspector.UIString("Disable Breakpoints"), toggleBreakpoints);
1090         else
1091             contextMenu.appendItem(WebInspector.UIString("Enable Breakpoints"), toggleBreakpoints);
1092         contextMenu.appendItem(WebInspector.UIString("Delete Breakpoints"), removeBreakpoints);
1093     }
1094
1095     textEditorBreakpointAdded(textEditor, lineNumber, columnNumber)
1096     {
1097         if (!this._supportsDebugging)
1098             return null;
1099
1100         var editorLineInfo = {lineNumber, columnNumber};
1101         var unformattedLineInfo = this._unformattedLineInfoForEditorLineInfo(editorLineInfo);
1102         var sourceCodeLocation = this._sourceCode.createSourceCodeLocation(unformattedLineInfo.lineNumber, unformattedLineInfo.columnNumber);
1103         var breakpoint = new WebInspector.Breakpoint(sourceCodeLocation);
1104
1105         var lineInfo = this._editorLineInfoForSourceCodeLocation(breakpoint.sourceCodeLocation);
1106         this._addBreakpointWithEditorLineInfo(breakpoint, lineInfo);
1107
1108         this._ignoreBreakpointAddedBreakpoint = breakpoint;
1109         const shouldSpeculativelyResolveBreakpoint = true;
1110         WebInspector.debuggerManager.addBreakpoint(breakpoint, shouldSpeculativelyResolveBreakpoint);
1111         this._ignoreBreakpointAddedBreakpoint = null;
1112
1113         // Return the more accurate location and breakpoint info.
1114         return {
1115             breakpointInfo: this._breakpointInfoForBreakpoint(breakpoint),
1116             lineNumber: lineInfo.lineNumber,
1117             columnNumber: lineInfo.columnNumber
1118         };
1119     }
1120
1121     textEditorBreakpointRemoved(textEditor, lineNumber, columnNumber)
1122     {
1123         console.assert(this._supportsDebugging);
1124         if (!this._supportsDebugging)
1125             return;
1126
1127         var lineInfo = {lineNumber, columnNumber};
1128         var breakpoint = this._breakpointForEditorLineInfo(lineInfo);
1129         console.assert(breakpoint);
1130         if (!breakpoint)
1131             return;
1132
1133         this._removeBreakpointWithEditorLineInfo(breakpoint, lineInfo);
1134
1135         this._ignoreBreakpointRemovedBreakpoint = breakpoint;
1136         WebInspector.debuggerManager.removeBreakpoint(breakpoint);
1137         this._ignoreBreakpointRemovedBreakpoint = null;
1138     }
1139
1140     textEditorBreakpointMoved(textEditor, oldLineNumber, oldColumnNumber, newLineNumber, newColumnNumber)
1141     {
1142         console.assert(this._supportsDebugging);
1143         if (!this._supportsDebugging)
1144             return;
1145
1146         var oldLineInfo = {lineNumber: oldLineNumber, columnNumber: oldColumnNumber};
1147         var breakpoint = this._breakpointForEditorLineInfo(oldLineInfo);
1148         console.assert(breakpoint);
1149         if (!breakpoint)
1150             return;
1151
1152         this._removeBreakpointWithEditorLineInfo(breakpoint, oldLineInfo);
1153
1154         var newLineInfo = {lineNumber: newLineNumber, columnNumber: newColumnNumber};
1155         var unformattedNewLineInfo = this._unformattedLineInfoForEditorLineInfo(newLineInfo);
1156         this._ignoreLocationUpdateBreakpoint = breakpoint;
1157         breakpoint.sourceCodeLocation.update(this._sourceCode, unformattedNewLineInfo.lineNumber, unformattedNewLineInfo.columnNumber);
1158         this._ignoreLocationUpdateBreakpoint = null;
1159
1160         var accurateNewLineInfo = this._editorLineInfoForSourceCodeLocation(breakpoint.sourceCodeLocation);
1161         this._addBreakpointWithEditorLineInfo(breakpoint, accurateNewLineInfo);
1162
1163         if (accurateNewLineInfo.lineNumber !== newLineInfo.lineNumber || accurateNewLineInfo.columnNumber !== newLineInfo.columnNumber)
1164             this.updateBreakpointLineAndColumn(newLineInfo.lineNumber, newLineInfo.columnNumber, accurateNewLineInfo.lineNumber, accurateNewLineInfo.columnNumber);
1165     }
1166
1167     textEditorBreakpointClicked(textEditor, lineNumber, columnNumber)
1168     {
1169         console.assert(this._supportsDebugging);
1170         if (!this._supportsDebugging)
1171             return;
1172
1173         var breakpoint = this._breakpointForEditorLineInfo({lineNumber, columnNumber});
1174         console.assert(breakpoint);
1175         if (!breakpoint)
1176             return;
1177
1178         breakpoint.cycleToNextMode();
1179     }
1180
1181     textEditorUpdatedFormatting(textEditor)
1182     {
1183         this._ignoreAllBreakpointLocationUpdates = true;
1184         this._sourceCode.formatterSourceMap = this.formatterSourceMap;
1185         this._ignoreAllBreakpointLocationUpdates = false;
1186
1187         // Always put the source map on both the Script and Resource if both exist. For example,
1188         // if this SourceCode is a Resource, then there might also be a Script. In the debugger,
1189         // the backend identifies call frames with Script line and column information, and the
1190         // Script needs the formatter source map to produce the proper display line and column.
1191         if (this._sourceCode instanceof WebInspector.Resource && !(this._sourceCode instanceof WebInspector.SourceMapResource)) {
1192             var scripts = this._sourceCode.scripts;
1193             for (var i = 0; i < scripts.length; ++i)
1194                 scripts[i].formatterSourceMap = this.formatterSourceMap;
1195         } else if (this._sourceCode instanceof WebInspector.Script) {
1196             if (this._sourceCode.resource)
1197                 this._sourceCode.resource.formatterSourceMap = this.formatterSourceMap;
1198         }
1199
1200         // Some breakpoints / issues may have moved, some might not have. Just go through
1201         // and remove and reinsert all the breakpoints / issues.
1202
1203         var oldBreakpointMap = this._breakpointMap;
1204         this._breakpointMap = {};
1205
1206         for (var lineNumber in oldBreakpointMap) {
1207             for (var columnNumber in oldBreakpointMap[lineNumber]) {
1208                 var breakpoint = oldBreakpointMap[lineNumber][columnNumber];
1209                 var newLineInfo = this._editorLineInfoForSourceCodeLocation(breakpoint.sourceCodeLocation);
1210                 this._addBreakpointWithEditorLineInfo(breakpoint, newLineInfo);
1211                 this.setBreakpointInfoForLineAndColumn(lineNumber, columnNumber, null);
1212                 this.setBreakpointInfoForLineAndColumn(newLineInfo.lineNumber, newLineInfo.columnNumber, this._breakpointInfoForBreakpoint(breakpoint));
1213             }
1214         }
1215
1216         this._reinsertAllIssues();
1217     }
1218
1219     textEditorExecutionHighlightRange(offset, position, characterAtOffset, callback)
1220     {
1221         let script = this._getAssociatedScript(position);
1222         if (!script) {
1223             callback(null);
1224             return;
1225         }
1226
1227         // If this is an inline script, then convert to offset within the inline script.
1228         let adjustment = script.range.startOffset || 0;
1229         offset = offset - adjustment;
1230
1231         // When returning offsets, convert to offsets within the SourceCode being viewed.
1232         function convertRangeOffsetsToSourceCodeOffsets(range) {
1233             return range.map((offset) => offset + adjustment);
1234         }
1235
1236         script.requestScriptSyntaxTree((syntaxTree) => {
1237             let nodes = syntaxTree.containersOfOffset(offset);
1238             if (!nodes.length) {
1239                 callback(null);
1240                 return;
1241             }
1242
1243             // Find a node starting at this offset.
1244             // Avoid highlighting the entire program if this is the start of the first statement.
1245             // Special case the assignment expression inside of a for..of and for..in to highlight a larger range.
1246             for (let node of nodes) {
1247                 let startOffset = node.range[0];
1248                 if (startOffset === offset && node.type !== WebInspector.ScriptSyntaxTree.NodeType.Program) {
1249                     callback(convertRangeOffsetsToSourceCodeOffsets(node.range));
1250                     return;
1251                 }
1252                 if (node.type === WebInspector.ScriptSyntaxTree.NodeType.ForInStatement || node.type === WebInspector.ScriptSyntaxTree.NodeType.ForOfStatement) {
1253                     if (node.left.range[0] === offset) {
1254                         callback(convertRangeOffsetsToSourceCodeOffsets([node.left.range[0], node.right.range[1]]));
1255                         return;
1256                     }
1257                 }
1258                 if (startOffset > offset)
1259                     break;
1260             }
1261
1262             // Find a node ending at this offset. (Leaving a block).
1263             // We check this after ensuring nothing starts with this offset,
1264             // as that would be more important.
1265             for (let node of nodes) {
1266                 let startOffset = node.range[0];
1267                 let endOffset = node.range[1];
1268                 if (endOffset === offset) {
1269                     if (node.type === WebInspector.ScriptSyntaxTree.NodeType.BlockStatement) {
1270                         // Closing brace of a block, only highlight the closing brace character.
1271                         callback(convertRangeOffsetsToSourceCodeOffsets([offset - 1, offset]));
1272                         return;
1273                     }
1274                 }
1275                 if (startOffset > offset)
1276                     break;
1277             }
1278
1279             // Find the best container node for this expression.
1280             // Sort by the tightest bounds so we can walk from specific to general nodes.
1281             nodes.sort((a, b) => {
1282                 let aLength = a.range[1] - a.range[0];
1283                 let bLength = b.range[1] - b.range[0];
1284                 return aLength - bLength;
1285             });
1286
1287             let characterAtOffsetIsDotOrBracket = characterAtOffset === "." || characterAtOffset === "[";
1288
1289             for (let i = 0; i < nodes.length; ++i) {
1290                 let node = nodes[i];
1291
1292                 // In a function call.
1293                 if (node.type === WebInspector.ScriptSyntaxTree.NodeType.CallExpression
1294                     || node.type === WebInspector.ScriptSyntaxTree.NodeType.NewExpression
1295                     || node.type === WebInspector.ScriptSyntaxTree.NodeType.ThrowStatement) {
1296                     callback(convertRangeOffsetsToSourceCodeOffsets(node.range));
1297                     return;
1298                 }
1299
1300                 // In the middle of a member expression we want to highlight the best
1301                 // member expression range. We can end up in the middle when we are
1302                 // paused inside of a getter and select the parent call frame. For
1303                 // these cases we may be at a '.' or '[' and we can find the best member
1304                 // expression from there.
1305                 //
1306                 // Examples:
1307                 //
1308                 //     foo*.x.y.z => inside x looking at parent call frame => |foo.x|.y.z
1309                 //     foo.x*.y.z => inside y looking at parent call frame => |foo.x.y|.z
1310                 //
1311                 //     foo*["x"]["y"]["z"] => inside x looking at parent call frame => |foo["x"]|["y"]["z"]
1312                 //     foo["x"]*["y"]["z"] => inside y looking at parent call frame => |foo["x"]["y"]|["z"]
1313                 //
1314                 if (node.type === WebInspector.ScriptSyntaxTree.NodeType.ThisExpression
1315                     || (characterAtOffsetIsDotOrBracket && (node.type === WebInspector.ScriptSyntaxTree.NodeType.Identifier || node.type === WebInspector.ScriptSyntaxTree.NodeType.MemberExpression))) {
1316                     let memberExpressionNode = null;
1317                     for (let j = i + 1; j < nodes.length; ++j) {
1318                         let nextNode = nodes[j];
1319                         if (nextNode.type === WebInspector.ScriptSyntaxTree.NodeType.MemberExpression) {
1320                             memberExpressionNode = nextNode;
1321                             if (offset === memberExpressionNode.range[1])
1322                                 continue;
1323                         }
1324                         break;
1325                     }
1326
1327                     if (memberExpressionNode) {
1328                         callback(convertRangeOffsetsToSourceCodeOffsets(memberExpressionNode.range));
1329                         return;
1330                     }
1331
1332                     callback(convertRangeOffsetsToSourceCodeOffsets(node.range));
1333                     return;
1334                 }
1335             }
1336
1337             // No matches, just highlight the line.
1338             callback(null);
1339         });
1340     }
1341
1342     _clearWidgets()
1343     {
1344         for (var widget of this._widgetMap.values())
1345             widget.clear();
1346
1347         this._widgetMap.clear();
1348     }
1349
1350     _reinsertAllIssues()
1351     {
1352         this._issuesLineNumberMap.clear();
1353         this._clearWidgets();
1354
1355         var issues = WebInspector.issueManager.issuesForSourceCode(this._sourceCode);
1356         for (var issue of issues)
1357             this._addIssue(issue);
1358     }
1359
1360     _debuggerDidPause(event)
1361     {
1362         this._updateTokenTrackingControllerState();
1363         if (this._typeTokenAnnotator && this._typeTokenAnnotator.isActive())
1364             this._typeTokenAnnotator.refresh();
1365         if (this._basicBlockAnnotator && this._basicBlockAnnotator.isActive())
1366             this._basicBlockAnnotator.refresh();
1367     }
1368
1369     _debuggerDidResume(event)
1370     {
1371         this._updateTokenTrackingControllerState();
1372         this._dismissPopover();
1373         if (this._typeTokenAnnotator && this._typeTokenAnnotator.isActive())
1374             this._typeTokenAnnotator.refresh();
1375         if (this._basicBlockAnnotator && this._basicBlockAnnotator.isActive())
1376             this._basicBlockAnnotator.refresh();
1377     }
1378
1379     _sourceCodeSourceMapAdded(event)
1380     {
1381         WebInspector.notifications.addEventListener(WebInspector.Notification.GlobalModifierKeysDidChange, this._updateTokenTrackingControllerState, this);
1382         this._sourceCode.removeEventListener(WebInspector.SourceCode.Event.SourceMapAdded, this._sourceCodeSourceMapAdded, this);
1383
1384         this._updateTokenTrackingControllerState();
1385     }
1386
1387     _updateTokenTrackingControllerState()
1388     {
1389         var mode = WebInspector.CodeMirrorTokenTrackingController.Mode.None;
1390         if (WebInspector.debuggerManager.paused)
1391             mode = WebInspector.CodeMirrorTokenTrackingController.Mode.JavaScriptExpression;
1392         else if (this._typeTokenAnnotator && this._typeTokenAnnotator.isActive())
1393             mode = WebInspector.CodeMirrorTokenTrackingController.Mode.JavaScriptTypeInformation;
1394         else if (this._hasColorMarkers())
1395             mode = WebInspector.CodeMirrorTokenTrackingController.Mode.MarkedTokens;
1396         else if ((this._sourceCode instanceof WebInspector.SourceMapResource || this._sourceCode.sourceMaps.length !== 0) && WebInspector.modifierKeys.metaKey && !WebInspector.modifierKeys.altKey && !WebInspector.modifierKeys.shiftKey)
1397             mode = WebInspector.CodeMirrorTokenTrackingController.Mode.NonSymbolTokens;
1398
1399         this.tokenTrackingController.enabled = mode !== WebInspector.CodeMirrorTokenTrackingController.Mode.None;
1400
1401         if (mode === this.tokenTrackingController.mode)
1402             return;
1403
1404         switch (mode) {
1405         case WebInspector.CodeMirrorTokenTrackingController.Mode.MarkedTokens:
1406             this.tokenTrackingController.mouseOverDelayDuration = 0;
1407             this.tokenTrackingController.mouseOutReleaseDelayDuration = 0;
1408             break;
1409         case WebInspector.CodeMirrorTokenTrackingController.Mode.NonSymbolTokens:
1410             this.tokenTrackingController.mouseOverDelayDuration = 0;
1411             this.tokenTrackingController.mouseOutReleaseDelayDuration = 0;
1412             this.tokenTrackingController.classNameForHighlightedRange = WebInspector.CodeMirrorTokenTrackingController.JumpToSymbolHighlightStyleClassName;
1413             this._dismissPopover();
1414             break;
1415         case WebInspector.CodeMirrorTokenTrackingController.Mode.JavaScriptExpression:
1416         case WebInspector.CodeMirrorTokenTrackingController.Mode.JavaScriptTypeInformation:
1417             this.tokenTrackingController.mouseOverDelayDuration = WebInspector.SourceCodeTextEditor.DurationToMouseOverTokenToMakeHoveredToken;
1418             this.tokenTrackingController.mouseOutReleaseDelayDuration = WebInspector.SourceCodeTextEditor.DurationToMouseOutOfHoveredTokenToRelease;
1419             this.tokenTrackingController.classNameForHighlightedRange = WebInspector.SourceCodeTextEditor.HoveredExpressionHighlightStyleClassName;
1420             break;
1421         }
1422
1423         this.tokenTrackingController.mode = mode;
1424     }
1425
1426     _hasColorMarkers()
1427     {
1428         for (var marker of this.markers) {
1429             if (marker.type === WebInspector.TextMarker.Type.Color)
1430                 return true;
1431         }
1432         return false;
1433     }
1434
1435     // CodeMirrorTokenTrackingController Delegate
1436
1437     tokenTrackingControllerCanReleaseHighlightedRange(tokenTrackingController, element)
1438     {
1439         if (!this._popover)
1440             return true;
1441
1442         if (!window.getSelection().isCollapsed && this._popover.element.contains(window.getSelection().anchorNode))
1443             return false;
1444
1445         return true;
1446     }
1447
1448     tokenTrackingControllerHighlightedRangeReleased(tokenTrackingController, forceHide = false)
1449     {
1450         if (forceHide || !this._mouseIsOverPopover)
1451             this._dismissPopover();
1452     }
1453
1454     tokenTrackingControllerHighlightedRangeWasClicked(tokenTrackingController)
1455     {
1456         if (this.tokenTrackingController.mode !== WebInspector.CodeMirrorTokenTrackingController.Mode.NonSymbolTokens)
1457             return;
1458
1459         // Links are handled by TextEditor.
1460         if (/\blink\b/.test(this.tokenTrackingController.candidate.hoveredToken.type))
1461             return;
1462
1463         var sourceCodeLocation = this._sourceCodeLocationForEditorPosition(this.tokenTrackingController.candidate.hoveredTokenRange.start);
1464         if (this.sourceCode instanceof WebInspector.SourceMapResource)
1465             WebInspector.showOriginalOrFormattedSourceCodeLocation(sourceCodeLocation);
1466         else
1467             WebInspector.showSourceCodeLocation(sourceCodeLocation);
1468     }
1469
1470     tokenTrackingControllerNewHighlightCandidate(tokenTrackingController, candidate)
1471     {
1472         if (this.tokenTrackingController.mode === WebInspector.CodeMirrorTokenTrackingController.Mode.NonSymbolTokens) {
1473             this.tokenTrackingController.highlightRange(candidate.hoveredTokenRange);
1474             return;
1475         }
1476
1477         if (this.tokenTrackingController.mode === WebInspector.CodeMirrorTokenTrackingController.Mode.JavaScriptExpression) {
1478             this._tokenTrackingControllerHighlightedJavaScriptExpression(candidate);
1479             return;
1480         }
1481
1482         if (this.tokenTrackingController.mode === WebInspector.CodeMirrorTokenTrackingController.Mode.JavaScriptTypeInformation) {
1483             this._tokenTrackingControllerHighlightedJavaScriptTypeInformation(candidate);
1484             return;
1485         }
1486
1487         if (this.tokenTrackingController.mode === WebInspector.CodeMirrorTokenTrackingController.Mode.MarkedTokens) {
1488             var markers = this.markersAtPosition(candidate.hoveredTokenRange.start);
1489             if (markers.length > 0)
1490                 this._tokenTrackingControllerHighlightedMarkedExpression(candidate, markers);
1491             else
1492                 this._dismissEditingController();
1493         }
1494     }
1495
1496     tokenTrackingControllerMouseOutOfHoveredMarker(tokenTrackingController, hoveredMarker)
1497     {
1498         this._dismissEditingController();
1499     }
1500
1501     _tokenTrackingControllerHighlightedJavaScriptExpression(candidate)
1502     {
1503         console.assert(candidate.expression);
1504
1505         function populate(error, result, wasThrown)
1506         {
1507             if (error || wasThrown)
1508                 return;
1509
1510             if (candidate !== this.tokenTrackingController.candidate)
1511                 return;
1512
1513             var data = WebInspector.RemoteObject.fromPayload(result);
1514             switch (data.type) {
1515             case "function":
1516                 this._showPopoverForFunction(data);
1517                 break;
1518             case "object":
1519                 if (data.subtype === "null" || data.subtype === "regexp")
1520                     this._showPopoverWithFormattedValue(data);
1521                 else
1522                     this._showPopoverForObject(data);
1523                 break;
1524             case "string":
1525             case "number":
1526             case "boolean":
1527             case "undefined":
1528             case "symbol":
1529                 this._showPopoverWithFormattedValue(data);
1530                 break;
1531             }
1532         }
1533
1534         var expression = appendWebInspectorSourceURL(candidate.expression);
1535
1536         if (WebInspector.debuggerManager.activeCallFrame) {
1537             DebuggerAgent.evaluateOnCallFrame.invoke({callFrameId: WebInspector.debuggerManager.activeCallFrame.id, expression, objectGroup: "popover", doNotPauseOnExceptionsAndMuteConsole: true}, populate.bind(this));
1538             return;
1539         }
1540
1541         // No call frame available. Use the main page's context.
1542         RuntimeAgent.evaluate.invoke({expression, objectGroup: "popover", doNotPauseOnExceptionsAndMuteConsole: true}, populate.bind(this));
1543     }
1544
1545     _tokenTrackingControllerHighlightedJavaScriptTypeInformation(candidate)
1546     {
1547         console.assert(candidate.expression);
1548
1549         var sourceCode = this._sourceCode;
1550         var sourceID = sourceCode instanceof WebInspector.Script ? sourceCode.id : sourceCode.scripts[0].id;
1551         var range = candidate.hoveredTokenRange;
1552         var offset = this.currentPositionToOriginalOffset(range.start);
1553
1554         var allRequests = [{
1555             typeInformationDescriptor: WebInspector.ScriptSyntaxTree.TypeProfilerSearchDescriptor.NormalExpression,
1556             sourceID,
1557             divot: offset
1558         }];
1559
1560         function handler(error, allTypes) {
1561             if (error)
1562                 return;
1563
1564             if (candidate !== this.tokenTrackingController.candidate)
1565                 return;
1566
1567             console.assert(allTypes.length === 1);
1568             if (!allTypes.length)
1569                 return;
1570
1571             var typeDescription = WebInspector.TypeDescription.fromPayload(allTypes[0]);
1572             if (typeDescription.valid) {
1573                 var popoverTitle = WebInspector.TypeTokenView.titleForPopover(WebInspector.TypeTokenView.TitleType.Variable, candidate.expression);
1574                 this.showPopoverForTypes(typeDescription, null, popoverTitle);
1575             }
1576         }
1577
1578         RuntimeAgent.getRuntimeTypesForVariablesAtOffsets(allRequests, handler.bind(this));
1579     }
1580
1581     _showPopover(content, bounds)
1582     {
1583         console.assert(this.tokenTrackingController.candidate || bounds);
1584
1585         var shouldHighlightRange = false;
1586         var candidate = this.tokenTrackingController.candidate;
1587         // If bounds is falsey, this is a popover introduced from a hover event.
1588         // Otherwise, this is called from TypeTokenAnnotator.
1589         if (!bounds) {
1590             if (!candidate)
1591                 return;
1592
1593             var rects = this.rectsForRange(candidate.hoveredTokenRange);
1594             bounds = WebInspector.Rect.unionOfRects(rects);
1595
1596             shouldHighlightRange = true;
1597         }
1598
1599         content.classList.add(WebInspector.SourceCodeTextEditor.PopoverDebuggerContentStyleClassName);
1600
1601         this._popover = this._popover || new WebInspector.Popover(this);
1602         this._popover.presentNewContentWithFrame(content, bounds.pad(5), [WebInspector.RectEdge.MIN_Y, WebInspector.RectEdge.MAX_Y, WebInspector.RectEdge.MAX_X]);
1603         if (shouldHighlightRange)
1604             this.tokenTrackingController.highlightRange(candidate.expressionRange);
1605
1606         this._trackPopoverEvents();
1607     }
1608
1609     _showPopoverForFunction(data)
1610     {
1611         let candidate = this.tokenTrackingController.candidate;
1612
1613         function didGetDetails(error, response)
1614         {
1615             if (error) {
1616                 console.error(error);
1617                 this._dismissPopover();
1618                 return;
1619             }
1620
1621             // Nothing to do if the token has changed since the time we
1622             // asked for the function details from the backend.
1623             if (candidate !== this.tokenTrackingController.candidate)
1624                 return;
1625
1626             let content = document.createElement("div");
1627             content.classList.add("function");
1628
1629             let title = document.createElement("div");
1630             title.classList.add("title");
1631             title.textContent = response.name || response.displayName || WebInspector.UIString("(anonymous function)");
1632             content.appendChild(title);
1633
1634             let location = response.location;
1635             let sourceCode = WebInspector.debuggerManager.scriptForIdentifier(location.scriptId);
1636             let sourceCodeLocation = sourceCode.createSourceCodeLocation(location.lineNumber, location.columnNumber);
1637             let functionSourceCodeLink = WebInspector.createSourceCodeLocationLink(sourceCodeLocation);
1638             title.appendChild(functionSourceCodeLink);
1639
1640             let wrapper = document.createElement("div");
1641             wrapper.classList.add("body");
1642             content.appendChild(wrapper);
1643
1644             let codeMirror = WebInspector.CodeMirrorEditor.create(wrapper, {
1645                 mode: "text/javascript",
1646                 readOnly: "nocursor",
1647             });
1648             codeMirror.on("update", () => {
1649                 this._popover.update();
1650             });
1651
1652             const isModule = false;
1653             const indentString = WebInspector.indentString();
1654             const includeSourceMapData = false;
1655             let workerProxy = WebInspector.FormatterWorkerProxy.singleton();
1656             workerProxy.formatJavaScript(data.description, isModule, indentString, includeSourceMapData, ({formattedText}) => {
1657                 codeMirror.setValue(formattedText || data.description);
1658             });
1659
1660             this._showPopover(content);
1661         }
1662         DebuggerAgent.getFunctionDetails(data.objectId, didGetDetails.bind(this));
1663     }
1664
1665     _showPopoverForObject(data)
1666     {
1667         var content = document.createElement("div");
1668         content.className = "object expandable";
1669
1670         var titleElement = document.createElement("div");
1671         titleElement.className = "title";
1672         titleElement.textContent = data.description;
1673         content.appendChild(titleElement);
1674
1675         if (data.subtype === "node") {
1676             data.pushNodeToFrontend(function(nodeId) {
1677                 if (!nodeId)
1678                     return;
1679
1680                 var domNode = WebInspector.domTreeManager.nodeForId(nodeId);
1681                 if (!domNode.ownerDocument)
1682                     return;
1683
1684                 var goToButton = titleElement.appendChild(WebInspector.createGoToArrowButton());
1685                 goToButton.addEventListener("click", function() {
1686                     WebInspector.domTreeManager.inspectElement(nodeId);
1687                 });
1688             });
1689         }
1690
1691         // FIXME: If this is a variable, it would be nice to put the variable name in the PropertyPath.
1692         var objectTree = new WebInspector.ObjectTreeView(data);
1693         objectTree.showOnlyProperties();
1694         objectTree.expand();
1695
1696         var bodyElement = content.appendChild(document.createElement("div"));
1697         bodyElement.className = "body";
1698         bodyElement.appendChild(objectTree.element);
1699
1700         // Show the popover once we have the first set of properties for the object.
1701         var candidate = this.tokenTrackingController.candidate;
1702         objectTree.addEventListener(WebInspector.ObjectTreeView.Event.Updated, function() {
1703             if (candidate === this.tokenTrackingController.candidate)
1704                 this._showPopover(content);
1705             objectTree.removeEventListener(null, null, this);
1706         }, this);
1707     }
1708
1709     _showPopoverWithFormattedValue(remoteObject)
1710     {
1711         var content = WebInspector.FormattedValue.createElementForRemoteObject(remoteObject);
1712         this._showPopover(content);
1713     }
1714
1715     willDismissPopover(popover)
1716     {
1717         this.tokenTrackingController.removeHighlightedRange();
1718
1719         RuntimeAgent.releaseObjectGroup("popover");
1720     }
1721
1722     _dismissPopover()
1723     {
1724         if (!this._popover)
1725             return;
1726
1727         this._popover.dismiss();
1728
1729         if (this._popoverEventListeners && this._popoverEventListenersAreRegistered) {
1730             this._popoverEventListenersAreRegistered = false;
1731             this._popoverEventListeners.unregister();
1732         }
1733     }
1734
1735     _trackPopoverEvents()
1736     {
1737         if (!this._popoverEventListeners)
1738             this._popoverEventListeners = new WebInspector.EventListenerSet(this, "Popover listeners");
1739         if (!this._popoverEventListenersAreRegistered) {
1740             this._popoverEventListenersAreRegistered = true;
1741             this._popoverEventListeners.register(this._popover.element, "mouseover", this._popoverMouseover);
1742             this._popoverEventListeners.register(this._popover.element, "mouseout", this._popoverMouseout);
1743             this._popoverEventListeners.install();
1744         }
1745     }
1746
1747     _popoverMouseover(event)
1748     {
1749         this._mouseIsOverPopover = true;
1750     }
1751
1752     _popoverMouseout(event)
1753     {
1754         this._mouseIsOverPopover = this._popover.element.contains(event.relatedTarget);
1755     }
1756
1757     _hasStyleSheetContents()
1758     {
1759         let mimeType = this.mimeType;
1760         return mimeType === "text/css"
1761             || mimeType === "text/x-less"
1762             || mimeType === "text/x-sass"
1763             || mimeType === "text/x-scss";
1764     }
1765
1766     _updateEditableMarkers(range)
1767     {
1768         if (this._hasStyleSheetContents()) {
1769             this.createColorMarkers(range);
1770             this.createGradientMarkers(range);
1771             this.createCubicBezierMarkers(range);
1772             this.createSpringMarkers(range);
1773         }
1774
1775         this._updateTokenTrackingControllerState();
1776     }
1777
1778     _tokenTrackingControllerHighlightedMarkedExpression(candidate, markers)
1779     {
1780         // Look for the outermost editable marker.
1781         var editableMarker;
1782         for (var marker of markers) {
1783             if (!marker.range || !Object.values(WebInspector.TextMarker.Type).includes(marker.type))
1784                 continue;
1785
1786             if (!editableMarker || (marker.range.startLine < editableMarker.range.startLine || (marker.range.startLine === editableMarker.range.startLine && marker.range.startColumn < editableMarker.range.startColumn)))
1787                 editableMarker = marker;
1788         }
1789
1790         if (!editableMarker) {
1791             this.tokenTrackingController.hoveredMarker = null;
1792             return;
1793         }
1794
1795         if (this.tokenTrackingController.hoveredMarker === editableMarker)
1796             return;
1797
1798         this._dismissEditingController();
1799
1800         this.tokenTrackingController.hoveredMarker = editableMarker;
1801
1802         this._editingController = this.editingControllerForMarker(editableMarker);
1803
1804         if (marker.type === WebInspector.TextMarker.Type.Color) {
1805             var color = this._editingController.value;
1806             if (!color || !color.valid) {
1807                 editableMarker.clear();
1808                 this._editingController = null;
1809                 return;
1810             }
1811         }
1812
1813         this._editingController.delegate = this;
1814         this._editingController.presentHoverMenu();
1815     }
1816
1817     _dismissEditingController(discrete)
1818     {
1819         if (this._editingController)
1820             this._editingController.dismissHoverMenu(discrete);
1821
1822         this.tokenTrackingController.hoveredMarker = null;
1823         this._editingController = null;
1824     }
1825
1826     // CodeMirrorEditingController Delegate
1827
1828     editingControllerDidStartEditing(editingController)
1829     {
1830         // We can pause the token tracking controller during editing, it will be reset
1831         // to the expected state by calling _updateEditableMarkers() in the
1832         // editingControllerDidFinishEditing delegate.
1833         this.tokenTrackingController.enabled = false;
1834
1835         // We clear the marker since we'll reset it after editing.
1836         editingController.marker.clear();
1837
1838         // We ignore content changes made as a result of color editing.
1839         this._ignoreContentDidChange++;
1840     }
1841
1842     editingControllerDidFinishEditing(editingController)
1843     {
1844         this._updateEditableMarkers(editingController.range);
1845
1846         this._ignoreContentDidChange--;
1847
1848         this._editingController = null;
1849     }
1850
1851     _setTypeTokenAnnotatorEnabledState(shouldActivate)
1852     {
1853         if (!this._typeTokenAnnotator)
1854             return;
1855
1856         if (shouldActivate) {
1857             console.assert(this.visible, "Annotators should not be enabled if the TextEditor is not visible");
1858             RuntimeAgent.enableTypeProfiler();
1859             this._typeTokenAnnotator.reset();
1860
1861             if (!this._typeTokenScrollHandler)
1862                 this._enableScrollEventsForTypeTokenAnnotator();
1863         } else {
1864             // Because we disable type profiling when exiting the inspector, there is no need to call
1865             // RuntimeAgent.disableTypeProfiler() here.  If we were to call it here, JavaScriptCore would
1866             // compile out all the necessary type profiling information, so if a user were to quickly press then
1867             // unpress the type profiling button, we wouldn't be able to re-show type information which would
1868             // provide a confusing user experience.
1869
1870             this._typeTokenAnnotator.clear();
1871
1872             if (this._typeTokenScrollHandler)
1873                 this._disableScrollEventsForTypeTokenAnnotator();
1874         }
1875
1876         WebInspector.showJavaScriptTypeInformationSetting.value = shouldActivate;
1877
1878         this._updateTokenTrackingControllerState();
1879     }
1880
1881     set _basicBlockAnnotatorEnabled(shouldActivate)
1882     {
1883         if (!this._basicBlockAnnotator)
1884             return;
1885
1886         if (shouldActivate) {
1887             console.assert(this.visible, "Annotators should not be enabled if the TextEditor is not visible");
1888
1889             RuntimeAgent.enableControlFlowProfiler();
1890
1891             console.assert(!this._basicBlockAnnotator.isActive());
1892             this._basicBlockAnnotator.reset();
1893
1894             if (!this._controlFlowScrollHandler)
1895                 this._enableScrollEventsForControlFlowAnnotator();
1896         } else {
1897             this._basicBlockAnnotator.clear();
1898
1899             if (this._controlFlowScrollHandler)
1900                 this._disableScrollEventsForControlFlowAnnotator();
1901         }
1902
1903         WebInspector.enableControlFlowProfilerSetting.value = shouldActivate;
1904     }
1905
1906     _getAssociatedScript(position)
1907     {
1908         let script = null;
1909
1910         if (this._sourceCode instanceof WebInspector.Script)
1911             script = this._sourceCode;
1912         else if (this._sourceCode instanceof WebInspector.Resource && this._sourceCode.scripts.length) {
1913             if (this._sourceCode.type === WebInspector.Resource.Type.Script)
1914                 script = this._sourceCode.scripts[0];
1915             else if (this._sourceCode.type === WebInspector.Resource.Type.Document && position) {
1916                 for (let inlineScript of this._sourceCode.scripts) {
1917                     if (inlineScript.range.contains(position.lineNumber, position.columnNumber)) {
1918                         if (isNaN(inlineScript.range.startOffset))
1919                             inlineScript.range.resolveOffsets(this._sourceCode.content);
1920                         script = inlineScript;
1921                         break;
1922                     }
1923                 }
1924             }
1925         }
1926
1927         return script;
1928     }
1929
1930     _createTypeTokenAnnotator()
1931     {
1932         // COMPATIBILITY (iOS 8): Runtime.getRuntimeTypesForVariablesAtOffsets did not exist yet.
1933         if (!RuntimeAgent.getRuntimeTypesForVariablesAtOffsets)
1934             return;
1935
1936         var script = this._getAssociatedScript();
1937         if (!script)
1938             return;
1939
1940         this._typeTokenAnnotator = new WebInspector.TypeTokenAnnotator(this, script);
1941     }
1942
1943     _createBasicBlockAnnotator()
1944     {
1945         // COMPATIBILITY (iOS 8): Runtime.getBasicBlocks did not exist yet.
1946         if (!RuntimeAgent.getBasicBlocks)
1947             return;
1948
1949         var script = this._getAssociatedScript();
1950         if (!script)
1951             return;
1952
1953         this._basicBlockAnnotator = new WebInspector.BasicBlockAnnotator(this, script);
1954     }
1955
1956     _enableScrollEventsForTypeTokenAnnotator()
1957     {
1958         // Pause updating type tokens while scrolling to prevent frame loss.
1959         console.assert(!this._typeTokenScrollHandler);
1960         this._typeTokenScrollHandler = this._createTypeTokenScrollEventHandler();
1961         this.addScrollHandler(this._typeTokenScrollHandler);
1962     }
1963
1964     _enableScrollEventsForControlFlowAnnotator()
1965     {
1966         console.assert(!this._controlFlowScrollHandler);
1967         this._controlFlowScrollHandler = this._createControlFlowScrollEventHandler();
1968         this.addScrollHandler(this._controlFlowScrollHandler);
1969     }
1970
1971     _disableScrollEventsForTypeTokenAnnotator()
1972     {
1973         console.assert(this._typeTokenScrollHandler);
1974         this.removeScrollHandler(this._typeTokenScrollHandler);
1975         this._typeTokenScrollHandler = null;
1976     }
1977
1978     _disableScrollEventsForControlFlowAnnotator()
1979     {
1980         console.assert(this._controlFlowScrollHandler);
1981         this.removeScrollHandler(this._controlFlowScrollHandler);
1982         this._controlFlowScrollHandler = null;
1983     }
1984
1985     _createTypeTokenScrollEventHandler()
1986     {
1987         let timeoutIdentifier = null;
1988         let scrollHandler = () => {
1989             if (timeoutIdentifier)
1990                 clearTimeout(timeoutIdentifier);
1991             else {
1992                 if (this._typeTokenAnnotator)
1993                     this._typeTokenAnnotator.pause();
1994             }
1995
1996             timeoutIdentifier = setTimeout(() => {
1997                 timeoutIdentifier = null;
1998                 if (this._typeTokenAnnotator)
1999                     this._typeTokenAnnotator.resume();
2000             }, WebInspector.SourceCodeTextEditor.DurationToUpdateTypeTokensAfterScrolling);
2001         };
2002
2003         return scrollHandler;
2004     }
2005
2006     _createControlFlowScrollEventHandler()
2007     {
2008         let timeoutIdentifier = null;
2009         let scrollHandler = () => {
2010             if (timeoutIdentifier)
2011                 clearTimeout(timeoutIdentifier);
2012             else if (this._basicBlockAnnotator)
2013                 this._basicBlockAnnotator.pause();
2014
2015             timeoutIdentifier = setTimeout(()  => {
2016                 timeoutIdentifier = null;
2017                 if (this._basicBlockAnnotator)
2018                     this._basicBlockAnnotator.resume();
2019             }, WebInspector.SourceCodeTextEditor.DurationToUpdateTypeTokensAfterScrolling);
2020         };
2021
2022         return scrollHandler;
2023     }
2024
2025     _logCleared(event)
2026     {
2027         for (let lineNumber of this._issuesLineNumberMap.keys()) {
2028             this.removeStyleClassFromLine(lineNumber, WebInspector.SourceCodeTextEditor.LineErrorStyleClassName);
2029             this.removeStyleClassFromLine(lineNumber, WebInspector.SourceCodeTextEditor.LineWarningStyleClassName);
2030         }
2031
2032         this._issuesLineNumberMap.clear();
2033         this._clearWidgets();
2034     }
2035 };
2036
2037 WebInspector.SourceCodeTextEditor.LineErrorStyleClassName = "error";
2038 WebInspector.SourceCodeTextEditor.LineWarningStyleClassName = "warning";
2039 WebInspector.SourceCodeTextEditor.PopoverDebuggerContentStyleClassName = "debugger-popover-content";
2040 WebInspector.SourceCodeTextEditor.HoveredExpressionHighlightStyleClassName = "hovered-expression-highlight";
2041 WebInspector.SourceCodeTextEditor.DurationToMouseOverTokenToMakeHoveredToken = 500;
2042 WebInspector.SourceCodeTextEditor.DurationToMouseOutOfHoveredTokenToRelease = 1000;
2043 WebInspector.SourceCodeTextEditor.DurationToUpdateTypeTokensAfterScrolling = 100;
2044 WebInspector.SourceCodeTextEditor.WidgetContainsMultipleIssuesSymbol = Symbol("source-code-widget-contains-multiple-issues");
2045
2046 WebInspector.SourceCodeTextEditor.Event = {
2047     ContentWillPopulate: "source-code-text-editor-content-will-populate",
2048     ContentDidPopulate: "source-code-text-editor-content-did-populate"
2049 };