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