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