9268d5b0116d553a6d11cee5d0aea7b2b96c03b6
[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         var sourceCode = parameters.sourceCode;
514         var content = parameters.content;
515         var base64Encoded = parameters.base64Encoded;
516
517         console.assert(sourceCode === this._sourceCode);
518         console.assert(!base64Encoded);
519
520         // Abort if the full content populated while waiting for this async callback.
521         if (this._fullContentPopulated)
522             return;
523
524         this._fullContentPopulated = true;
525         this._invalidLineNumbers = {};
526
527         this._populateWithContent(content);
528     },
529
530     _breakpointStatusDidChange: function(event)
531     {
532         this._updateBreakpointStatus(event.target);
533     },
534
535     _breakpointsEnabledDidChange: function()
536     {
537         console.assert(this._supportsDebugging);
538
539         var breakpoints = WebInspector.debuggerManager.breakpointsForSourceCode(this._sourceCode);
540         for (var breakpoint of breakpoints)
541             this._updateBreakpointStatus(breakpoint);
542     },
543
544     _updateBreakpointStatus: function(breakpoint)
545     {
546         console.assert(this._supportsDebugging);
547
548         if (!this._contentPopulated)
549             return;
550
551         if (!this._matchesBreakpoint(breakpoint))
552             return;
553
554         var lineInfo = this._editorLineInfoForSourceCodeLocation(breakpoint.sourceCodeLocation);
555         this.setBreakpointInfoForLineAndColumn(lineInfo.lineNumber, lineInfo.columnNumber, this._breakpointInfoForBreakpoint(breakpoint));
556     },
557
558     _updateBreakpointLocation: function(event)
559     {
560         console.assert(this._supportsDebugging);
561
562         if (!this._contentPopulated)
563             return;
564
565         var breakpoint = event.target;
566         if (!this._matchesBreakpoint(breakpoint))
567             return;
568
569         if (this._ignoreAllBreakpointLocationUpdates)
570             return;
571
572         if (breakpoint === this._ignoreLocationUpdateBreakpoint)
573             return;
574
575         var sourceCodeLocation = breakpoint.sourceCodeLocation;
576
577         if (this._sourceCode instanceof WebInspector.SourceMapResource) {
578             // Update our breakpoint location if the display location changed.
579             if (sourceCodeLocation.displaySourceCode !== this._sourceCode)
580                 return;
581             var oldLineInfo = {lineNumber: event.data.oldDisplayLineNumber, columnNumber: event.data.oldDisplayColumnNumber};
582             var newLineInfo = {lineNumber: sourceCodeLocation.displayLineNumber, columnNumber: sourceCodeLocation.displayColumnNumber};
583         } else {
584             // Update our breakpoint location if the original location changed.
585             if (sourceCodeLocation.sourceCode !== this._sourceCode)
586                 return;
587             var oldLineInfo = {lineNumber: event.data.oldFormattedLineNumber, columnNumber: event.data.oldFormattedColumnNumber};
588             var newLineInfo = {lineNumber: sourceCodeLocation.formattedLineNumber, columnNumber: sourceCodeLocation.formattedColumnNumber};
589         }
590
591         var existingBreakpoint = this._breakpointForEditorLineInfo(oldLineInfo);
592         if (!existingBreakpoint)
593             return;
594
595         console.assert(breakpoint === existingBreakpoint);
596
597         this.setBreakpointInfoForLineAndColumn(oldLineInfo.lineNumber, oldLineInfo.columnNumber, null);
598         this.setBreakpointInfoForLineAndColumn(newLineInfo.lineNumber, newLineInfo.columnNumber, this._breakpointInfoForBreakpoint(breakpoint));
599
600         this._removeBreakpointWithEditorLineInfo(breakpoint, oldLineInfo);
601         this._addBreakpointWithEditorLineInfo(breakpoint, newLineInfo);
602     },
603
604     _breakpointAdded: function(event)
605     {
606         console.assert(this._supportsDebugging);
607
608         if (!this._contentPopulated)
609             return;
610
611         var breakpoint = event.data.breakpoint;
612         if (!this._matchesBreakpoint(breakpoint))
613             return;
614
615         if (breakpoint === this._ignoreBreakpointAddedBreakpoint)
616             return;
617
618         var lineInfo = this._editorLineInfoForSourceCodeLocation(breakpoint.sourceCodeLocation);
619         this._addBreakpointWithEditorLineInfo(breakpoint, lineInfo);
620         this.setBreakpointInfoForLineAndColumn(lineInfo.lineNumber, lineInfo.columnNumber, this._breakpointInfoForBreakpoint(breakpoint));
621     },
622
623     _breakpointRemoved: function(event)
624     {
625         console.assert(this._supportsDebugging);
626
627         if (!this._contentPopulated)
628             return;
629
630         var breakpoint = event.data.breakpoint;
631         if (!this._matchesBreakpoint(breakpoint))
632             return;
633
634         if (breakpoint === this._ignoreBreakpointRemovedBreakpoint)
635             return;
636
637         var lineInfo = this._editorLineInfoForSourceCodeLocation(breakpoint.sourceCodeLocation);
638         this._removeBreakpointWithEditorLineInfo(breakpoint, lineInfo);
639         this.setBreakpointInfoForLineAndColumn(lineInfo.lineNumber, lineInfo.columnNumber, null);
640     },
641
642     _activeCallFrameDidChange: function()
643     {
644         console.assert(this._supportsDebugging);
645
646         if (this._activeCallFrameSourceCodeLocation) {
647             this._activeCallFrameSourceCodeLocation.removeEventListener(WebInspector.SourceCodeLocation.Event.LocationChanged, this._activeCallFrameSourceCodeLocationChanged, this);
648             delete this._activeCallFrameSourceCodeLocation;
649         }
650
651         var activeCallFrame = WebInspector.debuggerManager.activeCallFrame;
652         if (!activeCallFrame || !this._matchesSourceCodeLocation(activeCallFrame.sourceCodeLocation)) {
653             this.executionLineNumber = NaN;
654             this.executionColumnNumber = NaN;
655             return;
656         }
657
658         this._dismissPopover();
659
660         this._activeCallFrameSourceCodeLocation = activeCallFrame.sourceCodeLocation;
661         this._activeCallFrameSourceCodeLocation.addEventListener(WebInspector.SourceCodeLocation.Event.LocationChanged, this._activeCallFrameSourceCodeLocationChanged, this);
662
663         // Don't return early if the line number didn't change. The execution state still
664         // could have changed (e.g. continuing in a loop with a breakpoint inside).
665
666         var lineInfo = this._editorLineInfoForSourceCodeLocation(activeCallFrame.sourceCodeLocation);
667         this.executionLineNumber = lineInfo.lineNumber;
668         this.executionColumnNumber = lineInfo.columnNumber;
669
670         // If we have full content or this source code isn't a Resource we can return early.
671         // Script source code populates from the request started in the constructor.
672         if (this._fullContentPopulated || !(this._sourceCode instanceof WebInspector.Resource) || this._requestingScriptContent)
673             return;
674
675         // Since we are paused in the debugger we need to show some content, and since the Resource
676         // content hasn't populated yet we need to populate with content from the Scripts by URL.
677         // Document resources will attempt to populate the scripts as inline (in <script> tags.)
678         // Other resources are assumed to be full scripts (JavaScript resources).
679         if (this._sourceCode.type === WebInspector.Resource.Type.Document)
680             this._populateWithInlineScriptContent();
681         else
682             this._populateWithScriptContent();
683     },
684
685     _activeCallFrameSourceCodeLocationChanged: function(event)
686     {
687         console.assert(!isNaN(this.executionLineNumber));
688         if (isNaN(this.executionLineNumber))
689             return;
690
691         console.assert(WebInspector.debuggerManager.activeCallFrame);
692         console.assert(this._activeCallFrameSourceCodeLocation === WebInspector.debuggerManager.activeCallFrame.sourceCodeLocation);
693
694         var lineInfo = this._editorLineInfoForSourceCodeLocation(this._activeCallFrameSourceCodeLocation);
695         this.executionLineNumber = lineInfo.lineNumber;
696         this.executionColumnNumber = lineInfo.columnNumber;
697     },
698
699     _populateWithInlineScriptContent: function()
700     {
701         console.assert(this._sourceCode instanceof WebInspector.Resource);
702         console.assert(!this._fullContentPopulated);
703         console.assert(!this._requestingScriptContent);
704
705         var scripts = this._sourceCode.scripts;
706         console.assert(scripts.length);
707         if (!scripts.length)
708             return;
709
710         var pendingRequestCount = scripts.length;
711
712         // If the number of scripts hasn't change since the last populate, then there is nothing to do.
713         if (this._inlineScriptContentPopulated === pendingRequestCount)
714             return;
715
716         this._inlineScriptContentPopulated = pendingRequestCount;
717
718         function scriptContentAvailable(parameters)
719         {
720             // Return early if we are still waiting for content from other scripts.
721             if (--pendingRequestCount)
722                 return;
723
724             delete this._requestingScriptContent;
725
726             // Abort if the full content populated while waiting for these async callbacks.
727             if (this._fullContentPopulated)
728                 return;
729
730             const scriptOpenTag = "<script>";
731             const scriptCloseTag = "</script>";
732
733             var content = "";
734             var lineNumber = 0;
735             var columnNumber = 0;
736
737             this._invalidLineNumbers = {};
738
739             for (var i = 0; i < scripts.length; ++i) {
740                 // Fill the line gap with newline characters.
741                 for (var newLinesCount = scripts[i].range.startLine - lineNumber; newLinesCount > 0; --newLinesCount) {
742                     if (!columnNumber)
743                         this._invalidLineNumbers[scripts[i].range.startLine - newLinesCount] = true;
744                     columnNumber = 0;
745                     content += "\n";
746                 }
747
748                 // Fill the column gap with space characters.
749                 for (var spacesCount = scripts[i].range.startColumn - columnNumber - scriptOpenTag.length; spacesCount > 0; --spacesCount)
750                     content += " ";
751
752                 // Add script tags and content.
753                 content += scriptOpenTag;
754                 content += scripts[i].content;
755                 content += scriptCloseTag;
756
757                 lineNumber = scripts[i].range.endLine;
758                 columnNumber = scripts[i].range.endColumn + scriptCloseTag.length;
759             }
760
761             this._populateWithContent(content);
762         }
763
764         this._requestingScriptContent = true;
765
766         var boundScriptContentAvailable = scriptContentAvailable.bind(this);
767         for (var i = 0; i < scripts.length; ++i)
768             scripts[i].requestContent().then(boundScriptContentAvailable);
769     },
770
771     _populateWithScriptContent: function()
772     {
773         console.assert(this._sourceCode instanceof WebInspector.Resource);
774         console.assert(!this._fullContentPopulated);
775         console.assert(!this._requestingScriptContent);
776
777         // We can assume this resource only has one script that starts at line/column 0.
778         var scripts = this._sourceCode.scripts;
779         console.assert(scripts.length === 1);
780         if (!scripts.length)
781             return;
782
783         console.assert(scripts[0].range.startLine === 0);
784         console.assert(scripts[0].range.startColumn === 0);
785
786         function scriptContentAvailable(parameters)
787         {
788             var content = parameters.content;
789             delete this._requestingScriptContent;
790
791             // Abort if the full content populated while waiting for this async callback.
792             if (this._fullContentPopulated)
793                 return;
794
795             // This is the full content.
796             this._fullContentPopulated = true;
797
798             this._populateWithContent(content);
799         }
800
801         this._requestingScriptContent = true;
802
803         scripts[0].requestContent().then(scriptContentAvailable.bind(this));
804     },
805
806     _matchesSourceCodeLocation: function(sourceCodeLocation)
807     {
808         if (this._sourceCode instanceof WebInspector.SourceMapResource)
809             return sourceCodeLocation.displaySourceCode === this._sourceCode;
810         if (this._sourceCode instanceof WebInspector.Resource)
811             return sourceCodeLocation.sourceCode.url === this._sourceCode.url;
812         if (this._sourceCode instanceof WebInspector.Script)
813             return sourceCodeLocation.sourceCode === this._sourceCode;
814         return false;
815     },
816
817     _matchesBreakpoint: function(breakpoint)
818     {
819         console.assert(this._supportsDebugging);
820         if (this._sourceCode instanceof WebInspector.SourceMapResource)
821             return breakpoint.sourceCodeLocation.displaySourceCode === this._sourceCode;
822         if (this._sourceCode instanceof WebInspector.Resource)
823             return breakpoint.url === this._sourceCode.url;
824         if (this._sourceCode instanceof WebInspector.Script)
825             return breakpoint.url === this._sourceCode.url || breakpoint.scriptIdentifier === this._sourceCode.id;
826         return false;
827     },
828
829     _matchesIssue: function(issue)
830     {
831         if (this._sourceCode instanceof WebInspector.Resource)
832             return issue.url === this._sourceCode.url;
833         // FIXME: Support issues for Scripts based on id, not only by URL.
834         if (this._sourceCode instanceof WebInspector.Script)
835             return issue.url === this._sourceCode.url;
836         return false;
837     },
838
839     _issueWasAdded: function(event)
840     {
841         var issue = event.data.issue;
842         if (!this._matchesIssue(issue))
843             return;
844
845         this._addIssue(issue);
846     },
847
848     _addIssue: function(issue)
849     {
850         var lineNumberIssues = this._issuesLineNumberMap[issue.lineNumber];
851         if (!lineNumberIssues)
852             lineNumberIssues = this._issuesLineNumberMap[issue.lineNumber] = [];
853
854         lineNumberIssues.push(issue);
855
856         if (issue.level === WebInspector.IssueMessage.Level.Error)
857             this.addStyleClassToLine(issue.lineNumber, WebInspector.SourceCodeTextEditor.LineErrorStyleClassName);
858         else if (issue.level === WebInspector.IssueMessage.Level.Warning)
859             this.addStyleClassToLine(issue.lineNumber, WebInspector.SourceCodeTextEditor.LineWarningStyleClassName);
860         else
861             console.error("Unknown issue level");
862
863         // FIXME <rdar://problem/10854857>: Show the issue message on the line as a bubble.
864     },
865
866     _breakpointInfoForBreakpoint: function(breakpoint)
867     {
868         return {resolved: breakpoint.resolved, disabled: breakpoint.disabled, autoContinue: breakpoint.autoContinue};
869     },
870
871     get _supportsDebugging()
872     {
873         if (this._sourceCode instanceof WebInspector.Resource)
874             return this._sourceCode.type === WebInspector.Resource.Type.Document || this._sourceCode.type === WebInspector.Resource.Type.Script;
875         if (this._sourceCode instanceof WebInspector.Script)
876             return true;
877         return false;
878     },
879
880     // TextEditor Delegate
881
882     textEditorBaseURL: function(textEditor)
883     {
884         return this._sourceCode.url;
885     },
886
887     textEditorShouldHideLineNumber: function(textEditor, lineNumber)
888     {
889         return lineNumber in this._invalidLineNumbers;
890     },
891
892     textEditorGutterContextMenu: function(textEditor, lineNumber, columnNumber, editorBreakpoints, event)
893     {
894         if (!this._supportsDebugging)
895             return;
896
897         event.preventDefault();
898
899         var contextMenu = new WebInspector.ContextMenu(event);
900
901         // Paused. Add Continue to Here option only if we have a script identifier for the location.
902         if (WebInspector.debuggerManager.paused) {
903             var editorLineInfo = {lineNumber:lineNumber, columnNumber:columnNumber};
904             var unformattedLineInfo = this._unformattedLineInfoForEditorLineInfo(editorLineInfo);
905             var sourceCodeLocation = this._sourceCode.createSourceCodeLocation(unformattedLineInfo.lineNumber, unformattedLineInfo.columnNumber);
906
907             if (sourceCodeLocation.sourceCode instanceof WebInspector.Script)
908                 var script = sourceCodeLocation.sourceCode;
909             else if (sourceCodeLocation.sourceCode instanceof WebInspector.Resource)
910                 var script = sourceCodeLocation.sourceCode.scriptForLocation(sourceCodeLocation);
911
912             if (script) {
913                 function continueToLocation()
914                 {
915                     WebInspector.debuggerManager.continueToLocation(script.id, sourceCodeLocation.lineNumber, sourceCodeLocation.columnNumber);
916                 }
917
918                 contextMenu.appendItem(WebInspector.UIString("Continue to Here"), continueToLocation);
919                 contextMenu.appendSeparator();
920             }
921         }
922
923         var breakpoints = [];
924         for (var i = 0; i < editorBreakpoints.length; ++i) {
925             var lineInfo = editorBreakpoints[i];
926             var breakpoint = this._breakpointForEditorLineInfo(lineInfo);
927             console.assert(breakpoint);
928             if (breakpoint)
929                 breakpoints.push(breakpoint);
930         }
931
932         // No breakpoints.
933         if (!breakpoints.length) {
934             function addBreakpoint()
935             {
936                 var data = this.textEditorBreakpointAdded(this, lineNumber, columnNumber);
937                 this.setBreakpointInfoForLineAndColumn(data.lineNumber, data.columnNumber, data.breakpointInfo);
938             }
939
940             contextMenu.appendItem(WebInspector.UIString("Add Breakpoint"), addBreakpoint.bind(this));
941             contextMenu.show();
942             return;
943         }
944
945         // Single breakpoint.
946         if (breakpoints.length === 1) {
947             var breakpoint = breakpoints[0];
948             function revealInSidebar()
949             {
950                 WebInspector.debuggerSidebarPanel.show();
951                 var treeElement = WebInspector.debuggerSidebarPanel.treeElementForRepresentedObject(breakpoint);
952                 if (treeElement)
953                     treeElement.revealAndSelect();
954             }
955
956             breakpoint.appendContextMenuItems(contextMenu, event.target);
957             contextMenu.appendSeparator();
958             contextMenu.appendItem(WebInspector.UIString("Reveal in Debugger Navigation Sidebar"), revealInSidebar);
959             contextMenu.show();
960             return;
961         }
962
963         // Multiple breakpoints.
964         var shouldDisable = false;
965         for (var i = 0; i < breakpoints.length; ++i) {
966             if (!breakpoints[i].disabled) {
967                 shouldDisable = true;
968                 break;
969             }
970         }
971
972         function removeBreakpoints()
973         {
974             for (var i = 0; i < breakpoints.length; ++i) {
975                 var breakpoint = breakpoints[i];
976                 if (WebInspector.debuggerManager.isBreakpointRemovable(breakpoint))
977                     WebInspector.debuggerManager.removeBreakpoint(breakpoint);
978             }
979         }
980
981         function toggleBreakpoints()
982         {
983             for (var i = 0; i < breakpoints.length; ++i)
984                 breakpoints[i].disabled = shouldDisable;
985         }
986
987         if (shouldDisable)
988             contextMenu.appendItem(WebInspector.UIString("Disable Breakpoints"), toggleBreakpoints.bind(this));
989         else
990             contextMenu.appendItem(WebInspector.UIString("Enable Breakpoints"), toggleBreakpoints.bind(this));
991         contextMenu.appendItem(WebInspector.UIString("Delete Breakpoints"), removeBreakpoints.bind(this));
992         contextMenu.show();
993     },
994
995     textEditorBreakpointAdded: function(textEditor, lineNumber, columnNumber)
996     {
997         if (!this._supportsDebugging)
998             return null;
999
1000         var editorLineInfo = {lineNumber:lineNumber, columnNumber:columnNumber};
1001         var unformattedLineInfo = this._unformattedLineInfoForEditorLineInfo(editorLineInfo);
1002         var sourceCodeLocation = this._sourceCode.createSourceCodeLocation(unformattedLineInfo.lineNumber, unformattedLineInfo.columnNumber);
1003         var breakpoint = new WebInspector.Breakpoint(sourceCodeLocation);
1004
1005         var lineInfo = this._editorLineInfoForSourceCodeLocation(breakpoint.sourceCodeLocation);
1006         this._addBreakpointWithEditorLineInfo(breakpoint, lineInfo);
1007
1008         this._ignoreBreakpointAddedBreakpoint = breakpoint;
1009
1010         var shouldSkipEventDispatch = false;
1011         var shouldSpeculativelyResolveBreakpoint = true;
1012         WebInspector.debuggerManager.addBreakpoint(breakpoint, shouldSkipEventDispatch, shouldSpeculativelyResolveBreakpoint);
1013         delete this._ignoreBreakpointAddedBreakpoint;
1014
1015         // Return the more accurate location and breakpoint info.
1016         return {
1017             breakpointInfo: this._breakpointInfoForBreakpoint(breakpoint),
1018             lineNumber: lineInfo.lineNumber,
1019             columnNumber: lineInfo.columnNumber
1020         };
1021     },
1022
1023     textEditorBreakpointRemoved: function(textEditor, lineNumber, columnNumber)
1024     {
1025         console.assert(this._supportsDebugging);
1026         if (!this._supportsDebugging)
1027             return;
1028
1029         var lineInfo = {lineNumber: lineNumber, columnNumber: columnNumber};
1030         var breakpoint = this._breakpointForEditorLineInfo(lineInfo);
1031         console.assert(breakpoint);
1032         if (!breakpoint)
1033             return;
1034
1035         this._removeBreakpointWithEditorLineInfo(breakpoint, lineInfo);
1036
1037         this._ignoreBreakpointRemovedBreakpoint = breakpoint;
1038         WebInspector.debuggerManager.removeBreakpoint(breakpoint);
1039         delete this._ignoreBreakpointAddedBreakpoint;
1040     },
1041
1042     textEditorBreakpointMoved: function(textEditor, oldLineNumber, oldColumnNumber, newLineNumber, newColumnNumber)
1043     {
1044         console.assert(this._supportsDebugging);
1045         if (!this._supportsDebugging)
1046             return;
1047
1048         var oldLineInfo = {lineNumber: oldLineNumber, columnNumber: oldColumnNumber};
1049         var breakpoint = this._breakpointForEditorLineInfo(oldLineInfo);
1050         console.assert(breakpoint);
1051         if (!breakpoint)
1052             return;
1053
1054         this._removeBreakpointWithEditorLineInfo(breakpoint, oldLineInfo);
1055
1056         var newLineInfo = {lineNumber: newLineNumber, columnNumber: newColumnNumber};
1057         var unformattedNewLineInfo = this._unformattedLineInfoForEditorLineInfo(newLineInfo);
1058         this._ignoreLocationUpdateBreakpoint = breakpoint;
1059         breakpoint.sourceCodeLocation.update(this._sourceCode, unformattedNewLineInfo.lineNumber, unformattedNewLineInfo.columnNumber);
1060         delete this._ignoreLocationUpdateBreakpoint;
1061
1062         var accurateNewLineInfo = this._editorLineInfoForSourceCodeLocation(breakpoint.sourceCodeLocation);
1063         this._addBreakpointWithEditorLineInfo(breakpoint, accurateNewLineInfo);
1064
1065         if (accurateNewLineInfo.lineNumber !== newLineInfo.lineNumber || accurateNewLineInfo.columnNumber !== newLineInfo.columnNumber)
1066             this.updateBreakpointLineAndColumn(newLineInfo.lineNumber, newLineInfo.columnNumber, accurateNewLineInfo.lineNumber, accurateNewLineInfo.columnNumber);
1067     },
1068
1069     textEditorBreakpointClicked: function(textEditor, lineNumber, columnNumber)
1070     {
1071         console.assert(this._supportsDebugging);
1072         if (!this._supportsDebugging)
1073             return;
1074
1075         var breakpoint = this._breakpointForEditorLineInfo({lineNumber: lineNumber, columnNumber: columnNumber});
1076         console.assert(breakpoint);
1077         if (!breakpoint)
1078             return;
1079
1080         breakpoint.cycleToNextMode();
1081     },
1082
1083     textEditorUpdatedFormatting: function(textEditor)
1084     {
1085         this._ignoreAllBreakpointLocationUpdates = true;
1086         this._sourceCode.formatterSourceMap = this.formatterSourceMap;
1087         delete this._ignoreAllBreakpointLocationUpdates;
1088
1089         // Always put the source map on both the Script and Resource if both exist. For example,
1090         // if this SourceCode is a Resource, then there might also be a Script. In the debugger,
1091         // the backend identifies call frames with Script line and column information, and the
1092         // Script needs the formatter source map to produce the proper display line and column.
1093         if (this._sourceCode instanceof WebInspector.Resource && !(this._sourceCode instanceof WebInspector.SourceMapResource)) {
1094             var scripts = this._sourceCode.scripts;
1095             for (var i = 0; i < scripts.length; ++i)
1096                 scripts[i].formatterSourceMap = this.formatterSourceMap;
1097         } else if (this._sourceCode instanceof WebInspector.Script) {
1098             if (this._sourceCode.resource)
1099                 this._sourceCode.resource.formatterSourceMap = this.formatterSourceMap;
1100         }
1101
1102         // Some breakpoints may have moved, some might not have. Just go through
1103         // and remove and reinsert all the breakpoints.
1104
1105         var oldBreakpointMap = this._breakpointMap;
1106         this._breakpointMap = {};
1107
1108         for (var lineNumber in oldBreakpointMap) {
1109             for (var columnNumber in oldBreakpointMap[lineNumber]) {
1110                 var breakpoint = oldBreakpointMap[lineNumber][columnNumber];
1111                 var newLineInfo = this._editorLineInfoForSourceCodeLocation(breakpoint.sourceCodeLocation);
1112                 this._addBreakpointWithEditorLineInfo(breakpoint, newLineInfo);
1113                 this.setBreakpointInfoForLineAndColumn(lineNumber, columnNumber, null);
1114                 this.setBreakpointInfoForLineAndColumn(newLineInfo.lineNumber, newLineInfo.columnNumber, this._breakpointInfoForBreakpoint(breakpoint));
1115             }
1116         }
1117     },
1118
1119     _debuggerDidPause: function(event)
1120     {
1121         this._updateTokenTrackingControllerState();
1122         if (this._typeTokenAnnotator && this._typeTokenAnnotator.isActive())
1123             this._typeTokenAnnotator.refresh();
1124         if (this._basicBlockAnnotator && this._basicBlockAnnotator.isActive())
1125             this._basicBlockAnnotator.refresh();
1126     },
1127
1128     _debuggerDidResume: function(event)
1129     {
1130         this._updateTokenTrackingControllerState();
1131         this._dismissPopover();
1132         if (this._typeTokenAnnotator && this._typeTokenAnnotator.isActive())
1133             this._typeTokenAnnotator.refresh();
1134         if (this._basicBlockAnnotator && this._basicBlockAnnotator.isActive())
1135             this._basicBlockAnnotator.refresh();
1136     },
1137
1138     _sourceCodeSourceMapAdded: function(event)
1139     {
1140         WebInspector.notifications.addEventListener(WebInspector.Notification.GlobalModifierKeysDidChange, this._updateTokenTrackingControllerState, this);
1141         this._sourceCode.removeEventListener(WebInspector.SourceCode.Event.SourceMapAdded, this._sourceCodeSourceMapAdded, this);
1142
1143         this._updateTokenTrackingControllerState();
1144     },
1145
1146     _updateTokenTrackingControllerState: function()
1147     {
1148         var mode = WebInspector.CodeMirrorTokenTrackingController.Mode.None;
1149         if (WebInspector.debuggerManager.paused)
1150             mode = WebInspector.CodeMirrorTokenTrackingController.Mode.JavaScriptExpression;
1151         else if (this._typeTokenAnnotator && this._typeTokenAnnotator.isActive())
1152             mode = WebInspector.CodeMirrorTokenTrackingController.Mode.JavaScriptTypeInformation;
1153         else if (this._hasColorMarkers())
1154             mode = WebInspector.CodeMirrorTokenTrackingController.Mode.MarkedTokens;
1155         else if ((this._sourceCode instanceof WebInspector.SourceMapResource || this._sourceCode.sourceMaps.length !== 0) && WebInspector.modifierKeys.metaKey && !WebInspector.modifierKeys.altKey && !WebInspector.modifierKeys.shiftKey)
1156             mode = WebInspector.CodeMirrorTokenTrackingController.Mode.NonSymbolTokens;
1157
1158         this.tokenTrackingController.enabled = mode !== WebInspector.CodeMirrorTokenTrackingController.Mode.None;
1159
1160         if (mode === this.tokenTrackingController.mode)
1161             return;
1162
1163         switch (mode) {
1164         case WebInspector.CodeMirrorTokenTrackingController.Mode.MarkedTokens:
1165             this.tokenTrackingController.mouseOverDelayDuration = 0;
1166             this.tokenTrackingController.mouseOutReleaseDelayDuration = 0;
1167             break;
1168         case WebInspector.CodeMirrorTokenTrackingController.Mode.NonSymbolTokens:
1169             this.tokenTrackingController.mouseOverDelayDuration = 0;
1170             this.tokenTrackingController.mouseOutReleaseDelayDuration = 0;
1171             this.tokenTrackingController.classNameForHighlightedRange = WebInspector.CodeMirrorTokenTrackingController.JumpToSymbolHighlightStyleClassName;
1172             this._dismissPopover();
1173             break;
1174         case WebInspector.CodeMirrorTokenTrackingController.Mode.JavaScriptExpression:
1175         case WebInspector.CodeMirrorTokenTrackingController.Mode.JavaScriptTypeInformation:
1176             this.tokenTrackingController.mouseOverDelayDuration = WebInspector.SourceCodeTextEditor.DurationToMouseOverTokenToMakeHoveredToken;
1177             this.tokenTrackingController.mouseOutReleaseDelayDuration = WebInspector.SourceCodeTextEditor.DurationToMouseOutOfHoveredTokenToRelease;
1178             this.tokenTrackingController.classNameForHighlightedRange = WebInspector.SourceCodeTextEditor.HoveredExpressionHighlightStyleClassName;
1179             break;
1180         }
1181
1182         this.tokenTrackingController.mode = mode;
1183     },
1184
1185     _hasColorMarkers: function()
1186     {
1187         for (var marker of this.markers) {
1188             if (marker.type === WebInspector.TextMarker.Type.Color)
1189                 return true;
1190         }
1191         return false;
1192     },
1193
1194     // CodeMirrorTokenTrackingController Delegate
1195
1196     tokenTrackingControllerCanReleaseHighlightedRange: function(tokenTrackingController, element)
1197     {
1198         if (!this._popover)
1199             return true;
1200
1201         if (!window.getSelection().isCollapsed && this._popover.element.contains(window.getSelection().anchorNode))
1202             return false;
1203
1204         return true;
1205     },
1206
1207     tokenTrackingControllerHighlightedRangeReleased: function(tokenTrackingController)
1208     {
1209         if (!this._mouseIsOverPopover)
1210             this._dismissPopover();
1211     },
1212
1213     tokenTrackingControllerHighlightedRangeWasClicked: function(tokenTrackingController)
1214     {
1215         if (this.tokenTrackingController.mode !== WebInspector.CodeMirrorTokenTrackingController.Mode.NonSymbolTokens)
1216             return;
1217
1218         // Links are handled by TextEditor.
1219         if (/\blink\b/.test(this.tokenTrackingController.candidate.hoveredToken.type))
1220             return;
1221
1222         var sourceCodeLocation = this._sourceCodeLocationForEditorPosition(this.tokenTrackingController.candidate.hoveredTokenRange.start);
1223         if (this.sourceCode instanceof WebInspector.SourceMapResource)
1224             WebInspector.resourceSidebarPanel.showOriginalOrFormattedSourceCodeLocation(sourceCodeLocation);
1225         else
1226             WebInspector.resourceSidebarPanel.showSourceCodeLocation(sourceCodeLocation);
1227     },
1228
1229     tokenTrackingControllerNewHighlightCandidate: function(tokenTrackingController, candidate)
1230     {
1231         if (this.tokenTrackingController.mode === WebInspector.CodeMirrorTokenTrackingController.Mode.NonSymbolTokens) {
1232             this.tokenTrackingController.highlightRange(candidate.hoveredTokenRange);
1233             return;
1234         }
1235
1236         if (this.tokenTrackingController.mode === WebInspector.CodeMirrorTokenTrackingController.Mode.JavaScriptExpression) {
1237             this._tokenTrackingControllerHighlightedJavaScriptExpression(candidate);
1238             return;
1239         }
1240
1241         if (this.tokenTrackingController.mode === WebInspector.CodeMirrorTokenTrackingController.Mode.JavaScriptTypeInformation) {
1242             this._tokenTrackingControllerHighlightedJavaScriptTypeInformation(candidate);
1243             return;
1244         }
1245
1246         if (this.tokenTrackingController.mode === WebInspector.CodeMirrorTokenTrackingController.Mode.MarkedTokens) {
1247             var markers = this.markersAtPosition(candidate.hoveredTokenRange.start);
1248             if (markers.length > 0)
1249                 this._tokenTrackingControllerHighlightedMarkedExpression(candidate, markers);
1250             else
1251                 this._dismissEditingController();
1252         }
1253     },
1254
1255     tokenTrackingControllerMouseOutOfHoveredMarker: function(tokenTrackingController, hoveredMarker)
1256     {
1257         this._dismissEditingController();
1258     },
1259
1260     _tokenTrackingControllerHighlightedJavaScriptExpression: function(candidate)
1261     {
1262         console.assert(candidate.expression);
1263
1264         function populate(error, result, wasThrown)
1265         {
1266             if (error || wasThrown)
1267                 return;
1268
1269             if (candidate !== this.tokenTrackingController.candidate)
1270                 return;
1271
1272             var data = WebInspector.RemoteObject.fromPayload(result);
1273             switch (data.type) {
1274             case "function":
1275                 this._showPopoverForFunction(data);
1276                 break;
1277             case "object":
1278                 if (data.subtype === "regexp") 
1279                     this._showPopoverForRegExp(data);
1280                 else
1281                     this._showPopoverForObject(data);
1282                 break;
1283             case "string":
1284                 this._showPopoverForString(data);
1285                 break;
1286             case "number":
1287                 this._showPopoverForNumber(data);
1288                 break;
1289             case "boolean":
1290                 this._showPopoverForBoolean(data);
1291                 break;
1292             case "undefined":
1293                 this._showPopoverForUndefined(data);
1294                 break;
1295             }
1296         }
1297
1298         if (WebInspector.debuggerManager.activeCallFrame) {
1299             DebuggerAgent.evaluateOnCallFrame.invoke({callFrameId: WebInspector.debuggerManager.activeCallFrame.id, expression: candidate.expression, objectGroup: "popover", doNotPauseOnExceptionsAndMuteConsole: true}, populate.bind(this));
1300             return;
1301         }
1302
1303         // No call frame available. Use the main page's context.
1304         RuntimeAgent.evaluate.invoke({expression: candidate.expression, objectGroup: "popover", doNotPauseOnExceptionsAndMuteConsole: true}, populate.bind(this));
1305     },
1306
1307     _tokenTrackingControllerHighlightedJavaScriptTypeInformation: function(candidate)
1308     {
1309         console.assert(candidate.expression);
1310
1311         var sourceCode = this._sourceCode;
1312         var sourceID = sourceCode.scripts[0].id;
1313         var range = candidate.hoveredTokenRange;
1314         var offset = this.currentPositionToOriginalOffset({line: range.start.line, ch: range.start.ch});
1315
1316         var allRequests = [{
1317             typeInformationDescriptor: WebInspector.ScriptSyntaxTree.TypeProfilerSearchDescriptor.NormalExpression,
1318             sourceID: sourceID,
1319             divot: offset
1320         }];
1321
1322         function handler(error, allTypes) {
1323             if (error)
1324                 return;
1325
1326             if (candidate !== this.tokenTrackingController.candidate)
1327                 return;
1328
1329             console.assert(allTypes.length === 1);
1330             if (!allTypes.length)
1331                 return;
1332             var types = allTypes[0];
1333             if (types.isValid) {
1334                 var popoverTitle = WebInspector.TypeTokenView.titleForPopover(WebInspector.TypeTokenView.TitleType.Variable, candidate.expression);
1335                 this.showPopoverForTypes(types, null, popoverTitle);
1336             }
1337         }
1338
1339         RuntimeAgent.getRuntimeTypesForVariablesAtOffsets(allRequests, handler.bind(this));
1340     },
1341
1342     _showPopover: function(content, bounds)
1343     {
1344         console.assert(this.tokenTrackingController.candidate || bounds);
1345
1346         var shouldHighlightRange = false;
1347         var candidate = this.tokenTrackingController.candidate;
1348         // If bounds is falsey, this is a popover introduced from a hover event.
1349         // Otherwise, this is called from TypeTokenAnnotator.
1350         if (!bounds) {
1351             if (!candidate)
1352                 return;
1353
1354             var rects = this.rectsForRange(candidate.hoveredTokenRange);
1355             bounds = WebInspector.Rect.unionOfRects(rects);
1356
1357             shouldHighlightRange = true;
1358         }
1359
1360         content.classList.add(WebInspector.SourceCodeTextEditor.PopoverDebuggerContentStyleClassName);
1361
1362         this._popover = this._popover || new WebInspector.Popover(this);
1363         this._popover.presentNewContentWithFrame(content, bounds.pad(5), [WebInspector.RectEdge.MIN_Y, WebInspector.RectEdge.MAX_Y, WebInspector.RectEdge.MAX_X]);
1364         if (shouldHighlightRange)
1365             this.tokenTrackingController.highlightRange(candidate.expressionRange);
1366
1367         this._trackPopoverEvents();
1368     },
1369
1370     _showPopoverForFunction: function(data)
1371     {
1372         var candidate = this.tokenTrackingController.candidate;
1373
1374         function didGetDetails(error, response)
1375         {
1376             if (error) {
1377                 console.error(error);
1378                 this._dismissPopover();
1379                 return;
1380             }
1381
1382             // Nothing to do if the token has changed since the time we
1383             // asked for the function details from the backend.
1384             if (candidate !== this.tokenTrackingController.candidate)
1385                 return;
1386
1387             var wrapper = document.createElement("div");
1388             wrapper.className = "body console-formatted-function";
1389             wrapper.textContent = data.description;
1390
1391             var content = document.createElement("div");
1392             content.className = "function";
1393
1394             var title = content.appendChild(document.createElement("div"));
1395             title.className = "title";
1396             title.textContent = response.name || response.inferredName || response.displayName || WebInspector.UIString("(anonymous function)");
1397
1398             content.appendChild(wrapper);
1399
1400             this._showPopover(content);
1401         }
1402         DebuggerAgent.getFunctionDetails(data.objectId, didGetDetails.bind(this));
1403     },
1404
1405     _showPopoverForObject: function(data)
1406     {
1407         if (data.subtype === "null") {
1408             this._showPopoverForNull(data);
1409             return;
1410         }
1411
1412         var content = document.createElement("div");
1413         content.className = "object expandable";
1414
1415         var titleElement = document.createElement("div");
1416         titleElement.className = "title";
1417         titleElement.textContent = data.description;
1418         content.appendChild(titleElement);
1419
1420         var section = new WebInspector.ObjectPropertiesSection(data);
1421         section.expanded = true;
1422         section.element.classList.add("body");
1423         content.appendChild(section.element);
1424
1425         this._showPopover(content);
1426     },
1427
1428     _showPopoverForString: function(data)
1429     {
1430         var content = document.createElement("div");
1431         content.className = "string console-formatted-string";
1432         content.textContent = "\"" + data.description + "\"";
1433
1434         this._showPopover(content);
1435     },
1436
1437     _showPopoverForRegExp: function(data)
1438     {
1439         var content = document.createElement("div");
1440         content.className = "regexp console-formatted-regexp";
1441         content.textContent = data.description;
1442
1443         this._showPopover(content);
1444     },
1445
1446     _showPopoverForNumber: function(data)
1447     {
1448         var content = document.createElement("span");
1449         content.className = "number console-formatted-number";
1450         content.textContent = data.description;
1451
1452         this._showPopover(content);
1453     },
1454
1455     _showPopoverForBoolean: function(data)
1456     {
1457         var content = document.createElement("span");
1458         content.className = "boolean console-formatted-boolean";
1459         content.textContent = data.description;
1460
1461         this._showPopover(content);
1462     },
1463
1464     _showPopoverForNull: function(data)
1465     {
1466         var content = document.createElement("span");
1467         content.className = "boolean console-formatted-null";
1468         content.textContent = data.description;
1469
1470         this._showPopover(content);
1471     },
1472
1473     _showPopoverForUndefined: function(data)
1474     {
1475         var content = document.createElement("span");
1476         content.className = "boolean console-formatted-undefined";
1477         content.textContent = data.description;
1478
1479         this._showPopover(content);
1480     },
1481
1482     willDismissPopover: function(popover)
1483     {
1484         this.tokenTrackingController.removeHighlightedRange();
1485
1486         RuntimeAgent.releaseObjectGroup("popover");
1487     },
1488
1489     _dismissPopover: function()
1490     {
1491         if (!this._popover)
1492             return;
1493
1494         this._popover.dismiss();
1495
1496         if (this._popoverEventListeners && this._popoverEventListenersAreRegistered) {
1497             this._popoverEventListenersAreRegistered = false;
1498             this._popoverEventListeners.unregister();
1499         }
1500     },
1501
1502     _trackPopoverEvents: function()
1503     {
1504         if (!this._popoverEventListeners) 
1505             this._popoverEventListeners = new WebInspector.EventListenerSet(this, "Popover listeners");
1506         if (!this._popoverEventListenersAreRegistered) {
1507             this._popoverEventListenersAreRegistered = true;
1508             this._popoverEventListeners.register(this._popover.element, "mouseover", this._popoverMouseover);
1509             this._popoverEventListeners.register(this._popover.element, "mouseout", this._popoverMouseout);
1510             this._popoverEventListeners.install();
1511         }
1512     },
1513
1514     _popoverMouseover: function(event)
1515     {
1516         this._mouseIsOverPopover = true;
1517     },
1518
1519     _popoverMouseout: function(event)
1520     {
1521         this._mouseIsOverPopover = this._popover.element.contains(event.relatedTarget);
1522     },
1523
1524     _updateEditableMarkers: function(range)
1525     {
1526         this.createColorMarkers(range);
1527         this.createGradientMarkers(range);
1528
1529         this._updateTokenTrackingControllerState();
1530     },
1531
1532     _tokenTrackingControllerHighlightedMarkedExpression: function(candidate, markers)
1533     {
1534         // Look for the outermost editable marker.
1535         var editableMarker;
1536         for (var marker of markers) {
1537             if (!marker.range || (marker.type !== WebInspector.TextMarker.Type.Color && marker.type !== WebInspector.TextMarker.Type.Gradient))
1538                 continue;
1539
1540             if (!editableMarker || (marker.range.startLine < editableMarker.range.startLine || (marker.range.startLine === editableMarker.range.startLine && marker.range.startColumn < editableMarker.range.startColumn)))
1541                 editableMarker = marker;
1542         }
1543
1544         if (!editableMarker) {
1545             this.tokenTrackingController.hoveredMarker = null;
1546             return;
1547         }
1548
1549         if (this.tokenTrackingController.hoveredMarker === editableMarker)
1550             return;
1551
1552         this._dismissEditingController();
1553
1554         this.tokenTrackingController.hoveredMarker = editableMarker;
1555
1556         this._editingController = this.editingControllerForMarker(editableMarker);
1557
1558         if (marker.type === WebInspector.TextMarker.Type.Color) {
1559             var color = this._editingController.value;
1560             if (!color || !color.valid) {
1561                 editableMarker.clear();
1562                 delete this._editingController;
1563                 return;
1564             }
1565         }
1566
1567         this._editingController.delegate = this;
1568         this._editingController.presentHoverMenu();
1569     },
1570
1571     _dismissEditingController: function(discrete)
1572     {
1573         if (this._editingController)
1574             this._editingController.dismissHoverMenu(discrete);
1575         
1576         this.tokenTrackingController.hoveredMarker = null;
1577         delete this._editingController;
1578     },
1579
1580     // CodeMirrorEditingController Delegate
1581     
1582     editingControllerDidStartEditing: function(editingController)
1583     {
1584         // We can pause the token tracking controller during editing, it will be reset
1585         // to the expected state by calling _updateEditableMarkers() in the
1586         // editingControllerDidFinishEditing delegate.
1587         this.tokenTrackingController.enabled = false;
1588
1589         // We clear the marker since we'll reset it after editing.
1590         editingController.marker.clear();
1591         
1592         // We ignore content changes made as a result of color editing.
1593         this._ignoreContentDidChange++;
1594     },
1595     
1596     editingControllerDidFinishEditing: function(editingController)
1597     {
1598         this._updateEditableMarkers(editingController.range);
1599
1600         this._ignoreContentDidChange--;
1601
1602         delete this._editingController;
1603     },
1604
1605     _getAssociatedScript: function()
1606     {
1607         var script = null;
1608         // FIXME: This needs to me modified to work with HTML files with inline script tags.
1609         if (this._sourceCode instanceof WebInspector.Script)
1610             script = this._sourceCode;
1611         else if (this._sourceCode instanceof WebInspector.Resource && this._sourceCode.type === WebInspector.Resource.Type.Script && this._sourceCode.scripts.length)
1612             script = this._sourceCode.scripts[0];
1613         return script;
1614     },
1615
1616     _makeTypeTokenAnnotator: function()
1617     {
1618         if (!RuntimeAgent.getRuntimeTypesForVariablesAtOffsets)
1619             return;
1620
1621         var script = this._getAssociatedScript();
1622         if (!script)
1623             return;
1624
1625         this._typeTokenAnnotator = new WebInspector.TypeTokenAnnotator(this, script);
1626     },
1627
1628     _makeBasicBlockAnnotator: function()
1629     {
1630         if (!RuntimeAgent.getBasicBlocks)
1631             return;
1632
1633         var script = this._getAssociatedScript();
1634         if (!script)
1635             return;
1636
1637         this._basicBlockAnnotator = new WebInspector.BasicBlockAnnotator(this, script);
1638     },
1639
1640     _enableScrollEventsForTypeTokenAnnotator: function()
1641     {
1642         // Pause updating type tokens while scrolling to prevent frame loss.
1643         console.assert(!this._typeTokenScrollHandler);
1644         this._typeTokenScrollHandler = this._makeTypeTokenScrollEventHandler();
1645         this.addScrollHandler(this._typeTokenScrollHandler);
1646     },
1647
1648     _disableScrollEventsForTypeTokenAnnotator: function()
1649     {
1650         console.assert(this._typeTokenScrollHandler);
1651         this.removeScrollHandler(this._typeTokenScrollHandler);
1652         this._typeTokenScrollHandler = null;
1653     },
1654
1655     _makeTypeTokenScrollEventHandler: function()
1656     {
1657         var timeoutIdentifier = null;
1658         function scrollHandler()
1659         {
1660             if (timeoutIdentifier)
1661                 clearTimeout(timeoutIdentifier);
1662             else {
1663                 if (this._typeTokenAnnotator)
1664                     this._typeTokenAnnotator.pause();
1665                 if (this._basicBlockAnnotator)
1666                     this._basicBlockAnnotator.pause();
1667             }
1668
1669             timeoutIdentifier = setTimeout(function() {
1670                 timeoutIdentifier = null;
1671                 if (this._typeTokenAnnotator)
1672                     this._typeTokenAnnotator.resume();
1673                 if (!this._hasFocus && this._basicBlockAnnotator)
1674                     this._basicBlockAnnotator.resume();
1675             }.bind(this), WebInspector.SourceCodeTextEditor.DurationToUpdateTypeTokensAfterScrolling);
1676         }
1677
1678         return scrollHandler.bind(this);
1679     }
1680 };
1681
1682 WebInspector.SourceCodeTextEditor.prototype.__proto__ = WebInspector.TextEditor.prototype;