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