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