Web Inspector: Debugger Popovers and Probes should use FormattedValue/ObjectTreeView...
[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 = function(sourceCode)
27 {
28     console.assert(sourceCode instanceof WebInspector.SourceCode);
29
30     this._sourceCode = sourceCode;
31     this._breakpointMap = {};
32     this._issuesLineNumberMap = new Map;
33     this._widgetMap = new Map;
34     this._contentPopulated = false;
35     this._invalidLineNumbers = {0: true};
36     this._ignoreContentDidChange = 0;
37
38     WebInspector.TextEditor.call(this, null, null, this);
39
40     this._typeTokenScrollHandler = null;
41     this._typeTokenAnnotator = null;
42     this._basicBlockAnnotator = null;
43     
44     this._isProbablyMinified = false;
45
46     // FIXME: Currently this just jumps between resources and related source map resources. It doesn't "jump to symbol" yet.
47     this._updateTokenTrackingControllerState();
48
49     this.element.classList.add(WebInspector.SourceCodeTextEditor.StyleClassName);
50
51     if (this._supportsDebugging) {
52         WebInspector.Breakpoint.addEventListener(WebInspector.Breakpoint.Event.DisabledStateDidChange, this._breakpointStatusDidChange, this);
53         WebInspector.Breakpoint.addEventListener(WebInspector.Breakpoint.Event.AutoContinueDidChange, this._breakpointStatusDidChange, this);
54         WebInspector.Breakpoint.addEventListener(WebInspector.Breakpoint.Event.ResolvedStateDidChange, this._breakpointStatusDidChange, this);
55         WebInspector.Breakpoint.addEventListener(WebInspector.Breakpoint.Event.LocationDidChange, this._updateBreakpointLocation, this);
56
57         WebInspector.debuggerManager.addEventListener(WebInspector.DebuggerManager.Event.BreakpointsEnabledDidChange, this._breakpointsEnabledDidChange, this);
58         WebInspector.debuggerManager.addEventListener(WebInspector.DebuggerManager.Event.BreakpointAdded, this._breakpointAdded, this);
59         WebInspector.debuggerManager.addEventListener(WebInspector.DebuggerManager.Event.BreakpointRemoved, this._breakpointRemoved, this);
60         WebInspector.debuggerManager.addEventListener(WebInspector.DebuggerManager.Event.ActiveCallFrameDidChange, this._activeCallFrameDidChange, this);
61
62         WebInspector.debuggerManager.addEventListener(WebInspector.DebuggerManager.Event.Paused, this._debuggerDidPause, this);
63         WebInspector.debuggerManager.addEventListener(WebInspector.DebuggerManager.Event.Resumed, this._debuggerDidResume, this);
64         if (WebInspector.debuggerManager.activeCallFrame)
65             this._debuggerDidPause();
66
67         this._activeCallFrameDidChange();
68     }
69
70     WebInspector.issueManager.addEventListener(WebInspector.IssueManager.Event.IssueWasAdded, this._issueWasAdded, this);
71
72     if (this._sourceCode instanceof WebInspector.SourceMapResource || this._sourceCode.sourceMaps.length > 0)
73         WebInspector.notifications.addEventListener(WebInspector.Notification.GlobalModifierKeysDidChange, this._updateTokenTrackingControllerState, this);
74     else
75         this._sourceCode.addEventListener(WebInspector.SourceCode.Event.SourceMapAdded, this._sourceCodeSourceMapAdded, this);
76
77     sourceCode.requestContent().then(this._contentAvailable.bind(this));
78
79     // FIXME: Cmd+L shorcut doesn't actually work.
80     new WebInspector.KeyboardShortcut(WebInspector.KeyboardShortcut.Modifier.Command, "L", this.showGoToLineDialog.bind(this), this.element);
81     new WebInspector.KeyboardShortcut(WebInspector.KeyboardShortcut.Modifier.Control, "G", this.showGoToLineDialog.bind(this), this.element);
82 };
83
84 WebInspector.Object.addConstructorFunctions(WebInspector.SourceCodeTextEditor);
85
86 WebInspector.SourceCodeTextEditor.StyleClassName = "source-code";
87 WebInspector.SourceCodeTextEditor.LineErrorStyleClassName = "error";
88 WebInspector.SourceCodeTextEditor.LineWarningStyleClassName = "warning";
89 WebInspector.SourceCodeTextEditor.PopoverDebuggerContentStyleClassName = "debugger-popover-content";
90 WebInspector.SourceCodeTextEditor.HoveredExpressionHighlightStyleClassName = "hovered-expression-highlight";
91 WebInspector.SourceCodeTextEditor.DurationToMouseOverTokenToMakeHoveredToken = 500;
92 WebInspector.SourceCodeTextEditor.DurationToMouseOutOfHoveredTokenToRelease = 1000;
93 WebInspector.SourceCodeTextEditor.DurationToUpdateTypeTokensAfterScrolling = 100;
94 WebInspector.SourceCodeTextEditor.AutoFormatMinimumLineLength = 500;
95 WebInspector.SourceCodeTextEditor.WidgetContainsMultipleIssuesSymbol = Symbol("source-code-widget-contains-multiple-issues");
96
97 WebInspector.SourceCodeTextEditor.Event = {
98     ContentWillPopulate: "source-code-text-editor-content-will-populate",
99     ContentDidPopulate: "source-code-text-editor-content-did-populate"
100 };
101
102 WebInspector.SourceCodeTextEditor.prototype = {
103     constructor: WebInspector.SourceCodeTextEditor,
104
105     // Public
106
107     get sourceCode()
108     {
109         return this._sourceCode;
110     },
111
112     shown: function()
113     {
114         WebInspector.TextEditor.prototype.shown.call(this);
115
116         if (WebInspector.showJavaScriptTypeInformationSetting.value) {
117             if (this._typeTokenAnnotator)
118                 this._typeTokenAnnotator.resume();
119             if (this._basicBlockAnnotator)
120                 this._basicBlockAnnotator.resume();
121             if (!this._typeTokenScrollHandler && (this._typeTokenAnnotator || this._basicBlockAnnotator))
122                 this._enableScrollEventsForTypeTokenAnnotator();
123         } else {
124             if (this._typeTokenAnnotator || this._basicBlockAnnotator)
125                 this._setTypeTokenAnnotatorEnabledState(false);
126         }
127     },
128
129     hidden: function()
130     {
131         WebInspector.TextEditor.prototype.hidden.call(this);
132
133         this.tokenTrackingController.removeHighlightedRange();
134
135         this._dismissPopover();
136         
137         this._dismissEditingController(true);
138
139         if (this._typeTokenAnnotator)
140             this._typeTokenAnnotator.pause();
141         if (this._basicBlockAnnotator)
142             this._basicBlockAnnotator.pause();
143     },
144
145     close: function()
146     {
147         if (this._supportsDebugging) {
148             WebInspector.Breakpoint.removeEventListener(WebInspector.Breakpoint.Event.DisabledStateDidChange, this._breakpointStatusDidChange, this);
149             WebInspector.Breakpoint.removeEventListener(WebInspector.Breakpoint.Event.AutoContinueDidChange, this._breakpointStatusDidChange, this);
150             WebInspector.Breakpoint.removeEventListener(WebInspector.Breakpoint.Event.ResolvedStateDidChange, this._breakpointStatusDidChange, this);
151             WebInspector.Breakpoint.removeEventListener(WebInspector.Breakpoint.Event.LocationDidChange, this._updateBreakpointLocation, this);
152
153             WebInspector.debuggerManager.removeEventListener(WebInspector.DebuggerManager.Event.BreakpointsEnabledDidChange, this._breakpointsEnabledDidChange, this);
154             WebInspector.debuggerManager.removeEventListener(WebInspector.DebuggerManager.Event.BreakpointAdded, this._breakpointAdded, this);
155             WebInspector.debuggerManager.removeEventListener(WebInspector.DebuggerManager.Event.BreakpointRemoved, this._breakpointRemoved, this);
156             WebInspector.debuggerManager.removeEventListener(WebInspector.DebuggerManager.Event.ActiveCallFrameDidChange, this._activeCallFrameDidChange, this);
157
158             if (this._activeCallFrameSourceCodeLocation) {
159                 this._activeCallFrameSourceCodeLocation.removeEventListener(WebInspector.SourceCodeLocation.Event.LocationChanged, this._activeCallFrameSourceCodeLocationChanged, this);
160                 delete this._activeCallFrameSourceCodeLocation;
161             }
162         }
163
164         WebInspector.issueManager.removeEventListener(WebInspector.IssueManager.Event.IssueWasAdded, this._issueWasAdded, this);
165
166         WebInspector.notifications.removeEventListener(WebInspector.Notification.GlobalModifierKeysDidChange, this._updateTokenTrackingControllerState, this);
167         this._sourceCode.removeEventListener(WebInspector.SourceCode.Event.SourceMapAdded, this._sourceCodeSourceMapAdded, this);
168     },
169
170     canBeFormatted: function()
171     {
172         // Currently we assume that source map resources are formatted how the author wants it.
173         // We could allow source map resources to be formatted, we would then need to make
174         // SourceCodeLocation watch listen for mappedResource's formatting changes, and keep
175         // a formatted location alongside the regular mapped location.
176         if (this._sourceCode instanceof WebInspector.SourceMapResource)
177             return false;
178
179         return WebInspector.TextEditor.prototype.canBeFormatted.call(this);
180     },
181
182     canShowTypeAnnotations: function()
183     {
184         return !!this._typeTokenAnnotator;
185     },
186
187     customPerformSearch: function(query)
188     {
189         function searchResultCallback(error, matches)
190         {
191             // Bail if the query changed since we started.
192             if (this.currentSearchQuery !== query)
193                 return;
194
195             if (error || !matches || !matches.length) {
196                 // Report zero matches.
197                 this.dispatchEventToListeners(WebInspector.TextEditor.Event.NumberOfSearchResultsDidChange);
198                 return;
199             }
200
201             var queryRegex = new RegExp(query.escapeForRegExp(), "gi");
202             var searchResults = [];
203
204             for (var i = 0; i < matches.length; ++i) {
205                 var matchLineNumber = matches[i].lineNumber;
206                 var line = this.line(matchLineNumber);
207
208                 // Reset the last index to reuse the regex on a new line.
209                 queryRegex.lastIndex = 0;
210
211                 // Search the line and mark the ranges.
212                 var lineMatch = null;
213                 while (queryRegex.lastIndex + query.length <= line.length && (lineMatch = queryRegex.exec(line))) {
214                     var resultTextRange = new WebInspector.TextRange(matchLineNumber, lineMatch.index, matchLineNumber, queryRegex.lastIndex);
215                     searchResults.push(resultTextRange);
216                 }
217             }
218
219             this.addSearchResults(searchResults);
220
221             this.dispatchEventToListeners(WebInspector.TextEditor.Event.NumberOfSearchResultsDidChange);
222         }
223
224         if (this.hasEdits())
225             return false;
226
227         if (this._sourceCode instanceof WebInspector.SourceMapResource)
228             return false;
229
230         if (this._sourceCode instanceof WebInspector.Resource)
231             PageAgent.searchInResource(this._sourceCode.parentFrame.id, this._sourceCode.url, query, false, false, searchResultCallback.bind(this));
232         else if (this._sourceCode instanceof WebInspector.Script)
233             DebuggerAgent.searchInContent(this._sourceCode.id, query, false, false, searchResultCallback.bind(this));
234         return true;
235     },
236
237     showGoToLineDialog: function()
238     {
239         if (!this._goToLineDialog) {
240             this._goToLineDialog = new WebInspector.GoToLineDialog;
241             this._goToLineDialog.delegate = this;
242         }
243
244         this._goToLineDialog.present(this.element);
245     },
246
247     isGoToLineDialogValueValid: function(goToLineDialog, lineNumber)
248     {
249         return !isNaN(lineNumber) && lineNumber > 0 && lineNumber <= this.lineCount;
250     },
251
252     goToLineDialogValueWasValidated: function(goToLineDialog, lineNumber)
253     {
254         var position = new WebInspector.SourceCodePosition(lineNumber - 1, 0);
255         var range = new WebInspector.TextRange(lineNumber - 1, 0, lineNumber, 0);
256         this.revealPosition(position, range, false, true);
257     },
258
259     goToLineDialogWasDismissed: function()
260     {
261         this.focus();
262     },
263
264     contentDidChange: function(replacedRanges, newRanges)
265     {
266         WebInspector.TextEditor.prototype.contentDidChange.call(this, replacedRanges, newRanges);
267
268         if (this._ignoreContentDidChange > 0)
269             return;
270
271         for (var range of newRanges)
272             this._updateEditableMarkers(range);
273
274         if (this._typeTokenAnnotator || this._basicBlockAnnotator) {
275             this._setTypeTokenAnnotatorEnabledState(false);
276             this._typeTokenAnnotator = null;
277             this._basicBlockAnnotator = null;
278         }
279     },
280
281     toggleTypeAnnotations: function()
282     {
283         if (!this._typeTokenAnnotator)
284             return false;
285
286         var newActivatedState = !this._typeTokenAnnotator.isActive();
287         if (newActivatedState && this._isProbablyMinified && !this.formatted)
288             this.formatted = true;
289
290         this._setTypeTokenAnnotatorEnabledState(newActivatedState);
291         return newActivatedState;
292     },
293
294     showPopoverForTypes: function(types, bounds, title)
295     {
296         var content = document.createElement("div");
297         content.className = "object expandable";
298
299         var titleElement = document.createElement("div");
300         titleElement.className = "title";
301         titleElement.textContent = title;
302         content.appendChild(titleElement);
303
304         var section = new WebInspector.TypePropertiesSection(types);
305         section.expanded = true;
306         section.element.classList.add("body");
307         content.appendChild(section.element);
308
309         this._showPopover(content, bounds);
310     },
311
312     // Protected
313
314     prettyPrint: function(pretty)
315     {
316         // The annotators must be cleared before pretty printing takes place and resumed 
317         // after so that they clear their annotations in a known state and insert new annotations 
318         // in the new state.
319         var shouldResumeTypeTokenAnnotator = this._typeTokenAnnotator && this._typeTokenAnnotator.isActive();
320         var shouldResumeBasicBlockAnnotator = this._basicBlockAnnotator && this._basicBlockAnnotator.isActive();
321         if (shouldResumeTypeTokenAnnotator || shouldResumeBasicBlockAnnotator)
322             this._setTypeTokenAnnotatorEnabledState(false);
323
324         WebInspector.TextEditor.prototype.prettyPrint.call(this, pretty);
325
326         if (pretty || !this._isProbablyMinified) {
327             if (shouldResumeTypeTokenAnnotator || shouldResumeBasicBlockAnnotator)
328                 this._setTypeTokenAnnotatorEnabledState(true);
329         } else {
330             console.assert(!pretty && this._isProbablyMinified);
331             if (this._typeTokenAnnotator || this._basicBlockAnnotator)
332                 this._setTypeTokenAnnotatorEnabledState(false);
333         }
334     },
335
336     // Private
337
338     _unformattedLineInfoForEditorLineInfo: function(lineInfo)
339     {
340         if (this.formatterSourceMap)
341             return this.formatterSourceMap.formattedToOriginal(lineInfo.lineNumber, lineInfo.columnNumber);
342         return lineInfo;
343     },
344
345     _sourceCodeLocationForEditorPosition: function(position)
346     {
347         var lineInfo = {lineNumber: position.line, columnNumber: position.ch};
348         var unformattedLineInfo = this._unformattedLineInfoForEditorLineInfo(lineInfo);
349         return this.sourceCode.createSourceCodeLocation(unformattedLineInfo.lineNumber, unformattedLineInfo.columnNumber);
350     },
351
352     _editorLineInfoForSourceCodeLocation: function(sourceCodeLocation)
353     {
354         if (this._sourceCode instanceof WebInspector.SourceMapResource)
355             return {lineNumber: sourceCodeLocation.displayLineNumber, columnNumber: sourceCodeLocation.displayColumnNumber};
356         return {lineNumber: sourceCodeLocation.formattedLineNumber, columnNumber: sourceCodeLocation.formattedColumnNumber};
357     },
358
359     _breakpointForEditorLineInfo: function(lineInfo)
360     {
361         if (!this._breakpointMap[lineInfo.lineNumber])
362             return null;
363         return this._breakpointMap[lineInfo.lineNumber][lineInfo.columnNumber];
364     },
365
366     _addBreakpointWithEditorLineInfo: function(breakpoint, lineInfo)
367     {
368         if (!this._breakpointMap[lineInfo.lineNumber])
369             this._breakpointMap[lineInfo.lineNumber] = {};
370
371         this._breakpointMap[lineInfo.lineNumber][lineInfo.columnNumber] = breakpoint;
372     },
373
374     _removeBreakpointWithEditorLineInfo: function(breakpoint, lineInfo)
375     {
376         console.assert(breakpoint === this._breakpointMap[lineInfo.lineNumber][lineInfo.columnNumber]);
377
378         delete this._breakpointMap[lineInfo.lineNumber][lineInfo.columnNumber];
379
380         if (isEmptyObject(this._breakpointMap[lineInfo.lineNumber]))
381             delete this._breakpointMap[lineInfo.lineNumber];
382     },
383
384     _contentWillPopulate: function(content)
385     {
386         this.dispatchEventToListeners(WebInspector.SourceCodeTextEditor.Event.ContentWillPopulate);
387
388         // We only do the rest of this work before the first populate.
389         if (this._contentPopulated)
390             return;
391
392         if (this._supportsDebugging) {
393             this._breakpointMap = {};
394
395             var breakpoints = WebInspector.debuggerManager.breakpointsForSourceCode(this._sourceCode);
396             for (var i = 0; i < breakpoints.length; ++i) {
397                 var breakpoint = breakpoints[i];
398                 console.assert(this._matchesBreakpoint(breakpoint));
399                 var lineInfo = this._editorLineInfoForSourceCodeLocation(breakpoint.sourceCodeLocation);
400                 this._addBreakpointWithEditorLineInfo(breakpoint, lineInfo);
401                 this.setBreakpointInfoForLineAndColumn(lineInfo.lineNumber, lineInfo.columnNumber, this._breakpointInfoForBreakpoint(breakpoint));
402             }
403         }
404
405         if (this._sourceCode instanceof WebInspector.Resource)
406             this.mimeType = this._sourceCode.syntheticMIMEType;
407         else if (this._sourceCode instanceof WebInspector.Script)
408             this.mimeType = "text/javascript";
409
410         // Automatically format the content if it looks minified and it can be formatted.
411         console.assert(!this.formatted);
412         if (this.canBeFormatted()) {
413             var lastNewlineIndex = 0;
414             while (true) {
415                 var nextNewlineIndex = content.indexOf("\n", lastNewlineIndex);
416                 if (nextNewlineIndex === -1) {
417                     if (content.length - lastNewlineIndex > WebInspector.SourceCodeTextEditor.AutoFormatMinimumLineLength) {
418                         this.autoFormat = true;
419                         this._isProbablyMinified = true;
420                     }
421                     break;
422                 }
423
424                 if (nextNewlineIndex - lastNewlineIndex > WebInspector.SourceCodeTextEditor.AutoFormatMinimumLineLength) {
425                     this.autoFormat = true;
426                     this._isProbablyMinified = true;
427                     break;
428                 }
429
430                 lastNewlineIndex = nextNewlineIndex + 1;
431             }
432         }
433     },
434
435     _contentDidPopulate: function()
436     {
437         this._contentPopulated = true;
438
439         this.dispatchEventToListeners(WebInspector.SourceCodeTextEditor.Event.ContentDidPopulate);
440
441         // We add the issues each time content is populated. This is needed because lines might not exist
442         // if we tried added them before when the full content wasn't available. (When populating with
443         // partial script content this can be called multiple times.)
444
445         this._reinsertAllIssues();
446
447         this._updateEditableMarkers();
448     },
449
450     _populateWithContent: function(content)
451     {
452         content = content || "";
453
454         this._contentWillPopulate(content);
455         this.string = content;
456
457         this._makeTypeTokenAnnotator();
458         this._makeBasicBlockAnnotator();
459
460         if (WebInspector.showJavaScriptTypeInformationSetting.value) {
461             if (this._basicBlockAnnotator || this._typeTokenAnnotator)
462                 this._setTypeTokenAnnotatorEnabledState(true);
463         }
464
465         this._contentDidPopulate();
466     },
467
468     _contentAvailable: function(parameters)
469     {
470         // Return if resource is not available.
471         if (parameters.error)
472             return;
473
474         var sourceCode = parameters.sourceCode;
475         var content = parameters.content;
476         var base64Encoded = parameters.base64Encoded;
477
478         console.assert(sourceCode === this._sourceCode);
479         console.assert(!base64Encoded);
480
481         // Abort if the full content populated while waiting for this async callback.
482         if (this._fullContentPopulated)
483             return;
484
485         this._fullContentPopulated = true;
486         this._invalidLineNumbers = {};
487
488         this._populateWithContent(content);
489     },
490
491     _breakpointStatusDidChange: function(event)
492     {
493         this._updateBreakpointStatus(event.target);
494     },
495
496     _breakpointsEnabledDidChange: function()
497     {
498         console.assert(this._supportsDebugging);
499
500         var breakpoints = WebInspector.debuggerManager.breakpointsForSourceCode(this._sourceCode);
501         for (var breakpoint of breakpoints)
502             this._updateBreakpointStatus(breakpoint);
503     },
504
505     _updateBreakpointStatus: function(breakpoint)
506     {
507         console.assert(this._supportsDebugging);
508
509         if (!this._contentPopulated)
510             return;
511
512         if (!this._matchesBreakpoint(breakpoint))
513             return;
514
515         var lineInfo = this._editorLineInfoForSourceCodeLocation(breakpoint.sourceCodeLocation);
516         this.setBreakpointInfoForLineAndColumn(lineInfo.lineNumber, lineInfo.columnNumber, this._breakpointInfoForBreakpoint(breakpoint));
517     },
518
519     _updateBreakpointLocation: function(event)
520     {
521         console.assert(this._supportsDebugging);
522
523         if (!this._contentPopulated)
524             return;
525
526         var breakpoint = event.target;
527         if (!this._matchesBreakpoint(breakpoint))
528             return;
529
530         if (this._ignoreAllBreakpointLocationUpdates)
531             return;
532
533         if (breakpoint === this._ignoreLocationUpdateBreakpoint)
534             return;
535
536         var sourceCodeLocation = breakpoint.sourceCodeLocation;
537
538         if (this._sourceCode instanceof WebInspector.SourceMapResource) {
539             // Update our breakpoint location if the display location changed.
540             if (sourceCodeLocation.displaySourceCode !== this._sourceCode)
541                 return;
542             var oldLineInfo = {lineNumber: event.data.oldDisplayLineNumber, columnNumber: event.data.oldDisplayColumnNumber};
543             var newLineInfo = {lineNumber: sourceCodeLocation.displayLineNumber, columnNumber: sourceCodeLocation.displayColumnNumber};
544         } else {
545             // Update our breakpoint location if the original location changed.
546             if (sourceCodeLocation.sourceCode !== this._sourceCode)
547                 return;
548             var oldLineInfo = {lineNumber: event.data.oldFormattedLineNumber, columnNumber: event.data.oldFormattedColumnNumber};
549             var newLineInfo = {lineNumber: sourceCodeLocation.formattedLineNumber, columnNumber: sourceCodeLocation.formattedColumnNumber};
550         }
551
552         var existingBreakpoint = this._breakpointForEditorLineInfo(oldLineInfo);
553         if (!existingBreakpoint)
554             return;
555
556         console.assert(breakpoint === existingBreakpoint);
557
558         this.setBreakpointInfoForLineAndColumn(oldLineInfo.lineNumber, oldLineInfo.columnNumber, null);
559         this.setBreakpointInfoForLineAndColumn(newLineInfo.lineNumber, newLineInfo.columnNumber, this._breakpointInfoForBreakpoint(breakpoint));
560
561         this._removeBreakpointWithEditorLineInfo(breakpoint, oldLineInfo);
562         this._addBreakpointWithEditorLineInfo(breakpoint, newLineInfo);
563     },
564
565     _breakpointAdded: function(event)
566     {
567         console.assert(this._supportsDebugging);
568
569         if (!this._contentPopulated)
570             return;
571
572         var breakpoint = event.data.breakpoint;
573         if (!this._matchesBreakpoint(breakpoint))
574             return;
575
576         if (breakpoint === this._ignoreBreakpointAddedBreakpoint)
577             return;
578
579         var lineInfo = this._editorLineInfoForSourceCodeLocation(breakpoint.sourceCodeLocation);
580         this._addBreakpointWithEditorLineInfo(breakpoint, lineInfo);
581         this.setBreakpointInfoForLineAndColumn(lineInfo.lineNumber, lineInfo.columnNumber, this._breakpointInfoForBreakpoint(breakpoint));
582     },
583
584     _breakpointRemoved: function(event)
585     {
586         console.assert(this._supportsDebugging);
587
588         if (!this._contentPopulated)
589             return;
590
591         var breakpoint = event.data.breakpoint;
592         if (!this._matchesBreakpoint(breakpoint))
593             return;
594
595         if (breakpoint === this._ignoreBreakpointRemovedBreakpoint)
596             return;
597
598         var lineInfo = this._editorLineInfoForSourceCodeLocation(breakpoint.sourceCodeLocation);
599         this._removeBreakpointWithEditorLineInfo(breakpoint, lineInfo);
600         this.setBreakpointInfoForLineAndColumn(lineInfo.lineNumber, lineInfo.columnNumber, null);
601     },
602
603     _activeCallFrameDidChange: function()
604     {
605         console.assert(this._supportsDebugging);
606
607         if (this._activeCallFrameSourceCodeLocation) {
608             this._activeCallFrameSourceCodeLocation.removeEventListener(WebInspector.SourceCodeLocation.Event.LocationChanged, this._activeCallFrameSourceCodeLocationChanged, this);
609             delete this._activeCallFrameSourceCodeLocation;
610         }
611
612         var activeCallFrame = WebInspector.debuggerManager.activeCallFrame;
613         if (!activeCallFrame || !this._matchesSourceCodeLocation(activeCallFrame.sourceCodeLocation)) {
614             this.executionLineNumber = NaN;
615             this.executionColumnNumber = NaN;
616             return;
617         }
618
619         this._dismissPopover();
620
621         this._activeCallFrameSourceCodeLocation = activeCallFrame.sourceCodeLocation;
622         this._activeCallFrameSourceCodeLocation.addEventListener(WebInspector.SourceCodeLocation.Event.LocationChanged, this._activeCallFrameSourceCodeLocationChanged, this);
623
624         // Don't return early if the line number didn't change. The execution state still
625         // could have changed (e.g. continuing in a loop with a breakpoint inside).
626
627         var lineInfo = this._editorLineInfoForSourceCodeLocation(activeCallFrame.sourceCodeLocation);
628         this.executionLineNumber = lineInfo.lineNumber;
629         this.executionColumnNumber = lineInfo.columnNumber;
630
631         // If we have full content or this source code isn't a Resource we can return early.
632         // Script source code populates from the request started in the constructor.
633         if (this._fullContentPopulated || !(this._sourceCode instanceof WebInspector.Resource) || this._requestingScriptContent)
634             return;
635
636         // Since we are paused in the debugger we need to show some content, and since the Resource
637         // content hasn't populated yet we need to populate with content from the Scripts by URL.
638         // Document resources will attempt to populate the scripts as inline (in <script> tags.)
639         // Other resources are assumed to be full scripts (JavaScript resources).
640         if (this._sourceCode.type === WebInspector.Resource.Type.Document)
641             this._populateWithInlineScriptContent();
642         else
643             this._populateWithScriptContent();
644     },
645
646     _activeCallFrameSourceCodeLocationChanged: function(event)
647     {
648         console.assert(!isNaN(this.executionLineNumber));
649         if (isNaN(this.executionLineNumber))
650             return;
651
652         console.assert(WebInspector.debuggerManager.activeCallFrame);
653         console.assert(this._activeCallFrameSourceCodeLocation === WebInspector.debuggerManager.activeCallFrame.sourceCodeLocation);
654
655         var lineInfo = this._editorLineInfoForSourceCodeLocation(this._activeCallFrameSourceCodeLocation);
656         this.executionLineNumber = lineInfo.lineNumber;
657         this.executionColumnNumber = lineInfo.columnNumber;
658     },
659
660     _populateWithInlineScriptContent: function()
661     {
662         console.assert(this._sourceCode instanceof WebInspector.Resource);
663         console.assert(!this._fullContentPopulated);
664         console.assert(!this._requestingScriptContent);
665
666         var scripts = this._sourceCode.scripts;
667         console.assert(scripts.length);
668         if (!scripts.length)
669             return;
670
671         var pendingRequestCount = scripts.length;
672
673         // If the number of scripts hasn't change since the last populate, then there is nothing to do.
674         if (this._inlineScriptContentPopulated === pendingRequestCount)
675             return;
676
677         this._inlineScriptContentPopulated = pendingRequestCount;
678
679         function scriptContentAvailable(parameters)
680         {
681             // Return early if we are still waiting for content from other scripts.
682             if (--pendingRequestCount)
683                 return;
684
685             delete this._requestingScriptContent;
686
687             // Abort if the full content populated while waiting for these async callbacks.
688             if (this._fullContentPopulated)
689                 return;
690
691             const scriptOpenTag = "<script>";
692             const scriptCloseTag = "</script>";
693
694             var content = "";
695             var lineNumber = 0;
696             var columnNumber = 0;
697
698             this._invalidLineNumbers = {};
699
700             for (var i = 0; i < scripts.length; ++i) {
701                 // Fill the line gap with newline characters.
702                 for (var newLinesCount = scripts[i].range.startLine - lineNumber; newLinesCount > 0; --newLinesCount) {
703                     if (!columnNumber)
704                         this._invalidLineNumbers[scripts[i].range.startLine - newLinesCount] = true;
705                     columnNumber = 0;
706                     content += "\n";
707                 }
708
709                 // Fill the column gap with space characters.
710                 for (var spacesCount = scripts[i].range.startColumn - columnNumber - scriptOpenTag.length; spacesCount > 0; --spacesCount)
711                     content += " ";
712
713                 // Add script tags and content.
714                 content += scriptOpenTag;
715                 content += scripts[i].content;
716                 content += scriptCloseTag;
717
718                 lineNumber = scripts[i].range.endLine;
719                 columnNumber = scripts[i].range.endColumn + scriptCloseTag.length;
720             }
721
722             this._populateWithContent(content);
723         }
724
725         this._requestingScriptContent = true;
726
727         var boundScriptContentAvailable = scriptContentAvailable.bind(this);
728         for (var i = 0; i < scripts.length; ++i)
729             scripts[i].requestContent().then(boundScriptContentAvailable);
730     },
731
732     _populateWithScriptContent: function()
733     {
734         console.assert(this._sourceCode instanceof WebInspector.Resource);
735         console.assert(!this._fullContentPopulated);
736         console.assert(!this._requestingScriptContent);
737
738         // We can assume this resource only has one script that starts at line/column 0.
739         var scripts = this._sourceCode.scripts;
740         console.assert(scripts.length === 1);
741         if (!scripts.length)
742             return;
743
744         console.assert(scripts[0].range.startLine === 0);
745         console.assert(scripts[0].range.startColumn === 0);
746
747         function scriptContentAvailable(parameters)
748         {
749             var content = parameters.content;
750             delete this._requestingScriptContent;
751
752             // Abort if the full content populated while waiting for this async callback.
753             if (this._fullContentPopulated)
754                 return;
755
756             // This is the full content.
757             this._fullContentPopulated = true;
758
759             this._populateWithContent(content);
760         }
761
762         this._requestingScriptContent = true;
763
764         scripts[0].requestContent().then(scriptContentAvailable.bind(this));
765     },
766
767     _matchesSourceCodeLocation: function(sourceCodeLocation)
768     {
769         if (this._sourceCode instanceof WebInspector.SourceMapResource)
770             return sourceCodeLocation.displaySourceCode === this._sourceCode;
771         if (this._sourceCode instanceof WebInspector.Resource)
772             return sourceCodeLocation.sourceCode.url === this._sourceCode.url;
773         if (this._sourceCode instanceof WebInspector.Script)
774             return sourceCodeLocation.sourceCode === this._sourceCode;
775         return false;
776     },
777
778     _matchesBreakpoint: function(breakpoint)
779     {
780         console.assert(this._supportsDebugging);
781         if (this._sourceCode instanceof WebInspector.SourceMapResource)
782             return breakpoint.sourceCodeLocation.displaySourceCode === this._sourceCode;
783         if (this._sourceCode instanceof WebInspector.Resource)
784             return breakpoint.url === this._sourceCode.url;
785         if (this._sourceCode instanceof WebInspector.Script)
786             return breakpoint.url === this._sourceCode.url || breakpoint.scriptIdentifier === this._sourceCode.id;
787         return false;
788     },
789
790     _matchesIssue: function(issue)
791     {
792         if (this._sourceCode instanceof WebInspector.Resource)
793             return issue.url === this._sourceCode.url;
794         // FIXME: Support issues for Scripts based on id, not only by URL.
795         if (this._sourceCode instanceof WebInspector.Script)
796             return issue.url === this._sourceCode.url;
797         return false;
798     },
799
800     _issueWasAdded: function(event)
801     {
802         var issue = event.data.issue;
803         if (!this._matchesIssue(issue))
804             return;
805
806         this._addIssue(issue);
807     },
808
809     _addIssue: function(issue)
810     {
811         // FIXME: Issue should have a SourceCodeLocation.
812         var sourceCodeLocation = this._sourceCode.createSourceCodeLocation(issue.lineNumber, issue.columnNumber);
813         var lineNumber = sourceCodeLocation.formattedLineNumber;
814
815         var lineNumberIssues = this._issuesLineNumberMap.get(lineNumber);
816         if (!lineNumberIssues) {
817             lineNumberIssues = [];
818             this._issuesLineNumberMap.set(lineNumber, lineNumberIssues);
819         }
820
821         // Avoid displaying duplicate issues on the same line.
822         for (var existingIssue of lineNumberIssues) {
823             if (existingIssue.columnNumber === issue.columnNumber && existingIssue.text === issue.text)
824                 return;
825         }
826
827         lineNumberIssues.push(issue);
828
829         if (issue.level === WebInspector.IssueMessage.Level.Error)
830             this.addStyleClassToLine(lineNumber, WebInspector.SourceCodeTextEditor.LineErrorStyleClassName);
831         else if (issue.level === WebInspector.IssueMessage.Level.Warning)
832             this.addStyleClassToLine(lineNumber, WebInspector.SourceCodeTextEditor.LineWarningStyleClassName);
833         else
834             console.error("Unknown issue level");
835
836         var widget = this._issueWidgetForLine(lineNumber);
837         if (widget) {
838             if (issue.level === WebInspector.IssueMessage.Level.Error)
839                 widget.widgetElement.classList.add(WebInspector.SourceCodeTextEditor.LineErrorStyleClassName);
840             else if (issue.level === WebInspector.IssueMessage.Level.Warning)
841                 widget.widgetElement.classList.add(WebInspector.SourceCodeTextEditor.LineWarningStyleClassName);
842
843             this._updateIssueWidgetForIssues(widget, lineNumberIssues);
844         }
845     },
846
847     _issueWidgetForLine: function(lineNumber)
848     {
849         var widget = this._widgetMap.get(lineNumber);
850         if (widget)
851             return widget;
852
853         widget = this.createWidgetForLine(lineNumber);
854         if (!widget)
855             return null;
856
857         var widgetElement = widget.widgetElement;
858         widgetElement.classList.add("issue-widget");
859         widgetElement.classList.add("inline");
860         widgetElement.addEventListener("click", this._handleWidgetClick.bind(this, widget, lineNumber));
861
862         this._widgetMap.set(lineNumber, widget);
863
864         return widget;
865     },
866
867     _iconClassNameForIssueLevel: function(level)
868     {
869         if (level === WebInspector.IssueMessage.Level.Warning)
870             return "icon-warning";
871
872         console.assert(level === WebInspector.IssueMessage.Level.Error);
873         return "icon-error";
874     },
875
876     _updateIssueWidgetForIssues: function(widget, issues)
877     {
878         var widgetElement = widget.widgetElement;
879         widgetElement.removeChildren();
880
881         if (widgetElement.classList.contains("inline") || issues.length === 1) {
882             var arrowElement = widgetElement.appendChild(document.createElement("span"));
883             arrowElement.className = "arrow";
884
885             var iconElement = widgetElement.appendChild(document.createElement("span"));
886             iconElement.className = "icon";            
887
888             var textElement = widgetElement.appendChild(document.createElement("span"));
889             textElement.className = "text";
890
891             if (issues.length === 1) {
892                 iconElement.classList.add(this._iconClassNameForIssueLevel(issues[0].level));
893                 textElement.textContent = issues[0].text;
894             } else {
895                 var errorsCount = 0;
896                 var warningsCount = 0;
897                 for (var issue of issues) {
898                     if (issue.level === WebInspector.IssueMessage.Level.Error)
899                         ++errorsCount;
900                     else if (issue.level === WebInspector.IssueMessage.Level.Warning)
901                         ++warningsCount;
902                 }
903
904                 if (warningsCount && errorsCount) {
905                     iconElement.classList.add(this._iconClassNameForIssueLevel(issue.level));
906                     textElement.textContent = WebInspector.UIString("%d Errors, %d Warnings").format(errorsCount, warningsCount);
907                 } else if (errorsCount) {
908                     iconElement.classList.add(this._iconClassNameForIssueLevel(issue.level));
909                     textElement.textContent = WebInspector.UIString("%d Errors").format(errorsCount);
910                 } else if (warningsCount) {
911                     iconElement.classList.add(this._iconClassNameForIssueLevel(issue.level));
912                     textElement.textContent = WebInspector.UIString("%d Warnings").format(warningsCount);
913                 }
914
915                 widget[WebInspector.SourceCodeTextEditor.WidgetContainsMultipleIssuesSymbol] = true;
916             }
917         } else {
918             for (var issue of issues) {
919                 var iconElement = widgetElement.appendChild(document.createElement("span"));
920                 iconElement.className = "icon";
921                 iconElement.classList.add(this._iconClassNameForIssueLevel(issue.level));
922
923                 var textElement = widgetElement.appendChild(document.createElement("span"));
924                 textElement.className = "text";
925                 textElement.textContent = issue.text;
926
927                 widgetElement.appendChild(document.createElement("br"));
928             }
929         }
930
931         widget.update();
932     },
933
934     _isWidgetToggleable: function(widget)
935     {
936         if (widget[WebInspector.SourceCodeTextEditor.WidgetContainsMultipleIssuesSymbol])
937             return true;
938
939         if (!widget.widgetElement.classList.contains("inline"))
940             return true;
941
942         var textElement = widget.widgetElement.lastChild;
943         if (textElement.offsetWidth !== textElement.scrollWidth)
944             return true;
945         
946         return false;
947     },
948
949     _handleWidgetClick: function(widget, lineNumber, event)
950     {
951         if (!this._isWidgetToggleable(widget))
952             return;
953
954         widget.widgetElement.classList.toggle("inline");
955
956         var lineNumberIssues = this._issuesLineNumberMap.get(lineNumber);
957         this._updateIssueWidgetForIssues(widget, lineNumberIssues);
958     },
959
960     _breakpointInfoForBreakpoint: function(breakpoint)
961     {
962         return {resolved: breakpoint.resolved, disabled: breakpoint.disabled, autoContinue: breakpoint.autoContinue};
963     },
964
965     get _supportsDebugging()
966     {
967         if (this._sourceCode instanceof WebInspector.Resource)
968             return this._sourceCode.type === WebInspector.Resource.Type.Document || this._sourceCode.type === WebInspector.Resource.Type.Script;
969         if (this._sourceCode instanceof WebInspector.Script)
970             return true;
971         return false;
972     },
973
974     // TextEditor Delegate
975
976     textEditorBaseURL: function(textEditor)
977     {
978         return this._sourceCode.url;
979     },
980
981     textEditorShouldHideLineNumber: function(textEditor, lineNumber)
982     {
983         return lineNumber in this._invalidLineNumbers;
984     },
985
986     textEditorGutterContextMenu: function(textEditor, lineNumber, columnNumber, editorBreakpoints, event)
987     {
988         if (!this._supportsDebugging)
989             return;
990
991         event.preventDefault();
992
993         var contextMenu = new WebInspector.ContextMenu(event);
994
995         // Paused. Add Continue to Here option only if we have a script identifier for the location.
996         if (WebInspector.debuggerManager.paused) {
997             var editorLineInfo = {lineNumber, columnNumber};
998             var unformattedLineInfo = this._unformattedLineInfoForEditorLineInfo(editorLineInfo);
999             var sourceCodeLocation = this._sourceCode.createSourceCodeLocation(unformattedLineInfo.lineNumber, unformattedLineInfo.columnNumber);
1000
1001             if (sourceCodeLocation.sourceCode instanceof WebInspector.Script)
1002                 var script = sourceCodeLocation.sourceCode;
1003             else if (sourceCodeLocation.sourceCode instanceof WebInspector.Resource)
1004                 var script = sourceCodeLocation.sourceCode.scriptForLocation(sourceCodeLocation);
1005
1006             if (script) {
1007                 function continueToLocation()
1008                 {
1009                     WebInspector.debuggerManager.continueToLocation(script.id, sourceCodeLocation.lineNumber, sourceCodeLocation.columnNumber);
1010                 }
1011
1012                 contextMenu.appendItem(WebInspector.UIString("Continue to Here"), continueToLocation);
1013                 contextMenu.appendSeparator();
1014             }
1015         }
1016
1017         var breakpoints = [];
1018         for (var i = 0; i < editorBreakpoints.length; ++i) {
1019             var lineInfo = editorBreakpoints[i];
1020             var breakpoint = this._breakpointForEditorLineInfo(lineInfo);
1021             console.assert(breakpoint);
1022             if (breakpoint)
1023                 breakpoints.push(breakpoint);
1024         }
1025
1026         // No breakpoints.
1027         if (!breakpoints.length) {
1028             function addBreakpoint()
1029             {
1030                 var data = this.textEditorBreakpointAdded(this, lineNumber, columnNumber);
1031                 this.setBreakpointInfoForLineAndColumn(data.lineNumber, data.columnNumber, data.breakpointInfo);
1032             }
1033
1034             contextMenu.appendItem(WebInspector.UIString("Add Breakpoint"), addBreakpoint.bind(this));
1035             contextMenu.show();
1036             return;
1037         }
1038
1039         // Single breakpoint.
1040         if (breakpoints.length === 1) {
1041             var breakpoint = breakpoints[0];
1042             function revealInSidebar()
1043             {
1044                 WebInspector.debuggerSidebarPanel.show();
1045                 var treeElement = WebInspector.debuggerSidebarPanel.treeElementForRepresentedObject(breakpoint);
1046                 if (treeElement)
1047                     treeElement.revealAndSelect();
1048             }
1049
1050             breakpoint.appendContextMenuItems(contextMenu, event.target);
1051             contextMenu.appendSeparator();
1052             contextMenu.appendItem(WebInspector.UIString("Reveal in Debugger Navigation Sidebar"), revealInSidebar);
1053             contextMenu.show();
1054             return;
1055         }
1056
1057         // Multiple breakpoints.
1058         var shouldDisable = false;
1059         for (var i = 0; i < breakpoints.length; ++i) {
1060             if (!breakpoints[i].disabled) {
1061                 shouldDisable = true;
1062                 break;
1063             }
1064         }
1065
1066         function removeBreakpoints()
1067         {
1068             for (var i = 0; i < breakpoints.length; ++i) {
1069                 var breakpoint = breakpoints[i];
1070                 if (WebInspector.debuggerManager.isBreakpointRemovable(breakpoint))
1071                     WebInspector.debuggerManager.removeBreakpoint(breakpoint);
1072             }
1073         }
1074
1075         function toggleBreakpoints()
1076         {
1077             for (var i = 0; i < breakpoints.length; ++i)
1078                 breakpoints[i].disabled = shouldDisable;
1079         }
1080
1081         if (shouldDisable)
1082             contextMenu.appendItem(WebInspector.UIString("Disable Breakpoints"), toggleBreakpoints.bind(this));
1083         else
1084             contextMenu.appendItem(WebInspector.UIString("Enable Breakpoints"), toggleBreakpoints.bind(this));
1085         contextMenu.appendItem(WebInspector.UIString("Delete Breakpoints"), removeBreakpoints.bind(this));
1086         contextMenu.show();
1087     },
1088
1089     textEditorBreakpointAdded: function(textEditor, lineNumber, columnNumber)
1090     {
1091         if (!this._supportsDebugging)
1092             return null;
1093
1094         var editorLineInfo = {lineNumber, columnNumber};
1095         var unformattedLineInfo = this._unformattedLineInfoForEditorLineInfo(editorLineInfo);
1096         var sourceCodeLocation = this._sourceCode.createSourceCodeLocation(unformattedLineInfo.lineNumber, unformattedLineInfo.columnNumber);
1097         var breakpoint = new WebInspector.Breakpoint(sourceCodeLocation);
1098
1099         var lineInfo = this._editorLineInfoForSourceCodeLocation(breakpoint.sourceCodeLocation);
1100         this._addBreakpointWithEditorLineInfo(breakpoint, lineInfo);
1101
1102         this._ignoreBreakpointAddedBreakpoint = breakpoint;
1103
1104         var shouldSkipEventDispatch = false;
1105         var shouldSpeculativelyResolveBreakpoint = true;
1106         WebInspector.debuggerManager.addBreakpoint(breakpoint, shouldSkipEventDispatch, shouldSpeculativelyResolveBreakpoint);
1107         delete this._ignoreBreakpointAddedBreakpoint;
1108
1109         // Return the more accurate location and breakpoint info.
1110         return {
1111             breakpointInfo: this._breakpointInfoForBreakpoint(breakpoint),
1112             lineNumber: lineInfo.lineNumber,
1113             columnNumber: lineInfo.columnNumber
1114         };
1115     },
1116
1117     textEditorBreakpointRemoved: function(textEditor, lineNumber, columnNumber)
1118     {
1119         console.assert(this._supportsDebugging);
1120         if (!this._supportsDebugging)
1121             return;
1122
1123         var lineInfo = {lineNumber, columnNumber};
1124         var breakpoint = this._breakpointForEditorLineInfo(lineInfo);
1125         console.assert(breakpoint);
1126         if (!breakpoint)
1127             return;
1128
1129         this._removeBreakpointWithEditorLineInfo(breakpoint, lineInfo);
1130
1131         this._ignoreBreakpointRemovedBreakpoint = breakpoint;
1132         WebInspector.debuggerManager.removeBreakpoint(breakpoint);
1133         delete this._ignoreBreakpointAddedBreakpoint;
1134     },
1135
1136     textEditorBreakpointMoved: function(textEditor, oldLineNumber, oldColumnNumber, newLineNumber, newColumnNumber)
1137     {
1138         console.assert(this._supportsDebugging);
1139         if (!this._supportsDebugging)
1140             return;
1141
1142         var oldLineInfo = {lineNumber: oldLineNumber, columnNumber: oldColumnNumber};
1143         var breakpoint = this._breakpointForEditorLineInfo(oldLineInfo);
1144         console.assert(breakpoint);
1145         if (!breakpoint)
1146             return;
1147
1148         this._removeBreakpointWithEditorLineInfo(breakpoint, oldLineInfo);
1149
1150         var newLineInfo = {lineNumber: newLineNumber, columnNumber: newColumnNumber};
1151         var unformattedNewLineInfo = this._unformattedLineInfoForEditorLineInfo(newLineInfo);
1152         this._ignoreLocationUpdateBreakpoint = breakpoint;
1153         breakpoint.sourceCodeLocation.update(this._sourceCode, unformattedNewLineInfo.lineNumber, unformattedNewLineInfo.columnNumber);
1154         delete this._ignoreLocationUpdateBreakpoint;
1155
1156         var accurateNewLineInfo = this._editorLineInfoForSourceCodeLocation(breakpoint.sourceCodeLocation);
1157         this._addBreakpointWithEditorLineInfo(breakpoint, accurateNewLineInfo);
1158
1159         if (accurateNewLineInfo.lineNumber !== newLineInfo.lineNumber || accurateNewLineInfo.columnNumber !== newLineInfo.columnNumber)
1160             this.updateBreakpointLineAndColumn(newLineInfo.lineNumber, newLineInfo.columnNumber, accurateNewLineInfo.lineNumber, accurateNewLineInfo.columnNumber);
1161     },
1162
1163     textEditorBreakpointClicked: function(textEditor, lineNumber, columnNumber)
1164     {
1165         console.assert(this._supportsDebugging);
1166         if (!this._supportsDebugging)
1167             return;
1168
1169         var breakpoint = this._breakpointForEditorLineInfo({lineNumber, columnNumber});
1170         console.assert(breakpoint);
1171         if (!breakpoint)
1172             return;
1173
1174         breakpoint.cycleToNextMode();
1175     },
1176
1177     textEditorUpdatedFormatting: function(textEditor)
1178     {
1179         this._ignoreAllBreakpointLocationUpdates = true;
1180         this._sourceCode.formatterSourceMap = this.formatterSourceMap;
1181         delete this._ignoreAllBreakpointLocationUpdates;
1182
1183         // Always put the source map on both the Script and Resource if both exist. For example,
1184         // if this SourceCode is a Resource, then there might also be a Script. In the debugger,
1185         // the backend identifies call frames with Script line and column information, and the
1186         // Script needs the formatter source map to produce the proper display line and column.
1187         if (this._sourceCode instanceof WebInspector.Resource && !(this._sourceCode instanceof WebInspector.SourceMapResource)) {
1188             var scripts = this._sourceCode.scripts;
1189             for (var i = 0; i < scripts.length; ++i)
1190                 scripts[i].formatterSourceMap = this.formatterSourceMap;
1191         } else if (this._sourceCode instanceof WebInspector.Script) {
1192             if (this._sourceCode.resource)
1193                 this._sourceCode.resource.formatterSourceMap = this.formatterSourceMap;
1194         }
1195
1196         // Some breakpoints / issues may have moved, some might not have. Just go through
1197         // and remove and reinsert all the breakpoints / issues.
1198
1199         var oldBreakpointMap = this._breakpointMap;
1200         this._breakpointMap = {};
1201
1202         for (var lineNumber in oldBreakpointMap) {
1203             for (var columnNumber in oldBreakpointMap[lineNumber]) {
1204                 var breakpoint = oldBreakpointMap[lineNumber][columnNumber];
1205                 var newLineInfo = this._editorLineInfoForSourceCodeLocation(breakpoint.sourceCodeLocation);
1206                 this._addBreakpointWithEditorLineInfo(breakpoint, newLineInfo);
1207                 this.setBreakpointInfoForLineAndColumn(lineNumber, columnNumber, null);
1208                 this.setBreakpointInfoForLineAndColumn(newLineInfo.lineNumber, newLineInfo.columnNumber, this._breakpointInfoForBreakpoint(breakpoint));
1209             }
1210         }
1211
1212         this._reinsertAllIssues();
1213     },
1214
1215     _clearWidgets: function()
1216     {
1217         for (var widget of this._widgetMap.values())
1218             widget.clear();
1219
1220         this._widgetMap.clear();
1221     },
1222
1223     _reinsertAllIssues: function()
1224     {
1225         this._issuesLineNumberMap.clear();
1226         this._clearWidgets();
1227
1228         var issues = WebInspector.issueManager.issuesForSourceCode(this._sourceCode);
1229         for (var issue of issues) {
1230             console.assert(this._matchesIssue(issue));
1231             this._addIssue(issue);
1232         }
1233     },
1234
1235     _debuggerDidPause: function(event)
1236     {
1237         this._updateTokenTrackingControllerState();
1238         if (this._typeTokenAnnotator && this._typeTokenAnnotator.isActive())
1239             this._typeTokenAnnotator.refresh();
1240         if (this._basicBlockAnnotator && this._basicBlockAnnotator.isActive())
1241             this._basicBlockAnnotator.refresh();
1242     },
1243
1244     _debuggerDidResume: function(event)
1245     {
1246         this._updateTokenTrackingControllerState();
1247         this._dismissPopover();
1248         if (this._typeTokenAnnotator && this._typeTokenAnnotator.isActive())
1249             this._typeTokenAnnotator.refresh();
1250         if (this._basicBlockAnnotator && this._basicBlockAnnotator.isActive())
1251             this._basicBlockAnnotator.refresh();
1252     },
1253
1254     _sourceCodeSourceMapAdded: function(event)
1255     {
1256         WebInspector.notifications.addEventListener(WebInspector.Notification.GlobalModifierKeysDidChange, this._updateTokenTrackingControllerState, this);
1257         this._sourceCode.removeEventListener(WebInspector.SourceCode.Event.SourceMapAdded, this._sourceCodeSourceMapAdded, this);
1258
1259         this._updateTokenTrackingControllerState();
1260     },
1261
1262     _updateTokenTrackingControllerState: function()
1263     {
1264         var mode = WebInspector.CodeMirrorTokenTrackingController.Mode.None;
1265         if (WebInspector.debuggerManager.paused)
1266             mode = WebInspector.CodeMirrorTokenTrackingController.Mode.JavaScriptExpression;
1267         else if (this._typeTokenAnnotator && this._typeTokenAnnotator.isActive())
1268             mode = WebInspector.CodeMirrorTokenTrackingController.Mode.JavaScriptTypeInformation;
1269         else if (this._hasColorMarkers())
1270             mode = WebInspector.CodeMirrorTokenTrackingController.Mode.MarkedTokens;
1271         else if ((this._sourceCode instanceof WebInspector.SourceMapResource || this._sourceCode.sourceMaps.length !== 0) && WebInspector.modifierKeys.metaKey && !WebInspector.modifierKeys.altKey && !WebInspector.modifierKeys.shiftKey)
1272             mode = WebInspector.CodeMirrorTokenTrackingController.Mode.NonSymbolTokens;
1273
1274         this.tokenTrackingController.enabled = mode !== WebInspector.CodeMirrorTokenTrackingController.Mode.None;
1275
1276         if (mode === this.tokenTrackingController.mode)
1277             return;
1278
1279         switch (mode) {
1280         case WebInspector.CodeMirrorTokenTrackingController.Mode.MarkedTokens:
1281             this.tokenTrackingController.mouseOverDelayDuration = 0;
1282             this.tokenTrackingController.mouseOutReleaseDelayDuration = 0;
1283             break;
1284         case WebInspector.CodeMirrorTokenTrackingController.Mode.NonSymbolTokens:
1285             this.tokenTrackingController.mouseOverDelayDuration = 0;
1286             this.tokenTrackingController.mouseOutReleaseDelayDuration = 0;
1287             this.tokenTrackingController.classNameForHighlightedRange = WebInspector.CodeMirrorTokenTrackingController.JumpToSymbolHighlightStyleClassName;
1288             this._dismissPopover();
1289             break;
1290         case WebInspector.CodeMirrorTokenTrackingController.Mode.JavaScriptExpression:
1291         case WebInspector.CodeMirrorTokenTrackingController.Mode.JavaScriptTypeInformation:
1292             this.tokenTrackingController.mouseOverDelayDuration = WebInspector.SourceCodeTextEditor.DurationToMouseOverTokenToMakeHoveredToken;
1293             this.tokenTrackingController.mouseOutReleaseDelayDuration = WebInspector.SourceCodeTextEditor.DurationToMouseOutOfHoveredTokenToRelease;
1294             this.tokenTrackingController.classNameForHighlightedRange = WebInspector.SourceCodeTextEditor.HoveredExpressionHighlightStyleClassName;
1295             break;
1296         }
1297
1298         this.tokenTrackingController.mode = mode;
1299     },
1300
1301     _hasColorMarkers: function()
1302     {
1303         for (var marker of this.markers) {
1304             if (marker.type === WebInspector.TextMarker.Type.Color)
1305                 return true;
1306         }
1307         return false;
1308     },
1309
1310     // CodeMirrorTokenTrackingController Delegate
1311
1312     tokenTrackingControllerCanReleaseHighlightedRange: function(tokenTrackingController, element)
1313     {
1314         if (!this._popover)
1315             return true;
1316
1317         if (!window.getSelection().isCollapsed && this._popover.element.contains(window.getSelection().anchorNode))
1318             return false;
1319
1320         return true;
1321     },
1322
1323     tokenTrackingControllerHighlightedRangeReleased: function(tokenTrackingController)
1324     {
1325         if (!this._mouseIsOverPopover)
1326             this._dismissPopover();
1327     },
1328
1329     tokenTrackingControllerHighlightedRangeWasClicked: function(tokenTrackingController)
1330     {
1331         if (this.tokenTrackingController.mode !== WebInspector.CodeMirrorTokenTrackingController.Mode.NonSymbolTokens)
1332             return;
1333
1334         // Links are handled by TextEditor.
1335         if (/\blink\b/.test(this.tokenTrackingController.candidate.hoveredToken.type))
1336             return;
1337
1338         var sourceCodeLocation = this._sourceCodeLocationForEditorPosition(this.tokenTrackingController.candidate.hoveredTokenRange.start);
1339         if (this.sourceCode instanceof WebInspector.SourceMapResource)
1340             WebInspector.resourceSidebarPanel.showOriginalOrFormattedSourceCodeLocation(sourceCodeLocation);
1341         else
1342             WebInspector.resourceSidebarPanel.showSourceCodeLocation(sourceCodeLocation);
1343     },
1344
1345     tokenTrackingControllerNewHighlightCandidate: function(tokenTrackingController, candidate)
1346     {
1347         if (this.tokenTrackingController.mode === WebInspector.CodeMirrorTokenTrackingController.Mode.NonSymbolTokens) {
1348             this.tokenTrackingController.highlightRange(candidate.hoveredTokenRange);
1349             return;
1350         }
1351
1352         if (this.tokenTrackingController.mode === WebInspector.CodeMirrorTokenTrackingController.Mode.JavaScriptExpression) {
1353             this._tokenTrackingControllerHighlightedJavaScriptExpression(candidate);
1354             return;
1355         }
1356
1357         if (this.tokenTrackingController.mode === WebInspector.CodeMirrorTokenTrackingController.Mode.JavaScriptTypeInformation) {
1358             this._tokenTrackingControllerHighlightedJavaScriptTypeInformation(candidate);
1359             return;
1360         }
1361
1362         if (this.tokenTrackingController.mode === WebInspector.CodeMirrorTokenTrackingController.Mode.MarkedTokens) {
1363             var markers = this.markersAtPosition(candidate.hoveredTokenRange.start);
1364             if (markers.length > 0)
1365                 this._tokenTrackingControllerHighlightedMarkedExpression(candidate, markers);
1366             else
1367                 this._dismissEditingController();
1368         }
1369     },
1370
1371     tokenTrackingControllerMouseOutOfHoveredMarker: function(tokenTrackingController, hoveredMarker)
1372     {
1373         this._dismissEditingController();
1374     },
1375
1376     _tokenTrackingControllerHighlightedJavaScriptExpression: function(candidate)
1377     {
1378         console.assert(candidate.expression);
1379
1380         function populate(error, result, wasThrown)
1381         {
1382             if (error || wasThrown)
1383                 return;
1384
1385             if (candidate !== this.tokenTrackingController.candidate)
1386                 return;
1387
1388             var data = WebInspector.RemoteObject.fromPayload(result);
1389             switch (data.type) {
1390             case "function":
1391                 this._showPopoverForFunction(data);
1392                 break;
1393             case "object":
1394                 if (data.subtype === "null" || data.subtype === "regexp")
1395                     this._showPopoverWithFormattedValue(data);
1396                 else
1397                     this._showPopoverForObject(data);
1398                 break;
1399             case "string":
1400             case "number":
1401             case "boolean":
1402             case "undefined":
1403             case "symbol":
1404                 this._showPopoverWithFormattedValue(data);
1405                 break;
1406             }
1407         }
1408
1409         if (WebInspector.debuggerManager.activeCallFrame) {
1410             DebuggerAgent.evaluateOnCallFrame.invoke({callFrameId: WebInspector.debuggerManager.activeCallFrame.id, expression: candidate.expression, objectGroup: "popover", doNotPauseOnExceptionsAndMuteConsole: true}, populate.bind(this));
1411             return;
1412         }
1413
1414         // No call frame available. Use the main page's context.
1415         RuntimeAgent.evaluate.invoke({expression: candidate.expression, objectGroup: "popover", doNotPauseOnExceptionsAndMuteConsole: true}, populate.bind(this));
1416     },
1417
1418     _tokenTrackingControllerHighlightedJavaScriptTypeInformation: function(candidate)
1419     {
1420         console.assert(candidate.expression);
1421
1422         var sourceCode = this._sourceCode;
1423         var sourceID = sourceCode.scripts[0].id;
1424         var range = candidate.hoveredTokenRange;
1425         var offset = this.currentPositionToOriginalOffset({line: range.start.line, ch: range.start.ch});
1426
1427         var allRequests = [{
1428             typeInformationDescriptor: WebInspector.ScriptSyntaxTree.TypeProfilerSearchDescriptor.NormalExpression,
1429             sourceID,
1430             divot: offset
1431         }];
1432
1433         function handler(error, allTypes) {
1434             if (error)
1435                 return;
1436
1437             if (candidate !== this.tokenTrackingController.candidate)
1438                 return;
1439
1440             console.assert(allTypes.length === 1);
1441             if (!allTypes.length)
1442                 return;
1443             var types = allTypes[0];
1444             if (types.isValid) {
1445                 var popoverTitle = WebInspector.TypeTokenView.titleForPopover(WebInspector.TypeTokenView.TitleType.Variable, candidate.expression);
1446                 this.showPopoverForTypes(types, null, popoverTitle);
1447             }
1448         }
1449
1450         RuntimeAgent.getRuntimeTypesForVariablesAtOffsets(allRequests, handler.bind(this));
1451     },
1452
1453     _showPopover: function(content, bounds)
1454     {
1455         console.assert(this.tokenTrackingController.candidate || bounds);
1456
1457         var shouldHighlightRange = false;
1458         var candidate = this.tokenTrackingController.candidate;
1459         // If bounds is falsey, this is a popover introduced from a hover event.
1460         // Otherwise, this is called from TypeTokenAnnotator.
1461         if (!bounds) {
1462             if (!candidate)
1463                 return;
1464
1465             var rects = this.rectsForRange(candidate.hoveredTokenRange);
1466             bounds = WebInspector.Rect.unionOfRects(rects);
1467
1468             shouldHighlightRange = true;
1469         }
1470
1471         content.classList.add(WebInspector.SourceCodeTextEditor.PopoverDebuggerContentStyleClassName);
1472
1473         this._popover = this._popover || new WebInspector.Popover(this);
1474         this._popover.presentNewContentWithFrame(content, bounds.pad(5), [WebInspector.RectEdge.MIN_Y, WebInspector.RectEdge.MAX_Y, WebInspector.RectEdge.MAX_X]);
1475         if (shouldHighlightRange)
1476             this.tokenTrackingController.highlightRange(candidate.expressionRange);
1477
1478         this._trackPopoverEvents();
1479     },
1480
1481     _showPopoverForFunction: function(data)
1482     {
1483         var candidate = this.tokenTrackingController.candidate;
1484
1485         function didGetDetails(error, response)
1486         {
1487             if (error) {
1488                 console.error(error);
1489                 this._dismissPopover();
1490                 return;
1491             }
1492
1493             // Nothing to do if the token has changed since the time we
1494             // asked for the function details from the backend.
1495             if (candidate !== this.tokenTrackingController.candidate)
1496                 return;
1497
1498             var wrapper = document.createElement("div");
1499             wrapper.className = "body formatted-function";
1500             wrapper.textContent = data.description;
1501
1502             var content = document.createElement("div");
1503             content.className = "function";
1504
1505             var title = content.appendChild(document.createElement("div"));
1506             title.className = "title";
1507             title.textContent = response.name || response.inferredName || response.displayName || WebInspector.UIString("(anonymous function)");
1508
1509             content.appendChild(wrapper);
1510
1511             this._showPopover(content);
1512         }
1513         DebuggerAgent.getFunctionDetails(data.objectId, didGetDetails.bind(this));
1514     },
1515
1516     _showPopoverForObject: function(data)
1517     {
1518         var content = document.createElement("div");
1519         content.className = "object expandable";
1520
1521         var titleElement = document.createElement("div");
1522         titleElement.className = "title";
1523         titleElement.textContent = data.description;
1524         content.appendChild(titleElement);
1525
1526         // FIXME: If this is a variable, it would be nice to put the variable name in the PropertyPath.
1527         var objectTree = new WebInspector.ObjectTreeView(data, WebInspector.ObjectTreeView.Mode.Properties, null);
1528         objectTree.showOnlyProperties();
1529         objectTree.expand();
1530
1531         var bodyElement = content.appendChild(document.createElement("div"));
1532         bodyElement.className = "body";
1533         bodyElement.appendChild(objectTree.element);
1534
1535         this._showPopover(content);
1536     },
1537
1538     _showPopoverWithFormattedValue: function(remoteObject)
1539     {
1540         var content = WebInspector.FormattedValue.createElementForRemoteObject(remoteObject);
1541         this._showPopover(content);
1542     },
1543
1544     willDismissPopover: function(popover)
1545     {
1546         this.tokenTrackingController.removeHighlightedRange();
1547
1548         RuntimeAgent.releaseObjectGroup("popover");
1549     },
1550
1551     _dismissPopover: function()
1552     {
1553         if (!this._popover)
1554             return;
1555
1556         this._popover.dismiss();
1557
1558         if (this._popoverEventListeners && this._popoverEventListenersAreRegistered) {
1559             this._popoverEventListenersAreRegistered = false;
1560             this._popoverEventListeners.unregister();
1561         }
1562     },
1563
1564     _trackPopoverEvents: function()
1565     {
1566         if (!this._popoverEventListeners) 
1567             this._popoverEventListeners = new WebInspector.EventListenerSet(this, "Popover listeners");
1568         if (!this._popoverEventListenersAreRegistered) {
1569             this._popoverEventListenersAreRegistered = true;
1570             this._popoverEventListeners.register(this._popover.element, "mouseover", this._popoverMouseover);
1571             this._popoverEventListeners.register(this._popover.element, "mouseout", this._popoverMouseout);
1572             this._popoverEventListeners.install();
1573         }
1574     },
1575
1576     _popoverMouseover: function(event)
1577     {
1578         this._mouseIsOverPopover = true;
1579     },
1580
1581     _popoverMouseout: function(event)
1582     {
1583         this._mouseIsOverPopover = this._popover.element.contains(event.relatedTarget);
1584     },
1585
1586     _updateEditableMarkers: function(range)
1587     {
1588         this.createColorMarkers(range);
1589         this.createGradientMarkers(range);
1590
1591         this._updateTokenTrackingControllerState();
1592     },
1593
1594     _tokenTrackingControllerHighlightedMarkedExpression: function(candidate, markers)
1595     {
1596         // Look for the outermost editable marker.
1597         var editableMarker;
1598         for (var marker of markers) {
1599             if (!marker.range || (marker.type !== WebInspector.TextMarker.Type.Color && marker.type !== WebInspector.TextMarker.Type.Gradient))
1600                 continue;
1601
1602             if (!editableMarker || (marker.range.startLine < editableMarker.range.startLine || (marker.range.startLine === editableMarker.range.startLine && marker.range.startColumn < editableMarker.range.startColumn)))
1603                 editableMarker = marker;
1604         }
1605
1606         if (!editableMarker) {
1607             this.tokenTrackingController.hoveredMarker = null;
1608             return;
1609         }
1610
1611         if (this.tokenTrackingController.hoveredMarker === editableMarker)
1612             return;
1613
1614         this._dismissEditingController();
1615
1616         this.tokenTrackingController.hoveredMarker = editableMarker;
1617
1618         this._editingController = this.editingControllerForMarker(editableMarker);
1619
1620         if (marker.type === WebInspector.TextMarker.Type.Color) {
1621             var color = this._editingController.value;
1622             if (!color || !color.valid) {
1623                 editableMarker.clear();
1624                 delete this._editingController;
1625                 return;
1626             }
1627         }
1628
1629         this._editingController.delegate = this;
1630         this._editingController.presentHoverMenu();
1631     },
1632
1633     _dismissEditingController: function(discrete)
1634     {
1635         if (this._editingController)
1636             this._editingController.dismissHoverMenu(discrete);
1637         
1638         this.tokenTrackingController.hoveredMarker = null;
1639         delete this._editingController;
1640     },
1641
1642     // CodeMirrorEditingController Delegate
1643     
1644     editingControllerDidStartEditing: function(editingController)
1645     {
1646         // We can pause the token tracking controller during editing, it will be reset
1647         // to the expected state by calling _updateEditableMarkers() in the
1648         // editingControllerDidFinishEditing delegate.
1649         this.tokenTrackingController.enabled = false;
1650
1651         // We clear the marker since we'll reset it after editing.
1652         editingController.marker.clear();
1653         
1654         // We ignore content changes made as a result of color editing.
1655         this._ignoreContentDidChange++;
1656     },
1657     
1658     editingControllerDidFinishEditing: function(editingController)
1659     {
1660         this._updateEditableMarkers(editingController.range);
1661
1662         this._ignoreContentDidChange--;
1663
1664         delete this._editingController;
1665     },
1666
1667     _setTypeTokenAnnotatorEnabledState: function(shouldActivate)
1668     {
1669         console.assert(this._typeTokenAnnotator);
1670         if (!this._typeTokenAnnotator)
1671             return;
1672
1673         if (shouldActivate) {
1674             RuntimeAgent.enableTypeProfiler();
1675
1676             this._typeTokenAnnotator.reset();
1677             if (this._basicBlockAnnotator) {
1678                 console.assert(!this._basicBlockAnnotator.isActive());
1679                 this._basicBlockAnnotator.reset();
1680             }
1681
1682             if (!this._typeTokenScrollHandler)
1683                 this._enableScrollEventsForTypeTokenAnnotator();
1684         } else {
1685             // Because we disable type profiling when exiting the inspector, there is no need to call 
1686             // RuntimeAgent.disableTypeProfiler() here.  If we were to call it here, JavaScriptCore would 
1687             // compile out all the necessary type profiling information, so if a user were to quickly press then 
1688             // unpress the type profiling button, we wouldn't be able to re-show type information which would 
1689             // provide a confusing user experience.
1690
1691             this._typeTokenAnnotator.clear();
1692             if (this._basicBlockAnnotator)
1693                 this._basicBlockAnnotator.clear();
1694
1695             if (this._typeTokenScrollHandler)
1696                 this._disableScrollEventsForTypeTokenAnnotator();
1697         }
1698
1699         WebInspector.showJavaScriptTypeInformationSetting.value = shouldActivate;
1700
1701         this._updateTokenTrackingControllerState();
1702     },
1703
1704     _getAssociatedScript: function()
1705     {
1706         var script = null;
1707         // FIXME: This needs to me modified to work with HTML files with inline script tags.
1708         if (this._sourceCode instanceof WebInspector.Script)
1709             script = this._sourceCode;
1710         else if (this._sourceCode instanceof WebInspector.Resource && this._sourceCode.type === WebInspector.Resource.Type.Script && this._sourceCode.scripts.length)
1711             script = this._sourceCode.scripts[0];
1712         return script;
1713     },
1714
1715     _makeTypeTokenAnnotator: function()
1716     {
1717         if (!RuntimeAgent.getRuntimeTypesForVariablesAtOffsets)
1718             return;
1719
1720         var script = this._getAssociatedScript();
1721         if (!script)
1722             return;
1723
1724         this._typeTokenAnnotator = new WebInspector.TypeTokenAnnotator(this, script);
1725     },
1726
1727     _makeBasicBlockAnnotator: function()
1728     {
1729         if (!RuntimeAgent.getBasicBlocks)
1730             return;
1731
1732         var script = this._getAssociatedScript();
1733         if (!script)
1734             return;
1735
1736         this._basicBlockAnnotator = new WebInspector.BasicBlockAnnotator(this, script);
1737     },
1738
1739     _enableScrollEventsForTypeTokenAnnotator: function()
1740     {
1741         // Pause updating type tokens while scrolling to prevent frame loss.
1742         console.assert(!this._typeTokenScrollHandler);
1743         this._typeTokenScrollHandler = this._makeTypeTokenScrollEventHandler();
1744         this.addScrollHandler(this._typeTokenScrollHandler);
1745     },
1746
1747     _disableScrollEventsForTypeTokenAnnotator: function()
1748     {
1749         console.assert(this._typeTokenScrollHandler);
1750         this.removeScrollHandler(this._typeTokenScrollHandler);
1751         this._typeTokenScrollHandler = null;
1752     },
1753
1754     _makeTypeTokenScrollEventHandler: function()
1755     {
1756         var timeoutIdentifier = null;
1757         function scrollHandler()
1758         {
1759             if (timeoutIdentifier)
1760                 clearTimeout(timeoutIdentifier);
1761             else {
1762                 if (this._typeTokenAnnotator)
1763                     this._typeTokenAnnotator.pause();
1764                 if (this._basicBlockAnnotator)
1765                     this._basicBlockAnnotator.pause();
1766             }
1767
1768             timeoutIdentifier = setTimeout(function() {
1769                 timeoutIdentifier = null;
1770                 if (this._typeTokenAnnotator)
1771                     this._typeTokenAnnotator.resume();
1772                 if (this._basicBlockAnnotator)
1773                     this._basicBlockAnnotator.resume();
1774             }.bind(this), WebInspector.SourceCodeTextEditor.DurationToUpdateTypeTokensAfterScrolling);
1775         }
1776
1777         return scrollHandler.bind(this);
1778     }
1779 };
1780
1781 WebInspector.SourceCodeTextEditor.prototype.__proto__ = WebInspector.TextEditor.prototype;