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