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