2011-02-25 Andrey Kosyakov <caseq@chromium.org>
[WebKit-https.git] / Source / WebCore / inspector / front-end / ScriptsPanel.js
1 /*
2  * Copyright (C) 2008 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. ``AS IS'' AND ANY
14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
17  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 WebInspector.ScriptsPanel = function()
27 {
28     WebInspector.Panel.call(this, "scripts");
29
30     this._presentationModel = new WebInspector.DebuggerPresentationModel();
31
32     this.topStatusBar = document.createElement("div");
33     this.topStatusBar.className = "status-bar";
34     this.topStatusBar.id = "scripts-status-bar";
35     this.element.appendChild(this.topStatusBar);
36
37     this.backButton = document.createElement("button");
38     this.backButton.className = "status-bar-item";
39     this.backButton.id = "scripts-back";
40     this.backButton.title = WebInspector.UIString("Show the previous script resource.");
41     this.backButton.disabled = true;
42     this.backButton.appendChild(document.createElement("img"));
43     this.backButton.addEventListener("click", this._goBack.bind(this), false);
44     this.topStatusBar.appendChild(this.backButton);
45
46     this.forwardButton = document.createElement("button");
47     this.forwardButton.className = "status-bar-item";
48     this.forwardButton.id = "scripts-forward";
49     this.forwardButton.title = WebInspector.UIString("Show the next script resource.");
50     this.forwardButton.disabled = true;
51     this.forwardButton.appendChild(document.createElement("img"));
52     this.forwardButton.addEventListener("click", this._goForward.bind(this), false);
53     this.topStatusBar.appendChild(this.forwardButton);
54
55     this._filesSelectElement = document.createElement("select");
56     this._filesSelectElement.className = "status-bar-item";
57     this._filesSelectElement.id = "scripts-files";
58     this._filesSelectElement.addEventListener("change", this._filesSelectChanged.bind(this), false);
59     this.topStatusBar.appendChild(this._filesSelectElement);
60
61     this.functionsSelectElement = document.createElement("select");
62     this.functionsSelectElement.className = "status-bar-item";
63     this.functionsSelectElement.id = "scripts-functions";
64
65     // FIXME: append the functions select element to the top status bar when it is implemented.
66     // this.topStatusBar.appendChild(this.functionsSelectElement);
67
68     this.formatButton = document.createElement("button");
69     this.formatButton.className = "status-bar-item";
70     this.formatButton.id = "format-script";
71     this.formatButton.title = WebInspector.UIString("Format script.");
72     this.formatButton.appendChild(document.createElement("img"));
73     this.formatButton.addEventListener("click", this._formatScript.bind(this), false);
74     if (Preferences.debugMode)
75         this.topStatusBar.appendChild(this.formatButton);
76
77     this.sidebarButtonsElement = document.createElement("div");
78     this.sidebarButtonsElement.id = "scripts-sidebar-buttons";
79     this.topStatusBar.appendChild(this.sidebarButtonsElement);
80
81     this.pauseButton = document.createElement("button");
82     this.pauseButton.className = "status-bar-item";
83     this.pauseButton.id = "scripts-pause";
84     this.pauseButton.title = WebInspector.UIString("Pause script execution.");
85     this.pauseButton.disabled = true;
86     this.pauseButton.appendChild(document.createElement("img"));
87     this.pauseButton.addEventListener("click", this._togglePause.bind(this), false);
88     this.sidebarButtonsElement.appendChild(this.pauseButton);
89
90     this.stepOverButton = document.createElement("button");
91     this.stepOverButton.className = "status-bar-item";
92     this.stepOverButton.id = "scripts-step-over";
93     this.stepOverButton.title = WebInspector.UIString("Step over next function call.");
94     this.stepOverButton.disabled = true;
95     this.stepOverButton.addEventListener("click", this._stepOverClicked.bind(this), false);
96     this.stepOverButton.appendChild(document.createElement("img"));
97     this.sidebarButtonsElement.appendChild(this.stepOverButton);
98
99     this.stepIntoButton = document.createElement("button");
100     this.stepIntoButton.className = "status-bar-item";
101     this.stepIntoButton.id = "scripts-step-into";
102     this.stepIntoButton.title = WebInspector.UIString("Step into next function call.");
103     this.stepIntoButton.disabled = true;
104     this.stepIntoButton.addEventListener("click", this._stepIntoClicked.bind(this), false);
105     this.stepIntoButton.appendChild(document.createElement("img"));
106     this.sidebarButtonsElement.appendChild(this.stepIntoButton);
107
108     this.stepOutButton = document.createElement("button");
109     this.stepOutButton.className = "status-bar-item";
110     this.stepOutButton.id = "scripts-step-out";
111     this.stepOutButton.title = WebInspector.UIString("Step out of current function.");
112     this.stepOutButton.disabled = true;
113     this.stepOutButton.addEventListener("click", this._stepOutClicked.bind(this), false);
114     this.stepOutButton.appendChild(document.createElement("img"));
115     this.sidebarButtonsElement.appendChild(this.stepOutButton);
116
117     this.toggleBreakpointsButton = new WebInspector.StatusBarButton(WebInspector.UIString("Deactivate all breakpoints."), "toggle-breakpoints");
118     this.toggleBreakpointsButton.toggled = true;
119     this.toggleBreakpointsButton.addEventListener("click", this.toggleBreakpointsClicked.bind(this), false);
120     this.sidebarButtonsElement.appendChild(this.toggleBreakpointsButton.element);
121
122     this.debuggerStatusElement = document.createElement("div");
123     this.debuggerStatusElement.id = "scripts-debugger-status";
124     this.sidebarButtonsElement.appendChild(this.debuggerStatusElement);
125
126     this.viewsContainerElement = document.createElement("div");
127     this.viewsContainerElement.id = "script-resource-views";
128
129     this.sidebarElement = document.createElement("div");
130     this.sidebarElement.id = "scripts-sidebar";
131
132     this.sidebarResizeElement = document.createElement("div");
133     this.sidebarResizeElement.className = "sidebar-resizer-vertical";
134     this.sidebarResizeElement.addEventListener("mousedown", this._startSidebarResizeDrag.bind(this), false);
135
136     this.sidebarResizeWidgetElement = document.createElement("div");
137     this.sidebarResizeWidgetElement.id = "scripts-sidebar-resizer-widget";
138     this.sidebarResizeWidgetElement.addEventListener("mousedown", this._startSidebarResizeDrag.bind(this), false);
139     this.topStatusBar.appendChild(this.sidebarResizeWidgetElement);
140
141     this.sidebarPanes = {};
142     this.sidebarPanes.watchExpressions = new WebInspector.WatchExpressionsSidebarPane();
143     this.sidebarPanes.callstack = new WebInspector.CallStackSidebarPane();
144     this.sidebarPanes.scopechain = new WebInspector.ScopeChainSidebarPane();
145     this.sidebarPanes.jsBreakpoints = new WebInspector.JavaScriptBreakpointsSidebarPane();
146     if (Preferences.nativeInstrumentationEnabled) {
147         this.sidebarPanes.domBreakpoints = WebInspector.createDOMBreakpointsSidebarPane();
148         this.sidebarPanes.xhrBreakpoints = WebInspector.createXHRBreakpointsSidebarPane();
149         this.sidebarPanes.eventListenerBreakpoints = new WebInspector.EventListenerBreakpointsSidebarPane();
150     }
151
152     this.sidebarPanes.workers = new WebInspector.WorkersSidebarPane();
153
154     for (var pane in this.sidebarPanes)
155         this.sidebarElement.appendChild(this.sidebarPanes[pane].element);
156
157     this.sidebarPanes.callstack.expanded = true;
158     this.sidebarPanes.callstack.addEventListener("call frame selected", this._callFrameSelected, this);
159
160     this.sidebarPanes.scopechain.expanded = true;
161     this.sidebarPanes.jsBreakpoints.expanded = true;
162
163     var panelEnablerHeading = WebInspector.UIString("You need to enable debugging before you can use the Scripts panel.");
164     var panelEnablerDisclaimer = WebInspector.UIString("Enabling debugging will make scripts run slower.");
165     var panelEnablerButton = WebInspector.UIString("Enable Debugging");
166
167     this.panelEnablerView = new WebInspector.PanelEnablerView("scripts", panelEnablerHeading, panelEnablerDisclaimer, panelEnablerButton);
168     this.panelEnablerView.addEventListener("enable clicked", this._enableDebugging, this);
169
170     this.element.appendChild(this.panelEnablerView.element);
171     this.element.appendChild(this.viewsContainerElement);
172     this.element.appendChild(this.sidebarElement);
173     this.element.appendChild(this.sidebarResizeElement);
174
175     this.enableToggleButton = new WebInspector.StatusBarButton("", "enable-toggle-status-bar-item");
176     this.enableToggleButton.addEventListener("click", this._toggleDebugging.bind(this), false);
177     if (Preferences.debuggerAlwaysEnabled)
178         this.enableToggleButton.element.addStyleClass("hidden");
179
180     this._pauseOnExceptionButton = new WebInspector.StatusBarButton("", "scripts-pause-on-exceptions-status-bar-item", 3);
181     this._pauseOnExceptionButton.addEventListener("click", this._togglePauseOnExceptions.bind(this), false);
182
183     this._registerShortcuts();
184
185     this._debuggerEnabled = Preferences.debuggerAlwaysEnabled;
186
187     this.reset();
188
189     WebInspector.debuggerModel.addEventListener(WebInspector.DebuggerModel.Events.ParsedScriptSource, this._parsedScriptSource, this);
190     WebInspector.debuggerModel.addEventListener(WebInspector.DebuggerModel.Events.FailedToParseScriptSource, this._failedToParseScriptSource, this);
191     WebInspector.debuggerModel.addEventListener(WebInspector.DebuggerModel.Events.ScriptSourceChanged, this._scriptSourceChanged, this);
192     WebInspector.debuggerModel.addEventListener(WebInspector.DebuggerModel.Events.DebuggerPaused, this._debuggerPaused, this);
193     WebInspector.debuggerModel.addEventListener(WebInspector.DebuggerModel.Events.DebuggerResumed, this._debuggerResumed, this);
194     this._presentationModel.addEventListener(WebInspector.DebuggerPresentationModel.Events.BreakpointAdded, this._breakpointAdded, this);
195     this._presentationModel.addEventListener(WebInspector.DebuggerPresentationModel.Events.BreakpointRemoved, this._breakpointRemoved, this);
196 }
197
198 // Keep these in sync with WebCore::ScriptDebugServer
199 WebInspector.ScriptsPanel.PauseOnExceptionsState = {
200     DontPauseOnExceptions : 0,
201     PauseOnAllExceptions : 1,
202     PauseOnUncaughtExceptions: 2
203 };
204
205 WebInspector.ScriptsPanel.prototype = {
206     get toolbarItemLabel()
207     {
208         return WebInspector.UIString("Scripts");
209     },
210
211     get statusBarItems()
212     {
213         return [this.enableToggleButton.element, this._pauseOnExceptionButton.element];
214     },
215
216     get defaultFocusedElement()
217     {
218         return this._filesSelectElement;
219     },
220
221     get paused()
222     {
223         return this._paused;
224     },
225
226     show: function()
227     {
228         WebInspector.Panel.prototype.show.call(this);
229         this.sidebarResizeElement.style.right = (this.sidebarElement.offsetWidth - 3) + "px";
230
231         if (this.visibleView)
232             this.visibleView.show(this.viewsContainerElement);
233     },
234
235     hide: function()
236     {
237         if (this.visibleView)
238             this.visibleView.hide();
239         WebInspector.Panel.prototype.hide.call(this);
240     },
241
242     get breakpointsActivated()
243     {
244         return this.toggleBreakpointsButton.toggled;
245     },
246
247     _parsedScriptSource: function(event)
248     {
249         this._addScript(event.data);
250     },
251
252     _failedToParseScriptSource: function(event)
253     {
254         this._addScript(event.data);
255     },
256
257     _scriptSourceChanged: function(event)
258     {
259         var sourceID = event.data.sourceID;
260         var oldSource = event.data.oldSource;
261
262         var script = WebInspector.debuggerModel.scriptForSourceID(sourceID);
263         if (script.resource) {
264             var revertHandle = WebInspector.debuggerModel.editScriptSource.bind(WebInspector.debuggerModel, sourceID, oldSource);
265             script.resource.setContent(script.source, revertHandle);
266         }
267
268         var sourceName = this._sourceNameForScript(script);
269         this._recreateSourceFrame(sourceName);
270
271         var callFrames = WebInspector.debuggerModel.callFrames;
272         if (callFrames.length)
273             this._debuggerPaused({ data: { callFrames: callFrames } });
274     },
275
276     _addScript: function(script)
277     {
278         if (!script.sourceURL) {
279             // Anonymous scripts are shown only when stepping.
280             return;
281         }
282
283         var resource = this._resourceForURL(script.sourceURL);
284         if (resource) {
285             if (resource.finished) {
286                 // Resource is finished, bind the script right away.
287                 script.resource = resource;
288
289                 // Add resource url to files select if not already added while debugging inlined scripts.
290                 if (!(resource.url in this._sourceNameToFilesSelectOption))
291                     this._addOptionToFilesSelectAndShowSourceFrameIfNeeded(resource.url);
292             } else {
293                 // Resource is not finished, bind the script later.
294                 if (!resource._scriptsPendingResourceLoad) {
295                     resource._scriptsPendingResourceLoad = [];
296                     resource.addEventListener("finished", this._resourceLoadingFinished, this);
297                 }
298                 resource._scriptsPendingResourceLoad.push(script);
299
300                 // Source frame content is outdated since we have new script parsed.
301                 this._recreateSourceFrame(script.sourceURL);
302             }
303         } else if (!(script.sourceURL in this._sourceNameToFilesSelectOption)) {
304             // This is a dynamic script with "//@ sourceURL=" comment.
305             this._addOptionToFilesSelectAndShowSourceFrameIfNeeded(script.sourceURL);
306         }
307     },
308
309     _resourceForURL: function(url)
310     {
311         return WebInspector.networkManager.inflightResourceForURL(url) || WebInspector.resourceForURL(url);
312     },
313
314     _resourceLoadingFinished: function(e)
315     {
316         var resource = e.target;
317
318         // Bind scripts to resource.
319         for (var i = 0; i < resource._scriptsPendingResourceLoad.length; ++i) {
320             var script = resource._scriptsPendingResourceLoad[i];
321             script.resource = resource;
322         }
323         delete resource._scriptsPendingResourceLoad;
324
325         // Recreate source frame to show resource content.
326         this._recreateSourceFrame(resource.url);
327
328         // Add resource url to files select if not already added while debugging inlined scripts.
329         if (!(resource.url in this._sourceNameToFilesSelectOption))
330             this._addOptionToFilesSelectAndShowSourceFrameIfNeeded(resource.url);
331     },
332
333     _addOptionToFilesSelectAndShowSourceFrameIfNeeded: function(url)
334     {
335         this._addOptionToFilesSelect(url);
336
337         var lastViewedURL = WebInspector.settings.lastViewedScriptFile;
338         if (this._filesSelectElement.length === 1) {
339             // Option we just added is the only option in files select.
340             // We have to show corresponding source frame immediately.
341             this._showSourceFrameAndAddToHistory(url);
342             // Restore original value of lastViewedScriptFile because
343             // source frame was shown as a result of initial load.
344             WebInspector.settings.lastViewedScriptFile = lastViewedURL;
345         } else if (url === lastViewedURL)
346             this._showSourceFrameAndAddToHistory(url);
347     },
348
349     _addOptionToFilesSelect: function(sourceName)
350     {
351         var script = this._scriptForSourceName(sourceName);
352         var select = this._filesSelectElement;
353         var option = document.createElement("option");
354         option.text = script.sourceURL ? WebInspector.displayNameForURL(script.sourceURL) : WebInspector.UIString("(program)");
355         if (script.worldType === WebInspector.Script.WorldType.EXTENSIONS_WORLD)
356             option.addStyleClass("extension-script");
357         function optionCompare(a, b)
358         {
359             if (a.text === b.text)
360                 return 0;
361             return a.text < b.text ? -1 : 1;
362         }
363         var insertionIndex = insertionIndexForObjectInListSortedByFunction(option, select.childNodes, optionCompare);
364         if (insertionIndex < 0)
365             select.appendChild(option);
366         else
367             select.insertBefore(option, select.childNodes.item(insertionIndex));
368
369         option._sourceName = sourceName;
370         this._sourceNameToFilesSelectOption[sourceName] = option;
371     },
372
373     addConsoleMessage: function(message)
374     {
375         this._messages.push(message);
376         var sourceFrame = this._sourceNameToSourceFrame[message.url];
377         if (sourceFrame)
378             sourceFrame.addMessage(message);
379     },
380
381     clearConsoleMessages: function()
382     {
383         this._messages = [];
384         for (var url in this._sourceNameToSourceFrame)
385             this._sourceNameToSourceFrame[url].clearMessages();
386     },
387
388     _breakpointAdded: function(event)
389     {
390         var breakpoint = event.data;
391
392         var sourceFrame = this._sourceNameToSourceFrame[breakpoint.sourceName];
393         if (sourceFrame && sourceFrame.loaded)
394             sourceFrame.addBreakpoint(breakpoint.lineNumber, breakpoint.resolved, breakpoint.condition, breakpoint.enabled);
395     },
396
397     _breakpointRemoved: function(event)
398     {
399         var breakpoint = event.data;
400
401         var sourceFrame = this._sourceNameToSourceFrame[breakpoint.sourceName];
402         if (sourceFrame && sourceFrame.loaded)
403             sourceFrame.removeBreakpoint(breakpoint.lineNumber);
404     },
405
406     selectedCallFrameId: function()
407     {
408         var selectedCallFrame = this.sidebarPanes.callstack.selectedCallFrame;
409         if (!selectedCallFrame)
410             return null;
411         return selectedCallFrame.id;
412     },
413
414     evaluateInSelectedCallFrame: function(code, updateInterface, objectGroup, includeCommandLineAPI, callback)
415     {
416         var selectedCallFrame = this.sidebarPanes.callstack.selectedCallFrame;
417         if (!this._paused || !selectedCallFrame)
418             return;
419
420         if (typeof updateInterface === "undefined")
421             updateInterface = true;
422
423         function updatingCallbackWrapper(result)
424         {
425             if (result) {
426                 callback(WebInspector.RemoteObject.fromPayload(result));
427                 if (updateInterface)
428                     this.sidebarPanes.scopechain.update(selectedCallFrame);
429             }
430         }
431         DebuggerAgent.evaluateOnCallFrame(selectedCallFrame.id, code, objectGroup, includeCommandLineAPI, updatingCallbackWrapper.bind(this));
432     },
433
434     _debuggerPaused: function(event)
435     {
436         var callFrames = event.data.callFrames;
437
438         this._paused = true;
439         this._waitingToPause = false;
440         this._stepping = false;
441
442         this._updateDebuggerButtons();
443
444         WebInspector.currentPanel = this;
445
446         this.sidebarPanes.callstack.update(event.data);
447         this.sidebarPanes.callstack.selectedCallFrame = callFrames[0];
448
449         window.focus();
450         InspectorFrontendHost.bringToFront();
451     },
452
453     _debuggerResumed: function()
454     {
455         this._paused = false;
456         this._waitingToPause = false;
457         this._stepping = false;
458
459         this._clearInterface();
460     },
461
462     debuggerWasEnabled: function()
463     {
464         this._setPauseOnExceptions(WebInspector.settings.pauseOnExceptionState);
465
466         if (this._debuggerEnabled)
467             return;
468         this._debuggerEnabled = true;
469         this.reset(true);
470     },
471
472     debuggerWasDisabled: function()
473     {
474         if (!this._debuggerEnabled)
475             return;
476
477         this._debuggerEnabled = false;
478         this.reset(true);
479     },
480
481     reset: function(preserveItems)
482     {
483         this.visibleView = null;
484
485         delete this.currentQuery;
486         this.searchCanceled();
487
488         this._debuggerResumed();
489
490         this._backForwardList = [];
491         this._currentBackForwardIndex = -1;
492         this._updateBackAndForwardButtons();
493
494         this._sourceNameToSourceFrame = {};
495         this._sourceNameToFilesSelectOption = {};
496         this._messages = [];
497         this._filesSelectElement.removeChildren();
498         this.functionsSelectElement.removeChildren();
499         this.viewsContainerElement.removeChildren();
500
501         this.sidebarPanes.watchExpressions.refreshExpressions();
502         if (!preserveItems)
503             this.sidebarPanes.workers.reset();
504     },
505
506     get visibleView()
507     {
508         return this._visibleView;
509     },
510
511     set visibleView(x)
512     {
513         if (this._visibleView === x)
514             return;
515
516         if (this._visibleView)
517             this._visibleView.hide();
518
519         this._visibleView = x;
520
521         if (x)
522             x.show(this.viewsContainerElement);
523     },
524
525     canShowSourceLine: function(url, line)
526     {
527         return this._debuggerEnabled && (url in this._sourceNameToFilesSelectOption);
528     },
529
530     showSourceLine: function(url, line)
531     {
532         if (!(url in this._sourceNameToFilesSelectOption))
533             return;
534         var sourceFrame = this._showSourceFrameAndAddToHistory(url);
535         sourceFrame.highlightLine(line);
536     },
537
538     handleShortcut: function(event)
539     {
540         var shortcut = WebInspector.KeyboardShortcut.makeKeyFromEvent(event);
541         var handler = this._shortcuts[shortcut];
542         if (handler) {
543             handler(event);
544             event.handled = true;
545         } else
546             this.sidebarPanes.callstack.handleShortcut(event);
547     },
548
549     _showSourceFrameAndAddToHistory: function(sourceName)
550     {
551         var sourceFrame = this._showSourceFrame(sourceName);
552
553         var oldIndex = this._currentBackForwardIndex;
554         if (oldIndex >= 0)
555             this._backForwardList.splice(oldIndex + 1, this._backForwardList.length - oldIndex);
556
557         // Check for a previous entry of the same object in _backForwardList.
558         // If one is found, remove it.
559         var previousEntryIndex = this._backForwardList.indexOf(sourceName);
560         if (previousEntryIndex !== -1)
561             this._backForwardList.splice(previousEntryIndex, 1);
562
563         this._backForwardList.push(sourceName);
564         this._currentBackForwardIndex = this._backForwardList.length - 1;
565
566         this._updateBackAndForwardButtons();
567
568         return sourceFrame;
569     },
570
571     _showSourceFrame: function(sourceName)
572     {
573         var index = this._sourceNameToFilesSelectOption[sourceName].index;
574         this._filesSelectElement.selectedIndex = index;
575
576         var sourceFrame = this._sourceFrameForSourceName(sourceName);
577         this.visibleView = sourceFrame;
578
579         var script = this._scriptForSourceName(sourceName);
580         if (script.sourceURL)
581             WebInspector.settings.lastViewedScriptFile = script.sourceURL;
582
583         return sourceFrame;
584     },
585
586     _sourceFrameForSourceName: function(sourceName)
587     {
588         var sourceFrame = this._sourceNameToSourceFrame[sourceName];
589         return sourceFrame || this._createSourceFrame(sourceName);
590     },
591
592     _createSourceFrame: function(sourceName)
593     {
594         var script = this._scriptForSourceName(sourceName);
595         var contentProvider;
596         var isScript;
597         if (script.resource) {
598             contentProvider = new WebInspector.SourceFrameContentProviderForResource(script.resource);
599             isScript = script.resource.type === WebInspector.Resource.Type.Script;
600         } else {
601             contentProvider = new WebInspector.SourceFrameContentProviderForScript(script);
602             isScript = !script.lineOffset && !script.columnOffset;
603         }
604         sourceFrame = new WebInspector.SourceFrame(contentProvider, script.sourceURL, isScript);
605         sourceFrame._sourceName = sourceName;
606         sourceFrame.addEventListener(WebInspector.SourceFrame.Events.Loaded, this._sourceFrameLoaded, this);
607         this._sourceNameToSourceFrame[sourceName] = sourceFrame;
608         return sourceFrame;
609     },
610
611     _recreateSourceFrame: function(sourceName)
612     {
613         var oldSourceFrame = this._sourceNameToSourceFrame[sourceName];
614         if (!oldSourceFrame)
615             return;
616         oldSourceFrame.removeEventListener(WebInspector.SourceFrame.Events.Loaded, this._sourceFrameLoaded, this);
617         delete this._sourceNameToSourceFrame[sourceName];
618         oldSourceFrame.removeEventListener(WebInspector.SourceFrame.Events.Loaded, this._sourceFrameLoaded, this);
619         if (this.visibleView !== oldSourceFrame)
620             return;
621
622         var newSourceFrame = this._createSourceFrame(sourceName)
623         newSourceFrame.scrollTop = oldSourceFrame.scrollTop;
624         this.visibleView = newSourceFrame;
625     },
626
627     _sourceFrameLoaded: function(event)
628     {
629         var sourceFrame = event.target;
630         var sourceName = sourceFrame._sourceName;
631
632         for (var i = 0; i < this._messages.length; ++i) {
633             var message = this._messages[i];
634             if (message.url === sourceName)
635                 sourceFrame.addMessage(message);
636         }
637
638         var breakpoints = this._presentationModel.breakpointsForSourceName(sourceName);
639         for (var i = 0; i < breakpoints.length; ++i) {
640             var breakpoint = breakpoints[i];
641             sourceFrame.addBreakpoint(breakpoint.lineNumber, breakpoint.resolved, breakpoint.condition, breakpoint.enabled);
642         }
643
644         var selectedCallFrame = this.sidebarPanes.callstack.selectedCallFrame;
645         if (selectedCallFrame) {
646             var script = WebInspector.debuggerModel.scriptForSourceID(selectedCallFrame.sourceID);
647             if (this._sourceNameForScript(script) === sourceName) {
648                 sourceFrame.setExecutionLine(selectedCallFrame.line);
649                 this._executionSourceFrame = sourceFrame;
650             }
651         }
652     },
653
654     _sourceNameForScript: function(script)
655     {
656         return script.sourceURL || script.sourceID;
657     },
658
659     _scriptForSourceName: function(sourceName)
660     {
661         function filter(script)
662         {
663             return (script.sourceURL || script.sourceID) === sourceName;
664         }
665         return WebInspector.debuggerModel.queryScripts(filter)[0];
666     },
667
668     _clearCurrentExecutionLine: function()
669     {
670         if (this._executionSourceFrame)
671             this._executionSourceFrame.clearExecutionLine();
672         delete this._executionSourceFrame;
673     },
674
675     _callFrameSelected: function()
676     {
677         this._clearCurrentExecutionLine();
678
679         var callStackPane = this.sidebarPanes.callstack;
680         var currentFrame = callStackPane.selectedCallFrame;
681         if (!currentFrame)
682             return;
683
684         this.sidebarPanes.scopechain.update(currentFrame);
685         this.sidebarPanes.watchExpressions.refreshExpressions();
686
687         var script = WebInspector.debuggerModel.scriptForSourceID(currentFrame.sourceID);
688         var sourceName = this._sourceNameForScript(script);
689         if (!(sourceName in this._sourceNameToFilesSelectOption)) {
690             // This happens in two cases:
691             // 1) Current call frame function is defined in anonymous script (anonymous scripts aren't added to files select by default)
692             // 2) We are debugging synchronously executed inlined script and there is no resource so far
693             this._addOptionToFilesSelect(sourceName);
694         }
695         var sourceFrame = this._showSourceFrameAndAddToHistory(sourceName);
696         if (sourceFrame.loaded) {
697             sourceFrame.setExecutionLine(currentFrame.line);
698             this._executionSourceFrame = sourceFrame;
699         }
700     },
701
702     _filesSelectChanged: function()
703     {
704         var sourceName = this._filesSelectElement[this._filesSelectElement.selectedIndex]._sourceName;
705         this._showSourceFrameAndAddToHistory(sourceName);
706     },
707
708     _startSidebarResizeDrag: function(event)
709     {
710         WebInspector.elementDragStart(this.sidebarElement, this._sidebarResizeDrag.bind(this), this._endSidebarResizeDrag.bind(this), event, "col-resize");
711
712         if (event.target === this.sidebarResizeWidgetElement)
713             this._dragOffset = (event.target.offsetWidth - (event.pageX - event.target.totalOffsetLeft));
714         else
715             this._dragOffset = 0;
716     },
717
718     _endSidebarResizeDrag: function(event)
719     {
720         WebInspector.elementDragEnd(event);
721         delete this._dragOffset;
722         this.saveSidebarWidth();
723     },
724
725     _sidebarResizeDrag: function(event)
726     {
727         var x = event.pageX + this._dragOffset;
728         var newWidth = Number.constrain(window.innerWidth - x, Preferences.minScriptsSidebarWidth, window.innerWidth * 0.66);
729         this.setSidebarWidth(newWidth);
730         event.preventDefault();
731     },
732
733     setSidebarWidth: function(newWidth)
734     {
735         this.sidebarElement.style.width = newWidth + "px";
736         this.sidebarButtonsElement.style.width = newWidth + "px";
737         this.viewsContainerElement.style.right = newWidth + "px";
738         this.sidebarResizeWidgetElement.style.right = newWidth + "px";
739         this.sidebarResizeElement.style.right = (newWidth - 3) + "px";
740
741         this.resize();
742     },
743
744     _setPauseOnExceptions: function(pauseOnExceptionsState)
745     {
746         function callback(pauseOnExceptionsState)
747         {
748             if (pauseOnExceptionsState == WebInspector.ScriptsPanel.PauseOnExceptionsState.DontPauseOnExceptions)
749                 this._pauseOnExceptionButton.title = WebInspector.UIString("Don't pause on exceptions.\nClick to Pause on all exceptions.");
750             else if (pauseOnExceptionsState == WebInspector.ScriptsPanel.PauseOnExceptionsState.PauseOnAllExceptions)
751                 this._pauseOnExceptionButton.title = WebInspector.UIString("Pause on all exceptions.\nClick to Pause on uncaught exceptions.");
752             else if (pauseOnExceptionsState == WebInspector.ScriptsPanel.PauseOnExceptionsState.PauseOnUncaughtExceptions)
753                 this._pauseOnExceptionButton.title = WebInspector.UIString("Pause on uncaught exceptions.\nClick to Not pause on exceptions.");
754
755             this._pauseOnExceptionButton.state = pauseOnExceptionsState;
756             WebInspector.settings.pauseOnExceptionState = pauseOnExceptionsState;
757         }
758         DebuggerAgent.setPauseOnExceptionsState(pauseOnExceptionsState, callback.bind(this));
759     },
760
761     _updateDebuggerButtons: function()
762     {
763         if (this._debuggerEnabled) {
764             this.enableToggleButton.title = WebInspector.UIString("Debugging enabled. Click to disable.");
765             this.enableToggleButton.toggled = true;
766             this._pauseOnExceptionButton.visible = true;
767             this.panelEnablerView.visible = false;
768         } else {
769             this.enableToggleButton.title = WebInspector.UIString("Debugging disabled. Click to enable.");
770             this.enableToggleButton.toggled = false;
771             this._pauseOnExceptionButton.visible = false;
772             this.panelEnablerView.visible = true;
773         }
774
775         if (this._paused) {
776             this.pauseButton.addStyleClass("paused");
777
778             this.pauseButton.disabled = false;
779             this.stepOverButton.disabled = false;
780             this.stepIntoButton.disabled = false;
781             this.stepOutButton.disabled = false;
782
783             this.debuggerStatusElement.textContent = WebInspector.UIString("Paused");
784         } else {
785             this.pauseButton.removeStyleClass("paused");
786
787             this.pauseButton.disabled = this._waitingToPause;
788             this.stepOverButton.disabled = true;
789             this.stepIntoButton.disabled = true;
790             this.stepOutButton.disabled = true;
791
792             if (this._waitingToPause)
793                 this.debuggerStatusElement.textContent = WebInspector.UIString("Pausing");
794             else if (this._stepping)
795                 this.debuggerStatusElement.textContent = WebInspector.UIString("Stepping");
796             else
797                 this.debuggerStatusElement.textContent = "";
798         }
799     },
800
801     _updateBackAndForwardButtons: function()
802     {
803         this.backButton.disabled = this._currentBackForwardIndex <= 0;
804         this.forwardButton.disabled = this._currentBackForwardIndex >= (this._backForwardList.length - 1);
805     },
806
807     _clearInterface: function()
808     {
809         this.sidebarPanes.callstack.update(null);
810         this.sidebarPanes.scopechain.update(null);
811
812         this._clearCurrentExecutionLine();
813         this._updateDebuggerButtons();
814     },
815
816     _goBack: function()
817     {
818         if (this._currentBackForwardIndex <= 0) {
819             console.error("Can't go back from index " + this._currentBackForwardIndex);
820             return;
821         }
822
823         this._showSourceFrame(this._backForwardList[--this._currentBackForwardIndex]);
824         this._updateBackAndForwardButtons();
825     },
826
827     _goForward: function()
828     {
829         if (this._currentBackForwardIndex >= this._backForwardList.length - 1) {
830             console.error("Can't go forward from index " + this._currentBackForwardIndex);
831             return;
832         }
833
834         this._showSourceFrame(this._backForwardList[++this._currentBackForwardIndex]);
835         this._updateBackAndForwardButtons();
836     },
837
838     _formatScript: function()
839     {
840         if (this.visibleView)
841             this.visibleView.formatSource();
842     },
843
844     _enableDebugging: function()
845     {
846         if (this._debuggerEnabled)
847             return;
848         this._toggleDebugging(this.panelEnablerView.alwaysEnabled);
849     },
850
851     _toggleDebugging: function(optionalAlways)
852     {
853         this._paused = false;
854         this._waitingToPause = false;
855         this._stepping = false;
856
857         if (this._debuggerEnabled) {
858             WebInspector.settings.debuggerEnabled = false;
859             WebInspector.debuggerModel.disableDebugger();
860         } else {
861             WebInspector.settings.debuggerEnabled = !!optionalAlways;
862             WebInspector.debuggerModel.enableDebugger();
863         }
864     },
865
866     _togglePauseOnExceptions: function()
867     {
868         this._setPauseOnExceptions((this._pauseOnExceptionButton.state + 1) % this._pauseOnExceptionButton.states);
869     },
870
871     _togglePause: function()
872     {
873         if (this._paused) {
874             this._paused = false;
875             this._waitingToPause = false;
876             DebuggerAgent.resume();
877         } else {
878             this._stepping = false;
879             this._waitingToPause = true;
880             DebuggerAgent.pause();
881         }
882
883         this._clearInterface();
884     },
885
886     _stepOverClicked: function()
887     {
888         this._paused = false;
889         this._stepping = true;
890
891         this._clearInterface();
892
893         DebuggerAgent.stepOver();
894     },
895
896     _stepIntoClicked: function()
897     {
898         this._paused = false;
899         this._stepping = true;
900
901         this._clearInterface();
902
903         DebuggerAgent.stepInto();
904     },
905
906     _stepOutClicked: function()
907     {
908         this._paused = false;
909         this._stepping = true;
910
911         this._clearInterface();
912
913         DebuggerAgent.stepOut();
914     },
915
916     toggleBreakpointsClicked: function()
917     {
918         this.toggleBreakpointsButton.toggled = !this.toggleBreakpointsButton.toggled;
919         if (this.toggleBreakpointsButton.toggled) {
920             DebuggerAgent.activateBreakpoints();
921             this.toggleBreakpointsButton.title = WebInspector.UIString("Deactivate all breakpoints.");
922             document.getElementById("main-panels").removeStyleClass("breakpoints-deactivated");
923         } else {
924             DebuggerAgent.deactivateBreakpoints();
925             this.toggleBreakpointsButton.title = WebInspector.UIString("Activate all breakpoints.");
926             document.getElementById("main-panels").addStyleClass("breakpoints-deactivated");
927         }
928     },
929
930     elementsToRestoreScrollPositionsFor: function()
931     {
932         return [ this.sidebarElement ];
933     },
934
935     _registerShortcuts: function()
936     {
937         var section = WebInspector.shortcutsHelp.section(WebInspector.UIString("Scripts Panel"));
938         var handler, shortcut1, shortcut2;
939         var platformSpecificModifier = WebInspector.KeyboardShortcut.Modifiers.CtrlOrMeta;
940
941         this._shortcuts = {};
942
943         // Continue.
944         handler = this.pauseButton.click.bind(this.pauseButton);
945         shortcut1 = WebInspector.KeyboardShortcut.makeDescriptor(WebInspector.KeyboardShortcut.Keys.F8);
946         this._shortcuts[shortcut1.key] = handler;
947         shortcut2 = WebInspector.KeyboardShortcut.makeDescriptor(WebInspector.KeyboardShortcut.Keys.Slash, platformSpecificModifier);
948         this._shortcuts[shortcut2.key] = handler;
949         section.addAlternateKeys([ shortcut1.name, shortcut2.name ], WebInspector.UIString("Continue"));
950
951         // Step over.
952         handler = this.stepOverButton.click.bind(this.stepOverButton);
953         shortcut1 = WebInspector.KeyboardShortcut.makeDescriptor(WebInspector.KeyboardShortcut.Keys.F10);
954         this._shortcuts[shortcut1.key] = handler;
955         shortcut2 = WebInspector.KeyboardShortcut.makeDescriptor(WebInspector.KeyboardShortcut.Keys.SingleQuote, platformSpecificModifier);
956         this._shortcuts[shortcut2.key] = handler;
957         section.addAlternateKeys([ shortcut1.name, shortcut2.name ], WebInspector.UIString("Step over"));
958
959         // Step into.
960         handler = this.stepIntoButton.click.bind(this.stepIntoButton);
961         shortcut1 = WebInspector.KeyboardShortcut.makeDescriptor(WebInspector.KeyboardShortcut.Keys.F11);
962         this._shortcuts[shortcut1.key] = handler;
963         shortcut2 = WebInspector.KeyboardShortcut.makeDescriptor(WebInspector.KeyboardShortcut.Keys.Semicolon, platformSpecificModifier);
964         this._shortcuts[shortcut2.key] = handler;
965         section.addAlternateKeys([ shortcut1.name, shortcut2.name ], WebInspector.UIString("Step into"));
966
967         // Step out.
968         handler = this.stepOutButton.click.bind(this.stepOutButton);
969         shortcut1 = WebInspector.KeyboardShortcut.makeDescriptor(WebInspector.KeyboardShortcut.Keys.F11, WebInspector.KeyboardShortcut.Modifiers.Shift);
970         this._shortcuts[shortcut1.key] = handler;
971         shortcut2 = WebInspector.KeyboardShortcut.makeDescriptor(WebInspector.KeyboardShortcut.Keys.Semicolon, WebInspector.KeyboardShortcut.Modifiers.Shift, platformSpecificModifier);
972         this._shortcuts[shortcut2.key] = handler;
973         section.addAlternateKeys([ shortcut1.name, shortcut2.name ], WebInspector.UIString("Step out"));
974
975         var isMac = WebInspector.isMac();
976         if (isMac)
977             shortcut1 = WebInspector.KeyboardShortcut.makeDescriptor("l", WebInspector.KeyboardShortcut.Modifiers.Meta);
978         else
979             shortcut1 = WebInspector.KeyboardShortcut.makeDescriptor("g", WebInspector.KeyboardShortcut.Modifiers.Ctrl);
980         this._shortcuts[shortcut1.key] = this.showGoToLineDialog.bind(this);
981         section.addAlternateKeys([ shortcut1.name ], WebInspector.UIString("Go to Line"));
982         this.sidebarPanes.callstack.registerShortcuts(section);
983     },
984
985     searchCanceled: function()
986     {
987         if (this._searchView)
988             this._searchView.searchCanceled();
989
990         delete this._searchView;
991         delete this._searchQuery;
992     },
993
994     performSearch: function(query)
995     {
996         WebInspector.searchController.updateSearchMatchesCount(0, this);
997
998         if (!this.visibleView)
999             return;
1000
1001         // Call searchCanceled since it will reset everything we need before doing a new search.
1002         this.searchCanceled();
1003
1004         this._searchView = this.visibleView;
1005         this._searchQuery = query;
1006
1007         function finishedCallback(view, searchMatches)
1008         {
1009             if (!searchMatches)
1010                 return;
1011
1012             WebInspector.searchController.updateSearchMatchesCount(searchMatches, this);
1013             view.jumpToFirstSearchResult();
1014         }
1015
1016         this._searchView.performSearch(query, finishedCallback.bind(this));
1017     },
1018
1019     jumpToNextSearchResult: function()
1020     {
1021         if (!this._searchView)
1022             return;
1023
1024         if (this._searchView !== this.visibleView) {
1025             this.performSearch(this._searchQuery);
1026             return;
1027         }
1028
1029         if (this._searchView.showingLastSearchResult())
1030             this._searchView.jumpToFirstSearchResult();
1031         else
1032             this._searchView.jumpToNextSearchResult();
1033     },
1034
1035     jumpToPreviousSearchResult: function()
1036     {
1037         if (!this._searchView)
1038             return;
1039
1040         if (this._searchView !== this.visibleView) {
1041             this.performSearch(this._searchQuery);
1042             if (this._searchView)
1043                 this._searchView.jumpToLastSearchResult();
1044             return;
1045         }
1046
1047         if (this._searchView.showingFirstSearchResult())
1048             this._searchView.jumpToLastSearchResult();
1049         else
1050             this._searchView.jumpToPreviousSearchResult();
1051     },
1052
1053     showGoToLineDialog: function(e)
1054     {
1055          var view = this.visibleView;
1056          if (view)
1057              WebInspector.GoToLineDialog.show(view);
1058     }
1059 }
1060
1061 WebInspector.ScriptsPanel.prototype.__proto__ = WebInspector.Panel.prototype;
1062
1063
1064 WebInspector.SourceFrameContentProviderForScript = function(script)
1065 {
1066     WebInspector.SourceFrameContentProvider.call(this);
1067     this._script = script;
1068 }
1069
1070 WebInspector.SourceFrameContentProviderForScript.prototype = {
1071     requestContent: function(callback)
1072     {
1073         var scripts = [this._script];
1074         if (this._script.sourceURL)
1075             scripts = WebInspector.debuggerModel.scriptsForURL(this._script.sourceURL);
1076         scripts.sort(function(x, y) { return x.lineOffset - y.lineOffset || x.columnOffset - y.columnOffset; });
1077
1078         var scriptsLeft = scripts.length;
1079         var sources = [];
1080         function didRequestSource(index, source)
1081         {
1082             sources[index] = source;
1083             if (--scriptsLeft)
1084                 return;
1085             var result = this._buildSource(scripts, sources);
1086             var sourceMapping = new WebInspector.IdenticalSourceMapping();
1087             callback(result.mimeType, new WebInspector.SourceFrameContent(result.source, sourceMapping, result.scriptRanges));
1088
1089         }
1090         for (var i = 0; i < scripts.length; ++i)
1091             scripts[i].requestSource(didRequestSource.bind(this, i));
1092     },
1093
1094     _buildSource: function(scripts, sources)
1095     {
1096         var source = "";
1097         var lineNumber = 0;
1098         var columnNumber = 0;
1099         var scriptRanges = [];
1100         function appendChunk(chunk, script)
1101         {
1102             var start = { lineNumber: lineNumber, columnNumber: columnNumber };
1103             source += chunk;
1104             var lineEndings = chunk.lineEndings();
1105             var lineCount = lineEndings.length;
1106             if (lineCount === 1)
1107                 columnNumber += chunk.length;
1108             else {
1109                 lineNumber += lineCount - 1;
1110                 columnNumber = lineEndings[lineCount - 1] - lineEndings[lineCount - 2] - 1;
1111             }
1112             var end = { lineNumber: lineNumber, columnNumber: columnNumber };
1113             if (script)
1114                 scriptRanges.push({ start: start, end: end, sourceID: script.sourceID });
1115         }
1116
1117         var mimeType;
1118         if (scripts.length === 1 && !scripts[0].lineOffset && !scripts[0].columnOffset) {
1119             // Single script source.
1120             mimeType = "text/javascript";
1121             appendChunk(sources[0], scripts[0]);
1122         } else {
1123             // Scripts inlined in html document.
1124             mimeType = "text/html";
1125             var scriptOpenTag = "<script>";
1126             var scriptCloseTag = "</script>";
1127             for (var i = 0; i < scripts.length; ++i) {
1128                 // Fill the gap with whitespace characters.
1129                 while (lineNumber < scripts[i].lineOffset)
1130                     appendChunk("\n");
1131                 while (columnNumber < scripts[i].columnOffset - scriptOpenTag.length)
1132                     appendChunk(" ");
1133
1134                 // Add script tag.
1135                 appendChunk(scriptOpenTag);
1136                 appendChunk(sources[i], scripts[i]);
1137                 appendChunk(scriptCloseTag);
1138             }
1139         }
1140         return { mimeType: mimeType, source: source, scriptRanges: scriptRanges };
1141     }
1142 }
1143
1144 WebInspector.SourceFrameContentProviderForScript.prototype.__proto__ = WebInspector.SourceFrameContentProvider.prototype;