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