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