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