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