Web Inspector: remove setting for Sources tab
[WebKit-https.git] / Source / WebInspectorUI / UserInterface / Views / SourceCodeTextEditor.js
1 /*
2  * Copyright (C) 2013-2018 Apple Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23  * THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 WI.SourceCodeTextEditor = class SourceCodeTextEditor extends WI.TextEditor
27 {
28     constructor(sourceCode)
29     {
30         console.assert(sourceCode instanceof WI.SourceCode);
31
32         super();
33
34         this.delegate = this;
35
36         this._sourceCode = sourceCode;
37         this._breakpointMap = {};
38         this._issuesLineNumberMap = new Map;
39         this._widgetMap = new Map;
40         this._contentPopulated = false;
41         this._invalidLineNumbers = {0: true};
42         this._requestingScriptContent = false;
43         this._activeCallFrameSourceCodeLocation = null;
44
45         this._threadLineNumberMap = new Map; // line -> [targets]
46         this._threadWidgetMap = new Map; // line -> widget
47         this._threadTargetMap = new Map; // target -> line
48
49         this._typeTokenScrollHandler = null;
50         this._typeTokenAnnotator = null;
51         this._basicBlockAnnotator = null;
52         this._editingController = null;
53
54         this._autoFormat = false;
55         this._isProbablyMinified = false;
56
57         this._ignoreContentDidChange = 0;
58         this._ignoreLocationUpdateBreakpoint = null;
59         this._ignoreBreakpointAddedBreakpoint = null;
60         this._ignoreBreakpointRemovedBreakpoint = null;
61         this._ignoreAllBreakpointLocationUpdates = false;
62
63         // FIXME: Currently this just jumps between resources and related source map resources. It doesn't "jump to symbol" yet.
64         this._updateTokenTrackingControllerState();
65
66         this.element.classList.add("source-code");
67
68         if (this._supportsDebugging) {
69             WI.Breakpoint.addEventListener(WI.Breakpoint.Event.DisabledStateDidChange, this._breakpointStatusDidChange, this);
70             WI.Breakpoint.addEventListener(WI.Breakpoint.Event.AutoContinueDidChange, this._breakpointStatusDidChange, this);
71             WI.Breakpoint.addEventListener(WI.Breakpoint.Event.ResolvedStateDidChange, this._breakpointStatusDidChange, this);
72             WI.Breakpoint.addEventListener(WI.Breakpoint.Event.LocationDidChange, this._updateBreakpointLocation, this);
73
74             WI.targetManager.addEventListener(WI.TargetManager.Event.TargetAdded, this._targetAdded, this);
75             WI.targetManager.addEventListener(WI.TargetManager.Event.TargetRemoved, this._targetRemoved, this);
76
77             WI.debuggerManager.addEventListener(WI.DebuggerManager.Event.BreakpointsEnabledDidChange, this._breakpointsEnabledDidChange, this);
78             WI.debuggerManager.addEventListener(WI.DebuggerManager.Event.BreakpointAdded, this._breakpointAdded, this);
79             WI.debuggerManager.addEventListener(WI.DebuggerManager.Event.BreakpointRemoved, this._breakpointRemoved, this);
80             WI.debuggerManager.addEventListener(WI.DebuggerManager.Event.CallFramesDidChange, this._callFramesDidChange, this);
81             WI.debuggerManager.addEventListener(WI.DebuggerManager.Event.ActiveCallFrameDidChange, this._activeCallFrameDidChange, this);
82
83             WI.debuggerManager.addEventListener(WI.DebuggerManager.Event.Paused, this._debuggerDidPause, this);
84             WI.debuggerManager.addEventListener(WI.DebuggerManager.Event.Resumed, this._debuggerDidResume, this);
85             if (WI.debuggerManager.activeCallFrame)
86                 this._debuggerDidPause();
87
88             this._activeCallFrameDidChange();
89         }
90
91         WI.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         // If we had partial content (such as inline script content) before we had full content, we
551         // will want to re-restore the revealed position now that we are populating with full content.
552         this.repeatReveal = !!this.string;
553
554         this._populateWithContent(content);
555
556         this.repeatReveal = false;
557     }
558
559     _breakpointStatusDidChange(event)
560     {
561         this._updateBreakpointStatus(event.target);
562     }
563
564     _breakpointsEnabledDidChange()
565     {
566         console.assert(this._supportsDebugging);
567
568         var breakpoints = WI.debuggerManager.breakpointsForSourceCode(this._sourceCode);
569         for (var breakpoint of breakpoints)
570             this._updateBreakpointStatus(breakpoint);
571     }
572
573     _updateBreakpointStatus(breakpoint)
574     {
575         console.assert(this._supportsDebugging);
576
577         if (!this._contentPopulated)
578             return;
579
580         if (!this._matchesBreakpoint(breakpoint))
581             return;
582
583         var lineInfo = this._editorLineInfoForSourceCodeLocation(breakpoint.sourceCodeLocation);
584         this.setBreakpointInfoForLineAndColumn(lineInfo.lineNumber, lineInfo.columnNumber, this._breakpointInfoForBreakpoint(breakpoint));
585     }
586
587     _updateBreakpointLocation(event)
588     {
589         console.assert(this._supportsDebugging);
590
591         if (!this._contentPopulated)
592             return;
593
594         var breakpoint = event.target;
595         if (!this._matchesBreakpoint(breakpoint))
596             return;
597
598         if (this._ignoreAllBreakpointLocationUpdates)
599             return;
600
601         if (breakpoint === this._ignoreLocationUpdateBreakpoint)
602             return;
603
604         var sourceCodeLocation = breakpoint.sourceCodeLocation;
605
606         if (this._sourceCode instanceof WI.SourceMapResource) {
607             // Update our breakpoint location if the display location changed.
608             if (sourceCodeLocation.displaySourceCode !== this._sourceCode)
609                 return;
610             var oldLineInfo = {lineNumber: event.data.oldDisplayLineNumber, columnNumber: event.data.oldDisplayColumnNumber};
611             var newLineInfo = {lineNumber: sourceCodeLocation.displayLineNumber, columnNumber: sourceCodeLocation.displayColumnNumber};
612         } else {
613             // Update our breakpoint location if the original location changed.
614             if (sourceCodeLocation.sourceCode !== this._sourceCode)
615                 return;
616             var oldLineInfo = {lineNumber: event.data.oldFormattedLineNumber, columnNumber: event.data.oldFormattedColumnNumber};
617             var newLineInfo = {lineNumber: sourceCodeLocation.formattedLineNumber, columnNumber: sourceCodeLocation.formattedColumnNumber};
618         }
619
620         var existingBreakpoint = this._breakpointForEditorLineInfo(oldLineInfo);
621         if (!existingBreakpoint)
622             return;
623
624         console.assert(breakpoint === existingBreakpoint);
625
626         this.setBreakpointInfoForLineAndColumn(oldLineInfo.lineNumber, oldLineInfo.columnNumber, null);
627         this.setBreakpointInfoForLineAndColumn(newLineInfo.lineNumber, newLineInfo.columnNumber, this._breakpointInfoForBreakpoint(breakpoint));
628
629         this._removeBreakpointWithEditorLineInfo(breakpoint, oldLineInfo);
630         this._addBreakpointWithEditorLineInfo(breakpoint, newLineInfo);
631     }
632
633     _breakpointAdded(event)
634     {
635         console.assert(this._supportsDebugging);
636
637         if (!this._contentPopulated)
638             return;
639
640         var breakpoint = event.data.breakpoint;
641         if (!this._matchesBreakpoint(breakpoint))
642             return;
643
644         if (breakpoint === this._ignoreBreakpointAddedBreakpoint)
645             return;
646
647         var lineInfo = this._editorLineInfoForSourceCodeLocation(breakpoint.sourceCodeLocation);
648         this._addBreakpointWithEditorLineInfo(breakpoint, lineInfo);
649         this.setBreakpointInfoForLineAndColumn(lineInfo.lineNumber, lineInfo.columnNumber, this._breakpointInfoForBreakpoint(breakpoint));
650     }
651
652     _breakpointRemoved(event)
653     {
654         console.assert(this._supportsDebugging);
655
656         if (!this._contentPopulated)
657             return;
658
659         var breakpoint = event.data.breakpoint;
660         if (!this._matchesBreakpoint(breakpoint))
661             return;
662
663         if (breakpoint === this._ignoreBreakpointRemovedBreakpoint)
664             return;
665
666         var lineInfo = this._editorLineInfoForSourceCodeLocation(breakpoint.sourceCodeLocation);
667         this._removeBreakpointWithEditorLineInfo(breakpoint, lineInfo);
668         this.setBreakpointInfoForLineAndColumn(lineInfo.lineNumber, lineInfo.columnNumber, null);
669     }
670
671     _targetAdded(event)
672     {
673         if (WI.targets.size === 2)
674             this._reinsertAllThreadIndicators();
675     }
676
677     _targetRemoved(event)
678     {
679         if (WI.targets.size === 1) {
680             // Back to one thread, remove thread indicators.
681             this._reinsertAllThreadIndicators();
682             return;
683         }
684
685         let target = event.data.target;
686         this._removeThreadIndicatorForTarget(target);
687     }
688
689     _callFramesDidChange(event)
690     {
691         if (WI.targets.size === 1)
692             return;
693
694         let target = event.data.target;
695         this._removeThreadIndicatorForTarget(target);
696         this._addThreadIndicatorForTarget(target);
697     }
698
699     _addThreadIndicatorForTarget(target)
700     {
701         let targetData = WI.debuggerManager.dataForTarget(target);
702         let topCallFrame = targetData.callFrames[0];
703         if (!topCallFrame)
704             return;
705
706         let sourceCodeLocation = topCallFrame.sourceCodeLocation;
707         console.assert(sourceCodeLocation, "Expected source code location to place thread indicator.");
708         if (!sourceCodeLocation)
709             return;
710
711         if (!this._looselyMatchesSourceCodeLocation(sourceCodeLocation))
712             return;
713
714         let lineNumberWithIndicator = sourceCodeLocation.formattedLineNumber;
715         this._threadTargetMap.set(target, lineNumberWithIndicator);
716
717         let threads = this._threadLineNumberMap.get(lineNumberWithIndicator);
718         if (!threads) {
719             threads = [];
720             this._threadLineNumberMap.set(lineNumberWithIndicator, threads);
721         }
722         threads.push(target);
723
724         let widget = this._threadIndicatorWidgetForLine(target, lineNumberWithIndicator);
725         this._updateThreadIndicatorWidget(widget, threads);
726
727         this.addStyleClassToLine(lineNumberWithIndicator, "thread-indicator");
728     }
729
730     _removeThreadIndicatorForTarget(target)
731     {
732         let lineNumberWithIndicator = this._threadTargetMap.take(target);
733         if (lineNumberWithIndicator === undefined)
734             return;
735
736         let threads = this._threadLineNumberMap.get(lineNumberWithIndicator);
737         threads.remove(target);
738         if (threads.length) {
739             let widget = this._threadWidgetMap.get(lineNumberWithIndicator);
740             this._updateThreadIndicatorWidget(widget, threads);
741             return;
742         }
743
744         this._threadLineNumberMap.delete(lineNumberWithIndicator);
745
746         let widget = this._threadWidgetMap.take(lineNumberWithIndicator);
747         if (widget)
748             widget.clear();
749
750         this.removeStyleClassFromLine(lineNumberWithIndicator, "thread-indicator");
751     }
752
753     _threadIndicatorWidgetForLine(target, lineNumber)
754     {
755         let widget = this._threadWidgetMap.get(lineNumber);
756         if (widget)
757             return widget;
758
759         widget = this.createWidgetForLine(lineNumber);
760         if (!widget)
761             return null;
762
763         let widgetElement = widget.widgetElement;
764         widgetElement.classList.add("line-indicator-widget", "thread-widget", "inline");
765         widgetElement.addEventListener("click", this._handleThreadIndicatorWidgetClick.bind(this, widget, lineNumber));
766
767         this._threadWidgetMap.set(lineNumber, widget);
768
769         return widget;
770     }
771
772     _updateThreadIndicatorWidget(widget, threads)
773     {
774         if (!widget)
775             return;
776
777         console.assert(WI.targets.size > 1);
778
779         let widgetElement = widget.widgetElement;
780         widgetElement.removeChildren();
781
782         widget[WI.SourceCodeTextEditor.WidgetContainsMultipleThreadsSymbol] = threads.length > 1;
783
784         if (widgetElement.classList.contains("inline") || threads.length === 1) {
785             let textElement = widgetElement.appendChild(document.createElement("span"));
786             textElement.className = "text";
787             textElement.textContent = threads.length === 1 ? threads[0].displayName : WI.UIString("%d Threads").format(threads.length);
788         } else {
789             for (let target of threads) {
790                 let textElement = widgetElement.appendChild(document.createElement("span"));
791                 textElement.className = "text";
792                 textElement.textContent = target.displayName;
793
794                 widgetElement.appendChild(document.createElement("br"));
795             }
796         }
797
798         widget.update();
799     }
800
801     _handleThreadIndicatorWidgetClick(widget, lineNumber, event)
802     {
803         if (!this._isWidgetToggleable(widget))
804             return;
805
806         widget.widgetElement.classList.toggle("inline");
807
808         let threads = this._threadLineNumberMap.get(lineNumber);
809         this._updateThreadIndicatorWidget(widget, threads);
810     }
811
812     _activeCallFrameDidChange()
813     {
814         console.assert(this._supportsDebugging);
815
816         if (this._activeCallFrameSourceCodeLocation) {
817             this._activeCallFrameSourceCodeLocation.removeEventListener(WI.SourceCodeLocation.Event.LocationChanged, this._activeCallFrameSourceCodeLocationChanged, this);
818             this._activeCallFrameSourceCodeLocation = null;
819         }
820
821         let activeCallFrame = WI.debuggerManager.activeCallFrame;
822         if (!activeCallFrame || !this._matchesSourceCodeLocation(activeCallFrame.sourceCodeLocation)) {
823             this.setExecutionLineAndColumn(NaN, NaN);
824             return;
825         }
826
827         this._dismissPopover();
828
829         this._activeCallFrameSourceCodeLocation = activeCallFrame.sourceCodeLocation;
830         this._activeCallFrameSourceCodeLocation.addEventListener(WI.SourceCodeLocation.Event.LocationChanged, this._activeCallFrameSourceCodeLocationChanged, this);
831
832         // Don't return early if the line number didn't change. The execution state still
833         // could have changed (e.g. continuing in a loop with a breakpoint inside).
834
835         let lineInfo = this._editorLineInfoForSourceCodeLocation(activeCallFrame.sourceCodeLocation);
836         this.setExecutionLineAndColumn(lineInfo.lineNumber, lineInfo.columnNumber);
837
838         // If we have full content or this source code isn't a Resource we can return early.
839         // Script source code populates from the request started in the constructor.
840         if (this._fullContentPopulated || !(this._sourceCode instanceof WI.Resource) || this._requestingScriptContent)
841             return;
842
843         // Since we are paused in the debugger we need to show some content, and since the Resource
844         // content hasn't populated yet we need to populate with content from the Scripts by URL.
845         // Document resources will attempt to populate the scripts as inline (in <script> tags.)
846         // Other resources are assumed to be full scripts (JavaScript resources).
847         if (this._sourceCode.type === WI.Resource.Type.Document)
848             this._populateWithInlineScriptContent();
849         else
850             this._populateWithScriptContent();
851     }
852
853     _activeCallFrameSourceCodeLocationChanged(event)
854     {
855         console.assert(!isNaN(this.executionLineNumber));
856         if (isNaN(this.executionLineNumber))
857             return;
858
859         console.assert(WI.debuggerManager.activeCallFrame);
860         console.assert(this._activeCallFrameSourceCodeLocation === WI.debuggerManager.activeCallFrame.sourceCodeLocation);
861
862         var lineInfo = this._editorLineInfoForSourceCodeLocation(this._activeCallFrameSourceCodeLocation);
863         this.setExecutionLineAndColumn(lineInfo.lineNumber, lineInfo.columnNumber);
864     }
865
866     _populateWithInlineScriptContent()
867     {
868         console.assert(this._sourceCode instanceof WI.Resource);
869         console.assert(!this._fullContentPopulated);
870         console.assert(!this._requestingScriptContent);
871
872         var scripts = this._sourceCode.scripts;
873         console.assert(scripts.length);
874         if (!scripts.length)
875             return;
876
877         var pendingRequestCount = scripts.length;
878
879         // If the number of scripts hasn't change since the last populate, then there is nothing to do.
880         if (this._inlineScriptContentPopulated === pendingRequestCount)
881             return;
882
883         this._inlineScriptContentPopulated = pendingRequestCount;
884
885         function scriptContentAvailable(parameters)
886         {
887             // Return early if we are still waiting for content from other scripts.
888             if (--pendingRequestCount)
889                 return;
890
891             this._requestingScriptContent = false;
892
893             // Abort if the full content populated while waiting for these async callbacks.
894             if (this._fullContentPopulated)
895                 return;
896
897             var scriptOpenTag = "<script>";
898             var scriptCloseTag = "</script>";
899
900             var content = "";
901             var lineNumber = 0;
902             var columnNumber = 0;
903
904             this._invalidLineNumbers = {};
905
906             for (var i = 0; i < scripts.length; ++i) {
907                 // Fill the line gap with newline characters.
908                 for (var newLinesCount = scripts[i].range.startLine - lineNumber; newLinesCount > 0; --newLinesCount) {
909                     if (!columnNumber)
910                         this._invalidLineNumbers[scripts[i].range.startLine - newLinesCount] = true;
911                     columnNumber = 0;
912                     content += "\n";
913                 }
914
915                 // Fill the column gap with space characters.
916                 for (var spacesCount = scripts[i].range.startColumn - columnNumber - scriptOpenTag.length; spacesCount > 0; --spacesCount)
917                     content += " ";
918
919                 // Add script tags and content.
920                 content += scriptOpenTag;
921                 content += scripts[i].content;
922                 content += scriptCloseTag;
923
924                 lineNumber = scripts[i].range.endLine;
925                 columnNumber = scripts[i].range.endColumn + scriptCloseTag.length;
926             }
927
928             this._populateWithContent(content);
929         }
930
931         this._requestingScriptContent = true;
932
933         var boundScriptContentAvailable = scriptContentAvailable.bind(this);
934         for (var i = 0; i < scripts.length; ++i)
935             scripts[i].requestContent().then(boundScriptContentAvailable);
936     }
937
938     _populateWithScriptContent()
939     {
940         console.assert(this._sourceCode instanceof WI.Resource);
941         console.assert(!this._fullContentPopulated);
942         console.assert(!this._requestingScriptContent);
943
944         // We can assume this resource only has one script that starts at line/column 0.
945         var scripts = this._sourceCode.scripts;
946         console.assert(scripts.length === 1);
947         if (!scripts.length)
948             return;
949
950         console.assert(scripts[0].range.startLine === 0);
951         console.assert(scripts[0].range.startColumn === 0);
952
953         function scriptContentAvailable(parameters)
954         {
955             var content = parameters.content;
956             this._requestingScriptContent = false;
957
958             // Abort if the full content populated while waiting for this async callback.
959             if (this._fullContentPopulated)
960                 return;
961
962             // This is the full content.
963             this._fullContentPopulated = true;
964
965             this._populateWithContent(content);
966         }
967
968         this._requestingScriptContent = true;
969
970         scripts[0].requestContent().then(scriptContentAvailable.bind(this));
971     }
972
973     _looselyMatchesSourceCodeLocation(sourceCodeLocation)
974     {
975         if (this._sourceCode instanceof WI.SourceMapResource)
976             return sourceCodeLocation.displaySourceCode === this._sourceCode;
977         if (this._sourceCode instanceof WI.Resource || this._sourceCode instanceof WI.Script || this._sourceCode instanceof WI.CSSStyleSheet)
978             return sourceCodeLocation.sourceCode.url === this._sourceCode.url;
979         return false;
980     }
981
982     _matchesSourceCodeLocation(sourceCodeLocation)
983     {
984         if (this._sourceCode instanceof WI.SourceMapResource)
985             return sourceCodeLocation.displaySourceCode === this._sourceCode;
986         if (this._sourceCode instanceof WI.Resource || this._sourceCode instanceof WI.CSSStyleSheet)
987             return sourceCodeLocation.sourceCode.url === this._sourceCode.url;
988         if (this._sourceCode instanceof WI.Script)
989             return sourceCodeLocation.sourceCode === this._sourceCode;
990         return false;
991     }
992
993     _matchesBreakpoint(breakpoint)
994     {
995         console.assert(this._supportsDebugging);
996         if (this._sourceCode instanceof WI.SourceMapResource)
997             return breakpoint.sourceCodeLocation.displaySourceCode === this._sourceCode;
998         if (this._sourceCode instanceof WI.Resource)
999             return breakpoint.contentIdentifier === this._sourceCode.contentIdentifier;
1000         if (this._sourceCode instanceof WI.Script)
1001             return breakpoint.contentIdentifier === this._sourceCode.contentIdentifier || breakpoint.scriptIdentifier === this._sourceCode.id;
1002         return false;
1003     }
1004
1005     _issueWasAdded(event)
1006     {
1007         var issue = event.data.issue;
1008         if (!WI.IssueManager.issueMatchSourceCode(issue, this._sourceCode))
1009             return;
1010
1011         this._addIssue(issue);
1012     }
1013
1014     _addIssue(issue)
1015     {
1016         var sourceCodeLocation = issue.sourceCodeLocation;
1017         console.assert(sourceCodeLocation, "Expected source code location to place issue.");
1018         if (!sourceCodeLocation)
1019             return;
1020
1021         var lineNumber = sourceCodeLocation.formattedLineNumber;
1022
1023         var lineNumberIssues = this._issuesLineNumberMap.get(lineNumber);
1024         if (!lineNumberIssues) {
1025             lineNumberIssues = [];
1026             this._issuesLineNumberMap.set(lineNumber, lineNumberIssues);
1027         }
1028
1029         // Avoid displaying duplicate issues on the same line.
1030         for (var existingIssue of lineNumberIssues) {
1031             if (existingIssue.sourceCodeLocation.columnNumber === sourceCodeLocation.columnNumber && existingIssue.text === issue.text)
1032                 return;
1033         }
1034
1035         lineNumberIssues.push(issue);
1036
1037         if (issue.level === WI.IssueMessage.Level.Error)
1038             this.addStyleClassToLine(lineNumber, WI.SourceCodeTextEditor.LineErrorStyleClassName);
1039         else if (issue.level === WI.IssueMessage.Level.Warning)
1040             this.addStyleClassToLine(lineNumber, WI.SourceCodeTextEditor.LineWarningStyleClassName);
1041         else
1042             console.error("Unknown issue level");
1043
1044         var widget = this._issueWidgetForLine(lineNumber);
1045         if (widget) {
1046             if (issue.level === WI.IssueMessage.Level.Error)
1047                 widget.widgetElement.classList.add(WI.SourceCodeTextEditor.LineErrorStyleClassName);
1048             else if (issue.level === WI.IssueMessage.Level.Warning)
1049                 widget.widgetElement.classList.add(WI.SourceCodeTextEditor.LineWarningStyleClassName);
1050
1051             this._updateIssueWidgetForIssues(widget, lineNumberIssues);
1052         }
1053     }
1054
1055     _issueWidgetForLine(lineNumber)
1056     {
1057         var widget = this._widgetMap.get(lineNumber);
1058         if (widget)
1059             return widget;
1060
1061         widget = this.createWidgetForLine(lineNumber);
1062         if (!widget)
1063             return null;
1064
1065         var widgetElement = widget.widgetElement;
1066         widgetElement.classList.add("line-indicator-widget", "issue-widget", "inline");
1067         widgetElement.addEventListener("click", this._handleWidgetClick.bind(this, widget, lineNumber));
1068
1069         this._widgetMap.set(lineNumber, widget);
1070
1071         return widget;
1072     }
1073
1074     _iconClassNameForIssueLevel(level)
1075     {
1076         if (level === WI.IssueMessage.Level.Warning)
1077             return "icon-warning";
1078
1079         console.assert(level === WI.IssueMessage.Level.Error);
1080         return "icon-error";
1081     }
1082
1083     _updateIssueWidgetForIssues(widget, issues)
1084     {
1085         var widgetElement = widget.widgetElement;
1086         widgetElement.removeChildren();
1087
1088         if (widgetElement.classList.contains("inline") || issues.length === 1) {
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.isShowingDebuggerTab()) {
1252                 contextMenu.appendSeparator();
1253                 contextMenu.appendItem(WI.UIString("Reveal in Debugger Tab"), () => {
1254                     WI.showDebuggerTab({breakpointToSelect: breakpoints[0]});
1255                 });
1256             }
1257
1258             return;
1259         }
1260
1261         // Multiple breakpoints.
1262         let removeBreakpoints = () => {
1263             for (let breakpoint of breakpoints) {
1264                 if (WI.debuggerManager.isBreakpointRemovable(breakpoint))
1265                     WI.debuggerManager.removeBreakpoint(breakpoint);
1266             }
1267         };
1268
1269         let shouldDisable = breakpoints.some((breakpoint) => !breakpoint.disabled);
1270         let toggleBreakpoints = (shouldDisable) => {
1271             for (let breakpoint of breakpoints)
1272                 breakpoint.disabled = shouldDisable;
1273         };
1274
1275         if (shouldDisable)
1276             contextMenu.appendItem(WI.UIString("Disable Breakpoints"), toggleBreakpoints);
1277         else
1278             contextMenu.appendItem(WI.UIString("Enable Breakpoints"), toggleBreakpoints);
1279         contextMenu.appendItem(WI.UIString("Delete Breakpoints"), removeBreakpoints);
1280     }
1281
1282     textEditorBreakpointAdded(textEditor, lineNumber, columnNumber)
1283     {
1284         if (!this._supportsDebugging)
1285             return null;
1286
1287         var editorLineInfo = {lineNumber, columnNumber};
1288         var unformattedLineInfo = this._unformattedLineInfoForEditorLineInfo(editorLineInfo);
1289         var sourceCodeLocation = this._sourceCode.createSourceCodeLocation(unformattedLineInfo.lineNumber, unformattedLineInfo.columnNumber);
1290         var breakpoint = new WI.Breakpoint(sourceCodeLocation);
1291
1292         var lineInfo = this._editorLineInfoForSourceCodeLocation(breakpoint.sourceCodeLocation);
1293         this._addBreakpointWithEditorLineInfo(breakpoint, lineInfo);
1294
1295         this._ignoreBreakpointAddedBreakpoint = breakpoint;
1296         const shouldSpeculativelyResolveBreakpoint = true;
1297         WI.debuggerManager.addBreakpoint(breakpoint, shouldSpeculativelyResolveBreakpoint);
1298         this._ignoreBreakpointAddedBreakpoint = null;
1299
1300         // Return the more accurate location and breakpoint info.
1301         return {
1302             breakpointInfo: this._breakpointInfoForBreakpoint(breakpoint),
1303             lineNumber: lineInfo.lineNumber,
1304             columnNumber: lineInfo.columnNumber
1305         };
1306     }
1307
1308     textEditorBreakpointRemoved(textEditor, lineNumber, columnNumber)
1309     {
1310         console.assert(this._supportsDebugging);
1311         if (!this._supportsDebugging)
1312             return;
1313
1314         var lineInfo = {lineNumber, columnNumber};
1315         var breakpoint = this._breakpointForEditorLineInfo(lineInfo);
1316         console.assert(breakpoint);
1317         if (!breakpoint)
1318             return;
1319
1320         this._removeBreakpointWithEditorLineInfo(breakpoint, lineInfo);
1321
1322         this._ignoreBreakpointRemovedBreakpoint = breakpoint;
1323         WI.debuggerManager.removeBreakpoint(breakpoint);
1324         this._ignoreBreakpointRemovedBreakpoint = null;
1325     }
1326
1327     textEditorBreakpointMoved(textEditor, oldLineNumber, oldColumnNumber, newLineNumber, newColumnNumber)
1328     {
1329         console.assert(this._supportsDebugging);
1330         if (!this._supportsDebugging)
1331             return;
1332
1333         var oldLineInfo = {lineNumber: oldLineNumber, columnNumber: oldColumnNumber};
1334         var breakpoint = this._breakpointForEditorLineInfo(oldLineInfo);
1335         console.assert(breakpoint);
1336         if (!breakpoint)
1337             return;
1338
1339         this._removeBreakpointWithEditorLineInfo(breakpoint, oldLineInfo);
1340
1341         var newLineInfo = {lineNumber: newLineNumber, columnNumber: newColumnNumber};
1342         var unformattedNewLineInfo = this._unformattedLineInfoForEditorLineInfo(newLineInfo);
1343         this._ignoreLocationUpdateBreakpoint = breakpoint;
1344         breakpoint.sourceCodeLocation.update(this._sourceCode, unformattedNewLineInfo.lineNumber, unformattedNewLineInfo.columnNumber);
1345         this._ignoreLocationUpdateBreakpoint = null;
1346
1347         var accurateNewLineInfo = this._editorLineInfoForSourceCodeLocation(breakpoint.sourceCodeLocation);
1348         this._addBreakpointWithEditorLineInfo(breakpoint, accurateNewLineInfo);
1349
1350         if (accurateNewLineInfo.lineNumber !== newLineInfo.lineNumber || accurateNewLineInfo.columnNumber !== newLineInfo.columnNumber)
1351             this.updateBreakpointLineAndColumn(newLineInfo.lineNumber, newLineInfo.columnNumber, accurateNewLineInfo.lineNumber, accurateNewLineInfo.columnNumber);
1352     }
1353
1354     textEditorBreakpointClicked(textEditor, lineNumber, columnNumber)
1355     {
1356         console.assert(this._supportsDebugging);
1357         if (!this._supportsDebugging)
1358             return;
1359
1360         var breakpoint = this._breakpointForEditorLineInfo({lineNumber, columnNumber});
1361         console.assert(breakpoint);
1362         if (!breakpoint)
1363             return;
1364
1365         breakpoint.cycleToNextMode();
1366     }
1367
1368     textEditorUpdatedFormatting(textEditor)
1369     {
1370         this._ignoreAllBreakpointLocationUpdates = true;
1371         this._sourceCode.formatterSourceMap = this.formatterSourceMap;
1372         this._ignoreAllBreakpointLocationUpdates = false;
1373
1374         // Always put the source map on both the Script and Resource if both exist. For example,
1375         // if this SourceCode is a Resource, then there might also be a Script. In the debugger,
1376         // the backend identifies call frames with Script line and column information, and the
1377         // Script needs the formatter source map to produce the proper display line and column.
1378         if (this._sourceCode instanceof WI.Resource && !(this._sourceCode instanceof WI.SourceMapResource)) {
1379             var scripts = this._sourceCode.scripts;
1380             for (var i = 0; i < scripts.length; ++i)
1381                 scripts[i].formatterSourceMap = this.formatterSourceMap;
1382         } else if (this._sourceCode instanceof WI.Script) {
1383             if (this._sourceCode.resource)
1384                 this._sourceCode.resource.formatterSourceMap = this.formatterSourceMap;
1385         }
1386
1387         // Some breakpoints / issues may have moved, some might not have. Just go through
1388         // and remove and reinsert all the breakpoints / issues.
1389
1390         var oldBreakpointMap = this._breakpointMap;
1391         this._breakpointMap = {};
1392
1393         for (var lineNumber in oldBreakpointMap) {
1394             for (var columnNumber in oldBreakpointMap[lineNumber]) {
1395                 var breakpoint = oldBreakpointMap[lineNumber][columnNumber];
1396                 var newLineInfo = this._editorLineInfoForSourceCodeLocation(breakpoint.sourceCodeLocation);
1397                 this._addBreakpointWithEditorLineInfo(breakpoint, newLineInfo);
1398                 this.setBreakpointInfoForLineAndColumn(lineNumber, columnNumber, null);
1399                 this.setBreakpointInfoForLineAndColumn(newLineInfo.lineNumber, newLineInfo.columnNumber, this._breakpointInfoForBreakpoint(breakpoint));
1400             }
1401         }
1402
1403         this._reinsertAllIssues();
1404         this._reinsertAllThreadIndicators();
1405     }
1406
1407     textEditorExecutionHighlightRange(currentPosition, callback)
1408     {
1409         let position = this.currentPositionToOriginalPosition(currentPosition);
1410
1411         let script = this._getAssociatedScript(position);
1412         if (!script) {
1413             callback(null);
1414             return;
1415         }
1416
1417         let {startLine, startColumn} = script.range;
1418
1419         function toInlineScriptPosition(position) {
1420             let columnNumber = position.lineNumber === startLine ? position.columnNumber - startColumn : position.columnNumber;
1421             return new WI.SourceCodePosition(position.lineNumber - startLine, columnNumber);
1422         }
1423
1424         function fromInlineScriptPosition(position) {
1425             let columnNumber = position.lineNumber ? position.columnNumber : position.columnNumber + startColumn;
1426             return new WI.SourceCodePosition(position.lineNumber + startLine, columnNumber);
1427         }
1428
1429         // When returning offsets, convert to offsets within the SourceCode being viewed.
1430         let highlightSourceCodeRange = (startPosition, endPosition) => {
1431             startPosition = fromInlineScriptPosition(startPosition).toCodeMirror();
1432             endPosition = fromInlineScriptPosition(endPosition).toCodeMirror();
1433             callback({startPosition, endPosition});
1434         };
1435
1436         script.requestScriptSyntaxTree((syntaxTree) => {
1437             // Convert to the position within the inline script before querying the AST.
1438             position = toInlineScriptPosition(position);
1439             let nodes = syntaxTree.containersOfPosition(position);
1440             if (!nodes.length) {
1441                 callback(null);
1442                 return;
1443             }
1444
1445             // Find a node starting at this offset.
1446             // Avoid highlighting the entire program if this is the start of the first statement.
1447             // Special case the assignment expression inside of a for..of and for..in to highlight a larger range.
1448             for (let node of nodes) {
1449                 if (node.startPosition.equals(position) && node.type !== WI.ScriptSyntaxTree.NodeType.Program) {
1450                     highlightSourceCodeRange(node.startPosition, node.endPosition);
1451                     return;
1452                 }
1453                 if (node.type === WI.ScriptSyntaxTree.NodeType.ForInStatement || node.type === WI.ScriptSyntaxTree.NodeType.ForOfStatement) {
1454                     if (node.left.startPosition.equals(position)) {
1455                         highlightSourceCodeRange(node.left.startPosition, node.right.endPosition);
1456                         return;
1457                     }
1458                 }
1459                 if (node.startPosition.isAfter(position))
1460                     break;
1461             }
1462
1463             // Find a node ending at this offset. (Leaving a block).
1464             // We check this after ensuring nothing starts with this offset,
1465             // as that would be more important.
1466             for (let node of nodes) {
1467                 if (node.endPosition.equals(position)) {
1468                     if (node.type === WI.ScriptSyntaxTree.NodeType.BlockStatement) {
1469                         // Closing brace of a block, only highlight the closing brace character.
1470                         highlightSourceCodeRange(position.offsetColumn(-1), position);
1471                         return;
1472                     }
1473                 }
1474                 if (node.startPosition.isAfter(position))
1475                     break;
1476             }
1477
1478             // Find the best container node for this expression.
1479             // Sort by the tightest bounds so we can walk from specific to general nodes.
1480             nodes.sort((a, b) => {
1481                 let aLength = a.range[1] - a.range[0];
1482                 let bLength = b.range[1] - b.range[0];
1483                 return aLength - bLength;
1484             });
1485
1486             let characterAtPosition = this.getTextInRange(currentPosition, currentPosition.offsetColumn(1));
1487             let characterAtPositionIsDotOrBracket = characterAtPosition === "." || characterAtPosition === "[";
1488
1489             for (let i = 0; i < nodes.length; ++i) {
1490                 let node = nodes[i];
1491
1492                 // In a function call.
1493                 if (node.type === WI.ScriptSyntaxTree.NodeType.CallExpression
1494                     || node.type === WI.ScriptSyntaxTree.NodeType.NewExpression
1495                     || node.type === WI.ScriptSyntaxTree.NodeType.ThrowStatement) {
1496                     highlightSourceCodeRange(node.startPosition, node.endPosition);
1497                     return;
1498                 }
1499
1500                 // In the middle of a member expression we want to highlight the best
1501                 // member expression range. We can end up in the middle when we are
1502                 // paused inside of a getter and select the parent call frame. For
1503                 // these cases we may be at a '.' or '[' and we can find the best member
1504                 // expression from there.
1505                 //
1506                 // Examples:
1507                 //
1508                 //     foo*.x.y.z => inside x looking at parent call frame => |foo.x|.y.z
1509                 //     foo.x*.y.z => inside y looking at parent call frame => |foo.x.y|.z
1510                 //
1511                 //     foo*["x"]["y"]["z"] => inside x looking at parent call frame => |foo["x"]|["y"]["z"]
1512                 //     foo["x"]*["y"]["z"] => inside y looking at parent call frame => |foo["x"]["y"]|["z"]
1513                 //
1514                 if (node.type === WI.ScriptSyntaxTree.NodeType.ThisExpression
1515                     || (characterAtPositionIsDotOrBracket && (node.type === WI.ScriptSyntaxTree.NodeType.Identifier || node.type === WI.ScriptSyntaxTree.NodeType.MemberExpression))) {
1516                     let memberExpressionNode = null;
1517                     for (let j = i + 1; j < nodes.length; ++j) {
1518                         let nextNode = nodes[j];
1519                         if (nextNode.type === WI.ScriptSyntaxTree.NodeType.MemberExpression) {
1520                             memberExpressionNode = nextNode;
1521                             if (position.equals(memberExpressionNode.endPosition))
1522                                 continue;
1523                         }
1524                         break;
1525                     }
1526
1527                     if (memberExpressionNode) {
1528                         highlightSourceCodeRange(memberExpressionNode.startPosition, memberExpressionNode.endPosition);
1529                         return;
1530                     }
1531
1532                     highlightSourceCodeRange(node.startPosition, node.endPosition);
1533                     return;
1534                 }
1535             }
1536
1537             // No matches, just highlight the line.
1538             callback(null);
1539         });
1540     }
1541
1542     _clearIssueWidgets()
1543     {
1544         for (var widget of this._widgetMap.values())
1545             widget.clear();
1546
1547         this._widgetMap.clear();
1548     }
1549
1550     _reinsertAllIssues()
1551     {
1552         this._issuesLineNumberMap.clear();
1553         this._clearIssueWidgets();
1554
1555         let issues = WI.issueManager.issuesForSourceCode(this._sourceCode);
1556         for (let issue of issues)
1557             this._addIssue(issue);
1558     }
1559
1560     _reinsertAllThreadIndicators()
1561     {
1562         // Clear line styles.
1563         for (let lineNumber of this._threadLineNumberMap.keys())
1564             this.removeStyleClassFromLine(lineNumber, "thread-indicator");
1565         this._threadLineNumberMap.clear();
1566
1567         // Clear widgets.
1568         for (let widget of this._threadWidgetMap.values())
1569             widget.clear();
1570         this._threadWidgetMap.clear();
1571
1572         // Clear other maps.
1573         this._threadTargetMap.clear();
1574
1575         if (WI.targets.size > 1) {
1576             for (let target of WI.targets)
1577                 this._addThreadIndicatorForTarget(target);
1578         }
1579     }
1580
1581     _debuggerDidPause(event)
1582     {
1583         this._updateTokenTrackingControllerState();
1584         if (this._typeTokenAnnotator && this._typeTokenAnnotator.isActive())
1585             this._typeTokenAnnotator.refresh();
1586         if (this._basicBlockAnnotator && this._basicBlockAnnotator.isActive())
1587             this._basicBlockAnnotator.refresh();
1588     }
1589
1590     _debuggerDidResume(event)
1591     {
1592         this._updateTokenTrackingControllerState();
1593         this._dismissPopover();
1594         if (this._typeTokenAnnotator && this._typeTokenAnnotator.isActive())
1595             this._typeTokenAnnotator.refresh();
1596         if (this._basicBlockAnnotator && this._basicBlockAnnotator.isActive())
1597             this._basicBlockAnnotator.refresh();
1598     }
1599
1600     _sourceCodeSourceMapAdded(event)
1601     {
1602         WI.notifications.addEventListener(WI.Notification.GlobalModifierKeysDidChange, this._updateTokenTrackingControllerState, this);
1603         this._sourceCode.removeEventListener(WI.SourceCode.Event.SourceMapAdded, this._sourceCodeSourceMapAdded, this);
1604
1605         this._updateTokenTrackingControllerState();
1606     }
1607
1608     _updateTokenTrackingControllerState()
1609     {
1610         var mode = WI.CodeMirrorTokenTrackingController.Mode.None;
1611         if (WI.debuggerManager.paused)
1612             mode = WI.CodeMirrorTokenTrackingController.Mode.JavaScriptExpression;
1613         else if (this._typeTokenAnnotator && this._typeTokenAnnotator.isActive())
1614             mode = WI.CodeMirrorTokenTrackingController.Mode.JavaScriptTypeInformation;
1615         else if (this._hasColorMarkers())
1616             mode = WI.CodeMirrorTokenTrackingController.Mode.MarkedTokens;
1617         else if ((this._sourceCode instanceof WI.SourceMapResource || this._sourceCode.sourceMaps.length !== 0) && WI.modifierKeys.metaKey && !WI.modifierKeys.altKey && !WI.modifierKeys.shiftKey)
1618             mode = WI.CodeMirrorTokenTrackingController.Mode.NonSymbolTokens;
1619
1620         this.tokenTrackingController.enabled = mode !== WI.CodeMirrorTokenTrackingController.Mode.None;
1621
1622         if (mode === this.tokenTrackingController.mode)
1623             return;
1624
1625         switch (mode) {
1626         case WI.CodeMirrorTokenTrackingController.Mode.MarkedTokens:
1627             this.tokenTrackingController.mouseOverDelayDuration = 0;
1628             this.tokenTrackingController.mouseOutReleaseDelayDuration = 0;
1629             break;
1630         case WI.CodeMirrorTokenTrackingController.Mode.NonSymbolTokens:
1631             this.tokenTrackingController.mouseOverDelayDuration = 0;
1632             this.tokenTrackingController.mouseOutReleaseDelayDuration = 0;
1633             this.tokenTrackingController.classNameForHighlightedRange = WI.CodeMirrorTokenTrackingController.JumpToSymbolHighlightStyleClassName;
1634             this._dismissPopover();
1635             break;
1636         case WI.CodeMirrorTokenTrackingController.Mode.JavaScriptExpression:
1637         case WI.CodeMirrorTokenTrackingController.Mode.JavaScriptTypeInformation:
1638             this.tokenTrackingController.mouseOverDelayDuration = WI.SourceCodeTextEditor.DurationToMouseOverTokenToMakeHoveredToken;
1639             this.tokenTrackingController.mouseOutReleaseDelayDuration = WI.SourceCodeTextEditor.DurationToMouseOutOfHoveredTokenToRelease;
1640             this.tokenTrackingController.classNameForHighlightedRange = WI.SourceCodeTextEditor.HoveredExpressionHighlightStyleClassName;
1641             break;
1642         }
1643
1644         this.tokenTrackingController.mode = mode;
1645     }
1646
1647     _hasColorMarkers()
1648     {
1649         for (var marker of this.markers) {
1650             if (marker.type === WI.TextMarker.Type.Color)
1651                 return true;
1652         }
1653         return false;
1654     }
1655
1656     // CodeMirrorTokenTrackingController Delegate
1657
1658     tokenTrackingControllerCanReleaseHighlightedRange(tokenTrackingController, element)
1659     {
1660         if (!this._popover)
1661             return true;
1662
1663         if (!window.getSelection().isCollapsed && this._popover.element.contains(window.getSelection().anchorNode))
1664             return false;
1665
1666         return true;
1667     }
1668
1669     tokenTrackingControllerHighlightedRangeReleased(tokenTrackingController, forceHide = false)
1670     {
1671         if (forceHide || !this._mouseIsOverPopover)
1672             this._dismissPopover();
1673     }
1674
1675     tokenTrackingControllerHighlightedRangeWasClicked(tokenTrackingController)
1676     {
1677         if (this.tokenTrackingController.mode !== WI.CodeMirrorTokenTrackingController.Mode.NonSymbolTokens)
1678             return;
1679
1680         // Links are handled by TextEditor.
1681         if (/\blink\b/.test(this.tokenTrackingController.candidate.hoveredToken.type))
1682             return;
1683
1684         const options = {
1685             ignoreNetworkTab: true,
1686             ignoreSearchTab: true,
1687         };
1688
1689         var sourceCodeLocation = this._sourceCodeLocationForEditorPosition(this.tokenTrackingController.candidate.hoveredTokenRange.start);
1690         if (this.sourceCode instanceof WI.SourceMapResource)
1691             WI.showOriginalOrFormattedSourceCodeLocation(sourceCodeLocation, options);
1692         else
1693             WI.showSourceCodeLocation(sourceCodeLocation, options);
1694     }
1695
1696     tokenTrackingControllerNewHighlightCandidate(tokenTrackingController, candidate)
1697     {
1698         if (this.tokenTrackingController.mode === WI.CodeMirrorTokenTrackingController.Mode.NonSymbolTokens) {
1699             this.tokenTrackingController.highlightRange(candidate.hoveredTokenRange);
1700             return;
1701         }
1702
1703         if (this.tokenTrackingController.mode === WI.CodeMirrorTokenTrackingController.Mode.JavaScriptExpression) {
1704             this._tokenTrackingControllerHighlightedJavaScriptExpression(candidate);
1705             return;
1706         }
1707
1708         if (this.tokenTrackingController.mode === WI.CodeMirrorTokenTrackingController.Mode.JavaScriptTypeInformation) {
1709             this._tokenTrackingControllerHighlightedJavaScriptTypeInformation(candidate);
1710             return;
1711         }
1712
1713         if (this.tokenTrackingController.mode === WI.CodeMirrorTokenTrackingController.Mode.MarkedTokens) {
1714             var markers = this.markersAtPosition(candidate.hoveredTokenRange.start);
1715             if (markers.length > 0)
1716                 this._tokenTrackingControllerHighlightedMarkedExpression(candidate, markers);
1717             else
1718                 this._dismissEditingController();
1719         }
1720     }
1721
1722     tokenTrackingControllerMouseOutOfHoveredMarker(tokenTrackingController, hoveredMarker)
1723     {
1724         this._dismissEditingController();
1725     }
1726
1727     _tokenTrackingControllerHighlightedJavaScriptExpression(candidate)
1728     {
1729         console.assert(candidate.expression);
1730
1731         function populate(error, result, wasThrown)
1732         {
1733             if (error || wasThrown)
1734                 return;
1735
1736             if (candidate !== this.tokenTrackingController.candidate)
1737                 return;
1738
1739             let data = WI.RemoteObject.fromPayload(result, this.target);
1740             switch (data.type) {
1741             case "function":
1742                 this._showPopoverForFunction(data);
1743                 break;
1744             case "object":
1745                 if (data.subtype === "null" || data.subtype === "regexp")
1746                     this._showPopoverWithFormattedValue(data);
1747                 else
1748                     this._showPopoverForObject(data);
1749                 break;
1750             case "string":
1751             case "number":
1752             case "boolean":
1753             case "undefined":
1754             case "symbol":
1755                 this._showPopoverWithFormattedValue(data);
1756                 break;
1757             }
1758         }
1759
1760         let target = WI.debuggerManager.activeCallFrame ? WI.debuggerManager.activeCallFrame.target : this.target;
1761         let expression = appendWebInspectorSourceURL(candidate.expression);
1762
1763         if (WI.debuggerManager.activeCallFrame) {
1764             target.DebuggerAgent.evaluateOnCallFrame.invoke({callFrameId: WI.debuggerManager.activeCallFrame.id, expression, objectGroup: "popover", doNotPauseOnExceptionsAndMuteConsole: true}, populate.bind(this), target.DebuggerAgent);
1765             return;
1766         }
1767
1768         // No call frame available. Use the SourceCode's page's context.
1769         target.RuntimeAgent.evaluate.invoke({expression, objectGroup: "popover", doNotPauseOnExceptionsAndMuteConsole: true}, populate.bind(this), target.RuntimeAgent);
1770     }
1771
1772     _tokenTrackingControllerHighlightedJavaScriptTypeInformation(candidate)
1773     {
1774         console.assert(candidate.expression);
1775
1776         var sourceCode = this._sourceCode;
1777         var sourceID = sourceCode instanceof WI.Script ? sourceCode.id : sourceCode.scripts[0].id;
1778         var range = candidate.hoveredTokenRange;
1779         var offset = this.currentPositionToOriginalOffset(range.start);
1780
1781         var allRequests = [{
1782             typeInformationDescriptor: WI.ScriptSyntaxTree.TypeProfilerSearchDescriptor.NormalExpression,
1783             sourceID,
1784             divot: offset
1785         }];
1786
1787         function handler(error, allTypes) {
1788             if (error)
1789                 return;
1790
1791             if (candidate !== this.tokenTrackingController.candidate)
1792                 return;
1793
1794             console.assert(allTypes.length === 1);
1795             if (!allTypes.length)
1796                 return;
1797
1798             var typeDescription = WI.TypeDescription.fromPayload(allTypes[0]);
1799             if (typeDescription.valid) {
1800                 var popoverTitle = WI.TypeTokenView.titleForPopover(WI.TypeTokenView.TitleType.Variable, candidate.expression);
1801                 this.showPopoverForTypes(typeDescription, null, popoverTitle);
1802             }
1803         }
1804
1805         this.target.RuntimeAgent.getRuntimeTypesForVariablesAtOffsets(allRequests, handler.bind(this));
1806     }
1807
1808     _showPopover(content, bounds)
1809     {
1810         console.assert(this.tokenTrackingController.candidate || bounds);
1811
1812         var shouldHighlightRange = false;
1813         var candidate = this.tokenTrackingController.candidate;
1814         // If bounds is falsey, this is a popover introduced from a hover event.
1815         // Otherwise, this is called from TypeTokenAnnotator.
1816         if (!bounds) {
1817             if (!candidate)
1818                 return;
1819
1820             var rects = this.rectsForRange(candidate.hoveredTokenRange);
1821             bounds = WI.Rect.unionOfRects(rects);
1822
1823             if (this._popover && this._popover.visible) {
1824                 let intersection = bounds.intersectionWithRect(this._popover.frame);
1825                 if (intersection.size.width && intersection.size.height)
1826                     return;
1827             }
1828
1829             shouldHighlightRange = true;
1830         }
1831
1832         content.classList.add(WI.SourceCodeTextEditor.PopoverDebuggerContentStyleClassName);
1833
1834         this._popover = this._popover || new WI.Popover(this);
1835         this._popover.presentNewContentWithFrame(content, bounds.pad(5), [WI.RectEdge.MIN_Y, WI.RectEdge.MAX_Y, WI.RectEdge.MAX_X]);
1836         if (shouldHighlightRange)
1837             this.tokenTrackingController.highlightRange(candidate.expressionRange);
1838
1839         this._trackPopoverEvents();
1840     }
1841
1842     _showPopoverForFunction(data)
1843     {
1844         let candidate = this.tokenTrackingController.candidate;
1845
1846         function didGetDetails(error, response)
1847         {
1848             if (error) {
1849                 console.error(error);
1850                 this._dismissPopover();
1851                 return;
1852             }
1853
1854             // Nothing to do if the token has changed since the time we
1855             // asked for the function details from the backend.
1856             if (candidate !== this.tokenTrackingController.candidate)
1857                 return;
1858
1859             let content = document.createElement("div");
1860             content.classList.add("function");
1861
1862             let title = document.createElement("div");
1863             title.classList.add("title");
1864             title.textContent = response.name || response.displayName || WI.UIString("(anonymous function)");
1865             content.appendChild(title);
1866
1867             let location = response.location;
1868             let sourceCode = WI.debuggerManager.scriptForIdentifier(location.scriptId, this.target);
1869             let sourceCodeLocation = sourceCode.createSourceCodeLocation(location.lineNumber, location.columnNumber);
1870             let functionSourceCodeLink = WI.createSourceCodeLocationLink(sourceCodeLocation);
1871             title.appendChild(functionSourceCodeLink);
1872
1873             let wrapper = document.createElement("div");
1874             wrapper.classList.add("body");
1875             content.appendChild(wrapper);
1876
1877             let codeMirror = WI.CodeMirrorEditor.create(wrapper, {
1878                 mode: "text/javascript",
1879                 readOnly: "nocursor",
1880             });
1881             codeMirror.on("update", () => {
1882                 this._popover.update();
1883             });
1884
1885             const isModule = false;
1886             const indentString = WI.indentString();
1887             const includeSourceMapData = false;
1888             let workerProxy = WI.FormatterWorkerProxy.singleton();
1889             workerProxy.formatJavaScript(data.description, isModule, indentString, includeSourceMapData, ({formattedText}) => {
1890                 codeMirror.setValue(formattedText || data.description);
1891             });
1892
1893             this._showPopover(content);
1894         }
1895
1896         data.target.DebuggerAgent.getFunctionDetails(data.objectId, didGetDetails.bind(this));
1897     }
1898
1899     _showPopoverForObject(data)
1900     {
1901         var content = document.createElement("div");
1902         content.className = "object expandable";
1903
1904         var titleElement = document.createElement("div");
1905         titleElement.className = "title";
1906         titleElement.textContent = data.description;
1907         content.appendChild(titleElement);
1908
1909         if (data.subtype === "node") {
1910             data.pushNodeToFrontend(function(nodeId) {
1911                 if (!nodeId)
1912                     return;
1913
1914                 var domNode = WI.domTreeManager.nodeForId(nodeId);
1915                 if (!domNode.ownerDocument)
1916                     return;
1917
1918                 var goToButton = titleElement.appendChild(WI.createGoToArrowButton());
1919                 goToButton.addEventListener("click", function() {
1920                     WI.domTreeManager.inspectElement(nodeId);
1921                 });
1922             });
1923         }
1924
1925         // FIXME: If this is a variable, it would be nice to put the variable name in the PropertyPath.
1926         var objectTree = new WI.ObjectTreeView(data);
1927         objectTree.showOnlyProperties();
1928         objectTree.expand();
1929
1930         var bodyElement = content.appendChild(document.createElement("div"));
1931         bodyElement.className = "body";
1932         bodyElement.appendChild(objectTree.element);
1933
1934         // Show the popover once we have the first set of properties for the object.
1935         var candidate = this.tokenTrackingController.candidate;
1936         objectTree.addEventListener(WI.ObjectTreeView.Event.Updated, function() {
1937             if (candidate === this.tokenTrackingController.candidate)
1938                 this._showPopover(content);
1939             objectTree.removeEventListener(null, null, this);
1940         }, this);
1941     }
1942
1943     _showPopoverWithFormattedValue(remoteObject)
1944     {
1945         var content = WI.FormattedValue.createElementForRemoteObject(remoteObject);
1946         this._showPopover(content);
1947     }
1948
1949     willDismissPopover(popover)
1950     {
1951         this.tokenTrackingController.removeHighlightedRange();
1952
1953         this.target.RuntimeAgent.releaseObjectGroup("popover");
1954     }
1955
1956     _dismissPopover()
1957     {
1958         if (!this._popover)
1959             return;
1960
1961         this._popover.dismiss();
1962
1963         if (this._popoverEventListeners && this._popoverEventListenersAreRegistered) {
1964             this._popoverEventListenersAreRegistered = false;
1965             this._popoverEventListeners.unregister();
1966         }
1967     }
1968
1969     _trackPopoverEvents()
1970     {
1971         if (!this._popoverEventListeners)
1972             this._popoverEventListeners = new WI.EventListenerSet(this, "Popover listeners");
1973         if (!this._popoverEventListenersAreRegistered) {
1974             this._popoverEventListenersAreRegistered = true;
1975             this._popoverEventListeners.register(this._popover.element, "mouseover", this._popoverMouseover);
1976             this._popoverEventListeners.register(this._popover.element, "mouseout", this._popoverMouseout);
1977             this._popoverEventListeners.install();
1978         }
1979     }
1980
1981     _popoverMouseover(event)
1982     {
1983         this._mouseIsOverPopover = true;
1984     }
1985
1986     _popoverMouseout(event)
1987     {
1988         this._mouseIsOverPopover = this._popover.element.contains(event.relatedTarget);
1989     }
1990
1991     _hasStyleSheetContents()
1992     {
1993         let mimeType = this.mimeType;
1994         return mimeType === "text/css"
1995             || mimeType === "text/x-less"
1996             || mimeType === "text/x-sass"
1997             || mimeType === "text/x-scss";
1998     }
1999
2000     _updateEditableMarkers(range)
2001     {
2002         if (this._hasStyleSheetContents()) {
2003             this.createColorMarkers(range);
2004             this.createGradientMarkers(range);
2005             this.createCubicBezierMarkers(range);
2006             this.createSpringMarkers(range);
2007         }
2008
2009         this._updateTokenTrackingControllerState();
2010     }
2011
2012     _tokenTrackingControllerHighlightedMarkedExpression(candidate, markers)
2013     {
2014         // Look for the outermost editable marker.
2015         var editableMarker;
2016         for (var marker of markers) {
2017             if (!marker.range || !Object.values(WI.TextMarker.Type).includes(marker.type))
2018                 continue;
2019
2020             if (!editableMarker || (marker.range.startLine < editableMarker.range.startLine || (marker.range.startLine === editableMarker.range.startLine && marker.range.startColumn < editableMarker.range.startColumn)))
2021                 editableMarker = marker;
2022         }
2023
2024         if (!editableMarker) {
2025             this.tokenTrackingController.hoveredMarker = null;
2026             return;
2027         }
2028
2029         if (this.tokenTrackingController.hoveredMarker === editableMarker)
2030             return;
2031
2032         this._dismissEditingController();
2033
2034         this.tokenTrackingController.hoveredMarker = editableMarker;
2035
2036         this._editingController = this.editingControllerForMarker(editableMarker);
2037
2038         if (marker.type === WI.TextMarker.Type.Color) {
2039             var color = this._editingController.value;
2040             if (!color || !color.valid) {
2041                 editableMarker.clear();
2042                 this._editingController = null;
2043                 return;
2044             }
2045         }
2046
2047         this._editingController.delegate = this;
2048         this._editingController.presentHoverMenu();
2049     }
2050
2051     _dismissEditingController(discrete)
2052     {
2053         if (this._editingController)
2054             this._editingController.dismissHoverMenu(discrete);
2055
2056         this.tokenTrackingController.hoveredMarker = null;
2057         this._editingController = null;
2058     }
2059
2060     // CodeMirrorEditingController Delegate
2061
2062     editingControllerDidStartEditing(editingController)
2063     {
2064         // We can pause the token tracking controller during editing, it will be reset
2065         // to the expected state by calling _updateEditableMarkers() in the
2066         // editingControllerDidFinishEditing delegate.
2067         this.tokenTrackingController.enabled = false;
2068
2069         // We clear the marker since we'll reset it after editing.
2070         editingController.marker.clear();
2071
2072         // We ignore content changes made as a result of color editing.
2073         this._ignoreContentDidChange++;
2074     }
2075
2076     editingControllerDidFinishEditing(editingController)
2077     {
2078         this._updateEditableMarkers(editingController.range);
2079
2080         this._ignoreContentDidChange--;
2081
2082         this._editingController = null;
2083     }
2084
2085     _setTypeTokenAnnotatorEnabledState(shouldActivate)
2086     {
2087         if (!this._typeTokenAnnotator)
2088             return;
2089
2090         if (shouldActivate) {
2091             console.assert(this.visible, "Annotators should not be enabled if the TextEditor is not visible");
2092
2093             this._typeTokenAnnotator.reset();
2094
2095             if (!this._typeTokenScrollHandler)
2096                 this._enableScrollEventsForTypeTokenAnnotator();
2097         } else {
2098             this._typeTokenAnnotator.clear();
2099
2100             if (this._typeTokenScrollHandler)
2101                 this._disableScrollEventsForTypeTokenAnnotator();
2102         }
2103
2104         WI.showJavaScriptTypeInformationSetting.value = shouldActivate;
2105
2106         this._updateTokenTrackingControllerState();
2107     }
2108
2109     set _basicBlockAnnotatorEnabled(shouldActivate)
2110     {
2111         if (!this._basicBlockAnnotator)
2112             return;
2113
2114         if (shouldActivate) {
2115             console.assert(this.visible, "Annotators should not be enabled if the TextEditor is not visible");
2116
2117             console.assert(!this._basicBlockAnnotator.isActive());
2118             this._basicBlockAnnotator.reset();
2119
2120             if (!this._controlFlowScrollHandler)
2121                 this._enableScrollEventsForControlFlowAnnotator();
2122         } else {
2123             this._basicBlockAnnotator.clear();
2124
2125             if (this._controlFlowScrollHandler)
2126                 this._disableScrollEventsForControlFlowAnnotator();
2127         }
2128
2129         WI.enableControlFlowProfilerSetting.value = shouldActivate;
2130     }
2131
2132     _getAssociatedScript(position)
2133     {
2134         let script = null;
2135
2136         if (this._sourceCode instanceof WI.Script)
2137             script = this._sourceCode;
2138         else if (this._sourceCode instanceof WI.Resource && this._sourceCode.scripts.length) {
2139             if (this._sourceCode.type === WI.Resource.Type.Script)
2140                 script = this._sourceCode.scripts[0];
2141             else if (this._sourceCode.type === WI.Resource.Type.Document && position) {
2142                 for (let inlineScript of this._sourceCode.scripts) {
2143                     if (inlineScript.range.contains(position.lineNumber, position.columnNumber)) {
2144                         if (isNaN(inlineScript.range.startOffset))
2145                             inlineScript.range.resolveOffsets(this._sourceCode.content);
2146                         script = inlineScript;
2147                         break;
2148                     }
2149                 }
2150             }
2151         }
2152
2153         return script;
2154     }
2155
2156     _createTypeTokenAnnotator()
2157     {
2158         // COMPATIBILITY (iOS 8): Runtime.getRuntimeTypesForVariablesAtOffsets did not exist yet.
2159         if (!RuntimeAgent.getRuntimeTypesForVariablesAtOffsets)
2160             return;
2161
2162         var script = this._getAssociatedScript();
2163         if (!script)
2164             return;
2165
2166         this._typeTokenAnnotator = new WI.TypeTokenAnnotator(this, script);
2167     }
2168
2169     _createBasicBlockAnnotator()
2170     {
2171         // COMPATIBILITY (iOS 8): Runtime.getBasicBlocks did not exist yet.
2172         if (!RuntimeAgent.getBasicBlocks)
2173             return;
2174
2175         var script = this._getAssociatedScript();
2176         if (!script)
2177             return;
2178
2179         this._basicBlockAnnotator = new WI.BasicBlockAnnotator(this, script);
2180     }
2181
2182     _enableScrollEventsForTypeTokenAnnotator()
2183     {
2184         // Pause updating type tokens while scrolling to prevent frame loss.
2185         console.assert(!this._typeTokenScrollHandler);
2186         this._typeTokenScrollHandler = this._createTypeTokenScrollEventHandler();
2187         this.addScrollHandler(this._typeTokenScrollHandler);
2188     }
2189
2190     _enableScrollEventsForControlFlowAnnotator()
2191     {
2192         console.assert(!this._controlFlowScrollHandler);
2193         this._controlFlowScrollHandler = this._createControlFlowScrollEventHandler();
2194         this.addScrollHandler(this._controlFlowScrollHandler);
2195     }
2196
2197     _disableScrollEventsForTypeTokenAnnotator()
2198     {
2199         console.assert(this._typeTokenScrollHandler);
2200         this.removeScrollHandler(this._typeTokenScrollHandler);
2201         this._typeTokenScrollHandler = null;
2202     }
2203
2204     _disableScrollEventsForControlFlowAnnotator()
2205     {
2206         console.assert(this._controlFlowScrollHandler);
2207         this.removeScrollHandler(this._controlFlowScrollHandler);
2208         this._controlFlowScrollHandler = null;
2209     }
2210
2211     _createTypeTokenScrollEventHandler()
2212     {
2213         let timeoutIdentifier = null;
2214         let scrollHandler = () => {
2215             if (timeoutIdentifier)
2216                 clearTimeout(timeoutIdentifier);
2217             else {
2218                 if (this._typeTokenAnnotator)
2219                     this._typeTokenAnnotator.pause();
2220             }
2221
2222             timeoutIdentifier = setTimeout(() => {
2223                 timeoutIdentifier = null;
2224                 if (this._typeTokenAnnotator)
2225                     this._typeTokenAnnotator.resume();
2226             }, WI.SourceCodeTextEditor.DurationToUpdateTypeTokensAfterScrolling);
2227         };
2228
2229         return scrollHandler;
2230     }
2231
2232     _createControlFlowScrollEventHandler()
2233     {
2234         let timeoutIdentifier = null;
2235         let scrollHandler = () => {
2236             if (timeoutIdentifier)
2237                 clearTimeout(timeoutIdentifier);
2238             else if (this._basicBlockAnnotator)
2239                 this._basicBlockAnnotator.pause();
2240
2241             timeoutIdentifier = setTimeout(() => {
2242                 timeoutIdentifier = null;
2243                 if (this._basicBlockAnnotator)
2244                     this._basicBlockAnnotator.resume();
2245             }, WI.SourceCodeTextEditor.DurationToUpdateTypeTokensAfterScrolling);
2246         };
2247
2248         return scrollHandler;
2249     }
2250
2251     _logCleared(event)
2252     {
2253         for (let lineNumber of this._issuesLineNumberMap.keys()) {
2254             this.removeStyleClassFromLine(lineNumber, WI.SourceCodeTextEditor.LineErrorStyleClassName);
2255             this.removeStyleClassFromLine(lineNumber, WI.SourceCodeTextEditor.LineWarningStyleClassName);
2256         }
2257
2258         this._issuesLineNumberMap.clear();
2259         this._clearIssueWidgets();
2260     }
2261 };
2262
2263 WI.SourceCodeTextEditor.LineErrorStyleClassName = "error";
2264 WI.SourceCodeTextEditor.LineWarningStyleClassName = "warning";
2265 WI.SourceCodeTextEditor.PopoverDebuggerContentStyleClassName = "debugger-popover-content";
2266 WI.SourceCodeTextEditor.HoveredExpressionHighlightStyleClassName = "hovered-expression-highlight";
2267 WI.SourceCodeTextEditor.DurationToMouseOverTokenToMakeHoveredToken = 500;
2268 WI.SourceCodeTextEditor.DurationToMouseOutOfHoveredTokenToRelease = 1000;
2269 WI.SourceCodeTextEditor.DurationToUpdateTypeTokensAfterScrolling = 100;
2270 WI.SourceCodeTextEditor.WidgetContainsMultipleIssuesSymbol = Symbol("source-code-widget-contains-multiple-issues");
2271 WI.SourceCodeTextEditor.WidgetContainsMultipleThreadsSymbol = Symbol("source-code-widget-contains-multiple-threads");
2272
2273 WI.SourceCodeTextEditor.Event = {
2274     ContentWillPopulate: "source-code-text-editor-content-will-populate",
2275     ContentDidPopulate: "source-code-text-editor-content-did-populate"
2276 };