2010-08-09 Ilya Tikhonovsky <loislo@chromium.org>
[WebKit-https.git] / 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.topStatusBar = document.createElement("div");
31     this.topStatusBar.className = "status-bar";
32     this.topStatusBar.id = "scripts-status-bar";
33     this.element.appendChild(this.topStatusBar);
34
35     this.backButton = document.createElement("button");
36     this.backButton.className = "status-bar-item";
37     this.backButton.id = "scripts-back";
38     this.backButton.title = WebInspector.UIString("Show the previous script resource.");
39     this.backButton.disabled = true;
40     this.backButton.appendChild(document.createElement("img"));
41     this.backButton.addEventListener("click", this._goBack.bind(this), false);
42     this.topStatusBar.appendChild(this.backButton);
43
44     this.forwardButton = document.createElement("button");
45     this.forwardButton.className = "status-bar-item";
46     this.forwardButton.id = "scripts-forward";
47     this.forwardButton.title = WebInspector.UIString("Show the next script resource.");
48     this.forwardButton.disabled = true;
49     this.forwardButton.appendChild(document.createElement("img"));
50     this.forwardButton.addEventListener("click", this._goForward.bind(this), false);
51     this.topStatusBar.appendChild(this.forwardButton);
52
53     this.filesSelectElement = document.createElement("select");
54     this.filesSelectElement.className = "status-bar-item";
55     this.filesSelectElement.id = "scripts-files";
56     this.filesSelectElement.addEventListener("change", this._changeVisibleFile.bind(this), false);
57     this.topStatusBar.appendChild(this.filesSelectElement);
58
59     this.functionsSelectElement = document.createElement("select");
60     this.functionsSelectElement.className = "status-bar-item";
61     this.functionsSelectElement.id = "scripts-functions";
62
63     // FIXME: append the functions select element to the top status bar when it is implemented.
64     // this.topStatusBar.appendChild(this.functionsSelectElement);
65
66     this.sidebarButtonsElement = document.createElement("div");
67     this.sidebarButtonsElement.id = "scripts-sidebar-buttons";
68     this.topStatusBar.appendChild(this.sidebarButtonsElement);
69
70     this.pauseButton = document.createElement("button");
71     this.pauseButton.className = "status-bar-item";
72     this.pauseButton.id = "scripts-pause";
73     this.pauseButton.title = WebInspector.UIString("Pause script execution.");
74     this.pauseButton.disabled = true;
75     this.pauseButton.appendChild(document.createElement("img"));
76     this.pauseButton.addEventListener("click", this._togglePause.bind(this), false);
77     this.sidebarButtonsElement.appendChild(this.pauseButton);
78
79     this.stepOverButton = document.createElement("button");
80     this.stepOverButton.className = "status-bar-item";
81     this.stepOverButton.id = "scripts-step-over";
82     this.stepOverButton.title = WebInspector.UIString("Step over next function call.");
83     this.stepOverButton.disabled = true;
84     this.stepOverButton.addEventListener("click", this._stepOverClicked.bind(this), false);
85     this.stepOverButton.appendChild(document.createElement("img"));
86     this.sidebarButtonsElement.appendChild(this.stepOverButton);
87
88     this.stepIntoButton = document.createElement("button");
89     this.stepIntoButton.className = "status-bar-item";
90     this.stepIntoButton.id = "scripts-step-into";
91     this.stepIntoButton.title = WebInspector.UIString("Step into next function call.");
92     this.stepIntoButton.disabled = true;
93     this.stepIntoButton.addEventListener("click", this._stepIntoClicked.bind(this), false);
94     this.stepIntoButton.appendChild(document.createElement("img"));
95     this.sidebarButtonsElement.appendChild(this.stepIntoButton);
96
97     this.stepOutButton = document.createElement("button");
98     this.stepOutButton.className = "status-bar-item";
99     this.stepOutButton.id = "scripts-step-out";
100     this.stepOutButton.title = WebInspector.UIString("Step out of current function.");
101     this.stepOutButton.disabled = true;
102     this.stepOutButton.addEventListener("click", this._stepOutClicked.bind(this), false);
103     this.stepOutButton.appendChild(document.createElement("img"));
104     this.sidebarButtonsElement.appendChild(this.stepOutButton);
105
106     this.toggleBreakpointsButton = new WebInspector.StatusBarButton(WebInspector.UIString("Deactivate all breakpoints."), "toggle-breakpoints");
107     // Breakpoints should be activated by default, so emulate a click to toggle on.
108     this.toggleBreakpointsButton.toggled = true;
109     this.toggleBreakpointsButton.addEventListener("click", this.toggleBreakpointsClicked.bind(this), false);
110     this.sidebarButtonsElement.appendChild(this.toggleBreakpointsButton.element);
111
112     this.debuggerStatusElement = document.createElement("div");
113     this.debuggerStatusElement.id = "scripts-debugger-status";
114     this.sidebarButtonsElement.appendChild(this.debuggerStatusElement);
115
116     this.viewsContainerElement = document.createElement("div");
117     this.viewsContainerElement.id = "script-resource-views";
118
119     this.sidebarElement = document.createElement("div");
120     this.sidebarElement.id = "scripts-sidebar";
121
122     this.sidebarResizeElement = document.createElement("div");
123     this.sidebarResizeElement.className = "sidebar-resizer-vertical";
124     this.sidebarResizeElement.addEventListener("mousedown", this._startSidebarResizeDrag.bind(this), false);
125
126     this.sidebarResizeWidgetElement = document.createElement("div");
127     this.sidebarResizeWidgetElement.id = "scripts-sidebar-resizer-widget";
128     this.sidebarResizeWidgetElement.addEventListener("mousedown", this._startSidebarResizeDrag.bind(this), false);
129     this.topStatusBar.appendChild(this.sidebarResizeWidgetElement);
130
131     this.sidebarPanes = {};
132     this.sidebarPanes.watchExpressions = new WebInspector.WatchExpressionsSidebarPane();
133     this.sidebarPanes.callstack = new WebInspector.CallStackSidebarPane();
134     this.sidebarPanes.scopechain = new WebInspector.ScopeChainSidebarPane();
135     this.sidebarPanes.breakpoints = new WebInspector.BreakpointsSidebarPane();
136     this.sidebarPanes.workers = new WebInspector.WorkersSidebarPane();
137
138     for (var pane in this.sidebarPanes)
139         this.sidebarElement.appendChild(this.sidebarPanes[pane].element);
140
141     this.sidebarPanes.callstack.expanded = true;
142     this.sidebarPanes.callstack.addEventListener("call frame selected", this._callFrameSelected, this);
143
144     this.sidebarPanes.scopechain.expanded = true;
145     this.sidebarPanes.breakpoints.expanded = true;
146
147     var panelEnablerHeading = WebInspector.UIString("You need to enable debugging before you can use the Scripts panel.");
148     var panelEnablerDisclaimer = WebInspector.UIString("Enabling debugging will make scripts run slower.");
149     var panelEnablerButton = WebInspector.UIString("Enable Debugging");
150
151     this.panelEnablerView = new WebInspector.PanelEnablerView("scripts", panelEnablerHeading, panelEnablerDisclaimer, panelEnablerButton);
152     this.panelEnablerView.addEventListener("enable clicked", this._enableDebugging, this);
153
154     this.element.appendChild(this.panelEnablerView.element);
155     this.element.appendChild(this.viewsContainerElement);
156     this.element.appendChild(this.sidebarElement);
157     this.element.appendChild(this.sidebarResizeElement);
158
159     this.enableToggleButton = new WebInspector.StatusBarButton("", "enable-toggle-status-bar-item");
160     this.enableToggleButton.addEventListener("click", this._toggleDebugging.bind(this), false);
161     if (Preferences.debuggerAlwaysEnabled)
162         this.enableToggleButton.element.addStyleClass("hidden");
163
164     this._pauseOnExceptionButton = new WebInspector.StatusBarButton("", "scripts-pause-on-exceptions-status-bar-item", 3);
165     this._pauseOnExceptionButton.addEventListener("click", this._togglePauseOnExceptions.bind(this), false);
166     this._pauseOnExceptionButton.state = WebInspector.ScriptsPanel.PauseOnExceptionsState.DontPauseOnExceptions;
167     this._pauseOnExceptionButton.title = WebInspector.UIString("Don't pause on exceptions.\nClick to Pause on all exceptions.");
168
169     this._registerShortcuts();
170
171     this._debuggerEnabled = Preferences.debuggerAlwaysEnabled;
172
173     WebInspector.breakpointManager.addEventListener("breakpoint-added", this._breakpointAdded, this);
174     WebInspector.breakpointManager.addEventListener("breakpoint-removed", this._breakpointRemoved, this);
175
176     this.reset();
177 }
178
179 // Keep these in sync with WebCore::ScriptDebugServer
180 WebInspector.ScriptsPanel.PauseOnExceptionsState = {
181     DontPauseOnExceptions : 0,
182     PauseOnAllExceptions : 1,
183     PauseOnUncaughtExceptions: 2
184 };
185
186 WebInspector.ScriptsPanel.prototype = {
187     get toolbarItemLabel()
188     {
189         return WebInspector.UIString("Scripts");
190     },
191
192     get statusBarItems()
193     {
194         return [this.enableToggleButton.element, this._pauseOnExceptionButton.element];
195     },
196
197     get defaultFocusedElement()
198     {
199         return this.filesSelectElement;
200     },
201
202     get paused()
203     {
204         return this._paused;
205     },
206
207     show: function()
208     {
209         WebInspector.Panel.prototype.show.call(this);
210         this.sidebarResizeElement.style.right = (this.sidebarElement.offsetWidth - 3) + "px";
211
212         if (this.visibleView) {
213             if (this.visibleView instanceof WebInspector.ResourceView)
214                 this.visibleView.headersVisible = false;
215             this.visibleView.show(this.viewsContainerElement);
216         }
217         if (this._attachDebuggerWhenShown) {
218             InspectorBackend.enableDebugger(false);
219             delete this._attachDebuggerWhenShown;
220         }
221     },
222
223     hide: function()
224     {
225         if (this.visibleView)
226             this.visibleView.hide();
227         WebInspector.Panel.prototype.hide.call(this);
228     },
229
230     get searchableViews()
231     {
232         return [ this.visibleView ];
233     },
234
235     get breakpointsActivated()
236     {
237         return this.toggleBreakpointsButton.toggled;
238     },
239
240     addScript: function(sourceID, sourceURL, source, startingLine, errorLine, errorMessage, scriptWorldType)
241     {
242         var script = new WebInspector.Script(sourceID, sourceURL, source, startingLine, errorLine, errorMessage, scriptWorldType);
243         this._sourceIDMap[sourceID] = script;
244
245         var resource = WebInspector.resourceURLMap[sourceURL];
246         if (resource) {
247             if (resource.finished) {
248                 // Resource is finished, bind the script right away.
249                 resource.addScript(script);
250                 this._sourceIDMap[sourceID] = resource;
251             } else {
252                 // Resource is not finished, bind the script later.
253                 if (!resource._scriptsPendingResourceLoad) {
254                     resource._scriptsPendingResourceLoad = [];
255                     resource.addEventListener("finished", this._resourceLoadingFinished, this);
256                 }
257                 resource._scriptsPendingResourceLoad.push(script);
258             }
259         }
260         this._addScriptToFilesMenu(script);
261     },
262
263     continueToLine: function(sourceID, line)
264     {
265         WebInspector.breakpointManager.setOneTimeBreakpoint(sourceID, line);
266         if (this.paused)
267             this._togglePause();
268     },
269
270     _resourceLoadingFinished: function(e)
271     {
272         var resource = e.target;
273         for (var i = 0; i < resource._scriptsPendingResourceLoad.length; ++i) {
274             // Bind script to resource.
275             var script = resource._scriptsPendingResourceLoad[i];
276             resource.addScript(script);
277             this._sourceIDMap[script.sourceID] = resource;
278
279             // Remove script from the files list.
280             script.filesSelectOption.parentElement.removeChild(script.filesSelectOption);
281
282             // Move breakpoints to the resource's frame.
283             if (script._scriptView) {
284                 var sourceFrame = script._scriptView.sourceFrame;
285                 var resourceFrame = this._sourceFrameForScriptOrResource(resource);
286                 for (var j = 0; j < sourceFrame.breakpoints; ++j)
287                     resourceFrame.addBreakpoint(sourceFrame.breakpoints[j]);
288             }
289         }
290         // Adding first script will add resource.
291         this._addScriptToFilesMenu(resource._scriptsPendingResourceLoad[0]);
292         delete resource._scriptsPendingResourceLoad;
293     },
294
295     _breakpointAdded: function(event)
296     {
297         var breakpoint = event.data;
298
299         var sourceFrame;
300         if (breakpoint.url) {
301             var resource = WebInspector.resourceURLMap[breakpoint.url];
302             if (resource && resource.finished)
303                 sourceFrame = this._sourceFrameForScriptOrResource(resource);
304         }
305
306         if (breakpoint.sourceID && !sourceFrame) {
307             var object = this._sourceIDMap[breakpoint.sourceID]
308             sourceFrame = this._sourceFrameForScriptOrResource(object);
309         }
310
311         if (sourceFrame)
312             sourceFrame.addBreakpoint(breakpoint);
313     },
314
315     _breakpointRemoved: function(event)
316     {
317         var breakpoint = event.data;
318
319         var sourceFrame;
320         if (breakpoint.url) {
321             var resource = WebInspector.resourceURLMap[breakpoint.url];
322             if (resource && resource.finished)
323                 sourceFrame = this._sourceFrameForScriptOrResource(resource);
324         }
325
326         if (breakpoint.sourceID && !sourceFrame) {
327             var object = this._sourceIDMap[breakpoint.sourceID]
328             sourceFrame = this._sourceFrameForScriptOrResource(object);
329         }
330
331         if (sourceFrame)
332             sourceFrame.removeBreakpoint(breakpoint);
333     },
334
335     canEditScripts: function()
336     {
337         return Preferences.canEditScriptSource;
338     },
339
340     editScriptSource: function(sourceID, newContent, line, linesCountToShift, commitEditingCallback, cancelEditingCallback)
341     {
342         if (!this.canEditScripts())
343             return;
344
345         // Need to clear breakpoints and re-create them later when editing source.
346         var breakpoints = WebInspector.breakpointManager.breakpointsForSourceID(sourceID);
347         for (var i = 0; i < breakpoints.length; ++i)
348             WebInspector.breakpointManager.removeBreakpoint(breakpoints[i]);
349
350         function mycallback(success, newBodyOrErrorMessage, callFrames)
351         {
352             if (success) {
353                 commitEditingCallback(newBodyOrErrorMessage);
354                 if (callFrames && callFrames.length)
355                     this.debuggerPaused(callFrames);
356             } else {
357                 cancelEditingCallback();
358                 WebInspector.log(newBodyOrErrorMessage, WebInspector.ConsoleMessage.MessageLevel.Warning);
359             }
360             for (var i = 0; i < breakpoints.length; ++i) {
361                 var breakpoint = breakpoints[i];
362                 var newLine = breakpoint.line;
363                 if (success && breakpoint.line >= line)
364                     newLine += linesCountToShift;
365                 WebInspector.breakpointManager.setBreakpoint(sourceID, breakpoint.url, newLine, breakpoint.enabled, breakpoint.condition);
366             }
367         };
368         var callbackId = WebInspector.Callback.wrap(mycallback.bind(this))
369         InspectorBackend.editScriptSource(callbackId, sourceID, newContent);
370     },
371
372     selectedCallFrameId: function()
373     {
374         var selectedCallFrame = this.sidebarPanes.callstack.selectedCallFrame;
375         if (!selectedCallFrame)
376             return null;
377         return selectedCallFrame.id;
378     },
379
380     evaluateInSelectedCallFrame: function(code, updateInterface, objectGroup, callback)
381     {
382         var selectedCallFrame = this.sidebarPanes.callstack.selectedCallFrame;
383         if (!this._paused || !selectedCallFrame)
384             return;
385
386         if (typeof updateInterface === "undefined")
387             updateInterface = true;
388
389         var self = this;
390         function updatingCallbackWrapper(result, exception)
391         {
392             callback(result, exception);
393             if (updateInterface)
394                 self.sidebarPanes.scopechain.update(selectedCallFrame);
395         }
396         this.doEvalInCallFrame(selectedCallFrame, code, objectGroup, updatingCallbackWrapper);
397     },
398
399     doEvalInCallFrame: function(callFrame, code, objectGroup, callback)
400     {
401         function evalCallback(result)
402         {
403             if (result)
404                 callback(result.value, result.isException);
405         }
406         InjectedScriptAccess.get(callFrame.injectedScriptId).evaluateInCallFrame(callFrame.id, code, objectGroup, evalCallback);
407     },
408
409     debuggerPaused: function(callFrames)
410     {
411         WebInspector.breakpointManager.removeOneTimeBreakpoint();
412         this._paused = true;
413         this._waitingToPause = false;
414         this._stepping = false;
415
416         this._updateDebuggerButtons();
417
418         this.sidebarPanes.callstack.update(callFrames, this._sourceIDMap);
419         this.sidebarPanes.callstack.selectedCallFrame = callFrames[0];
420
421         WebInspector.currentPanel = this;
422         window.focus();
423     },
424
425     debuggerResumed: function()
426     {
427         this._paused = false;
428         this._waitingToPause = false;
429         this._stepping = false;
430
431         this._clearInterface();
432     },
433
434     attachDebuggerWhenShown: function()
435     {
436         if (this.element.parentElement) {
437             InspectorBackend.enableDebugger(false);
438         } else {
439             this._attachDebuggerWhenShown = true;
440         }
441     },
442
443     debuggerWasEnabled: function()
444     {
445         if (this._debuggerEnabled)
446             return;
447
448         this._debuggerEnabled = true;
449         this.reset(true);
450     },
451
452     debuggerWasDisabled: function()
453     {
454         if (!this._debuggerEnabled)
455             return;
456
457         this._debuggerEnabled = false;
458         this.reset(true);
459     },
460
461     reset: function(preserveItems)
462     {
463         this.visibleView = null;
464
465         delete this.currentQuery;
466         this.searchCanceled();
467
468         if (!this._debuggerEnabled) {
469             this._paused = false;
470             this._waitingToPause = false;
471             this._stepping = false;
472         }
473
474         this._clearInterface();
475
476         this._backForwardList = [];
477         this._currentBackForwardIndex = -1;
478         this._updateBackAndForwardButtons();
479
480         this._resourceForURLInFilesSelect = {};
481         this.filesSelectElement.removeChildren();
482         this.functionsSelectElement.removeChildren();
483         this.viewsContainerElement.removeChildren();
484
485         if (this._sourceIDMap) {
486             for (var sourceID in this._sourceIDMap) {
487                 var object = this._sourceIDMap[sourceID];
488                 if (object instanceof WebInspector.Resource)
489                     object.removeAllScripts();
490             }
491         }
492
493         this._sourceIDMap = {};
494
495         this.sidebarPanes.watchExpressions.refreshExpressions();
496         if (!preserveItems) {
497             this.sidebarPanes.breakpoints.reset();
498             this.sidebarPanes.workers.reset();
499         }
500     },
501
502     get visibleView()
503     {
504         return this._visibleView;
505     },
506
507     set visibleView(x)
508     {
509         if (this._visibleView === x)
510             return;
511
512         if (this._visibleView)
513             this._visibleView.hide();
514
515         this._visibleView = x;
516
517         if (x)
518             x.show(this.viewsContainerElement);
519     },
520
521     viewRecreated: function(oldView, newView)
522     {
523         if (this._visibleView === oldView)
524             this._visibleView = newView;
525     },
526
527     canShowSourceLine: function(url, line)
528     {
529         if (!this._debuggerEnabled)
530             return false;
531         return !!this._scriptOrResourceForURLAndLine(url, line);
532     },
533
534     showSourceLine: function(url, line)
535     {
536         var scriptOrResource = this._scriptOrResourceForURLAndLine(url, line);
537         this._showScriptOrResource(scriptOrResource, {line: line, shouldHighlightLine: true});
538     },
539
540     _scriptOrResourceForURLAndLine: function(url, line)
541     {
542         var scriptWithMatchingUrl = null;
543         for (var sourceID in this._sourceIDMap) {
544             var scriptOrResource = this._sourceIDMap[sourceID];
545             if (scriptOrResource instanceof WebInspector.Script) {
546                 if (scriptOrResource.sourceURL !== url)
547                     continue;
548                 scriptWithMatchingUrl = scriptOrResource;
549                 if (scriptWithMatchingUrl.startingLine <= line && scriptWithMatchingUrl.startingLine + scriptWithMatchingUrl.linesCount > line)
550                     return scriptWithMatchingUrl;
551             } else {
552                 var resource = scriptOrResource;
553                 if (resource.url === url)
554                     return resource;
555             }
556         }
557         return scriptWithMatchingUrl;
558     },
559
560     showView: function(view)
561     {
562         if (!view)
563             return;
564         this._showScriptOrResource(view.resource || view.script);
565     },
566
567     handleShortcut: function(event)
568     {
569         var shortcut = WebInspector.KeyboardShortcut.makeKeyFromEvent(event);
570         var handler = this._shortcuts[shortcut];
571         if (handler) {
572             handler(event);
573             event.handled = true;
574         } else
575             this.sidebarPanes.callstack.handleShortcut(event);
576     },
577
578     scriptViewForScript: function(script)
579     {
580         if (!script)
581             return null;
582         if (!script._scriptView)
583             script._scriptView = new WebInspector.ScriptView(script);
584         return script._scriptView;
585     },
586
587     sourceFrameForScript: function(script)
588     {
589         var view = this.scriptViewForScript(script);
590         if (!view)
591             return null;
592
593         // Setting up the source frame requires that we be attached.
594         if (!this.element.parentNode)
595             this.attach();
596
597         view.setupSourceFrameIfNeeded();
598         return view.sourceFrame;
599     },
600
601     _sourceFrameForScriptOrResource: function(scriptOrResource)
602     {
603         if (scriptOrResource instanceof WebInspector.Resource)
604             return WebInspector.panels.resources.sourceFrameForResource(scriptOrResource);
605         if (scriptOrResource instanceof WebInspector.Script)
606             return this.sourceFrameForScript(scriptOrResource);
607     },
608
609     _showScriptOrResource: function(scriptOrResource, options)
610     {
611         // options = {line:, shouldHighlightLine:, fromBackForwardAction:, initialLoad:}
612         options = options || {};
613
614         if (!scriptOrResource)
615             return;
616
617         var view;
618         if (scriptOrResource instanceof WebInspector.Resource) {
619             if (!WebInspector.panels.resources)
620                 return null;
621             view = WebInspector.panels.resources.resourceViewForResource(scriptOrResource);
622             view.headersVisible = false;
623             var sourceFrame = this._sourceFrameForScriptOrResource(scriptOrResource);
624             var breakpoints = WebInspector.breakpointManager.breakpointsForURL(scriptOrResource.url);
625             for (var i = 0; i < breakpoints.length; ++i)
626                 sourceFrame.addBreakpoint(breakpoints[i]);
627         } else if (scriptOrResource instanceof WebInspector.Script)
628             view = this.scriptViewForScript(scriptOrResource);
629
630         if (!view)
631             return;
632
633         var url = scriptOrResource.url || scriptOrResource.sourceURL;
634         if (url && !options.initialLoad)
635             WebInspector.applicationSettings.lastViewedScriptFile = url;
636
637         if (!options.fromBackForwardAction) {
638             var oldIndex = this._currentBackForwardIndex;
639             if (oldIndex >= 0)
640                 this._backForwardList.splice(oldIndex + 1, this._backForwardList.length - oldIndex);
641
642             // Check for a previous entry of the same object in _backForwardList.
643             // If one is found, remove it and update _currentBackForwardIndex to match.
644             var previousEntryIndex = this._backForwardList.indexOf(scriptOrResource);
645             if (previousEntryIndex !== -1) {
646                 this._backForwardList.splice(previousEntryIndex, 1);
647                 --this._currentBackForwardIndex;
648             }
649
650             this._backForwardList.push(scriptOrResource);
651             ++this._currentBackForwardIndex;
652
653             this._updateBackAndForwardButtons();
654         }
655
656         this.visibleView = view;
657
658         if (options.line) {
659             if (view.revealLine)
660                 view.revealLine(options.line);
661             if (view.highlightLine && options.shouldHighlightLine)
662                 view.highlightLine(options.line);
663         }
664
665         var option;
666         if (scriptOrResource instanceof WebInspector.Script) {
667             option = scriptOrResource.filesSelectOption;
668
669             // hasn't been added yet - happens for stepping in evals,
670             // so use the force option to force the script into the menu.
671             if (!option) {
672                 this._addScriptToFilesMenu(scriptOrResource, true);
673                 option = scriptOrResource.filesSelectOption;
674             }
675
676             console.assert(option);
677         } else
678             option = scriptOrResource.filesSelectOption;
679
680         if (option)
681             this.filesSelectElement.selectedIndex = option.index;
682     },
683
684     _addScriptToFilesMenu: function(script, force)
685     {
686         if (!script.sourceURL && !force)
687             return;
688
689         if (script.resource) {
690             if (this._resourceForURLInFilesSelect[script.resource.url])
691                 return;
692             this._resourceForURLInFilesSelect[script.resource.url] = script.resource;
693         }
694
695         var displayName = script.sourceURL ? WebInspector.displayNameForURL(script.sourceURL) : WebInspector.UIString("(program)");
696
697         var select = this.filesSelectElement;
698         var option = document.createElement("option");
699         option.representedObject = script.resource || script;
700         option.url = displayName;
701         option.startingLine = script.startingLine;
702         option.text = script.resource || script.startingLine === 1 ? displayName : String.sprintf("%s:%d", displayName, script.startingLine);
703
704         function optionCompare(a, b)
705         {
706             if (a.url < b.url)
707                 return -1;
708             else if (a.url > b.url)
709                 return 1;
710
711             if (typeof a.startingLine !== "number")
712                 return -1;
713             if (typeof b.startingLine !== "number")
714                 return -1;
715             return a.startingLine - b.startingLine;
716         }
717
718         var insertionIndex = insertionIndexForObjectInListSortedByFunction(option, select.childNodes, optionCompare);
719         if (insertionIndex < 0)
720             select.appendChild(option);
721         else
722             select.insertBefore(option, select.childNodes.item(insertionIndex));
723
724         if (script.resource)
725             script.resource.filesSelectOption = option;
726         else
727             script.filesSelectOption = option;
728
729         // Call _showScriptOrResource if the option we just appended ended up being selected.
730         // This will happen for the first item added to the menu.
731         if (select.options[select.selectedIndex] === option)
732             this._showScriptOrResource(option.representedObject, {initialLoad: true});
733         else {
734             // if not first item, check to see if this was the last viewed
735             var url = option.representedObject.url || option.representedObject.sourceURL;
736             var lastURL = WebInspector.applicationSettings.lastViewedScriptFile;
737             if (url && url === lastURL)
738                 this._showScriptOrResource(option.representedObject, {initialLoad: true});
739         }
740
741         if (script.worldType === WebInspector.Script.WorldType.EXTENSIONS_WORLD)
742             script.filesSelectOption.addStyleClass("extension-script");
743     },
744
745     _clearCurrentExecutionLine: function()
746     {
747         if (this._executionSourceFrame)
748             this._executionSourceFrame.executionLine = 0;
749         delete this._executionSourceFrame;
750     },
751
752     _callFrameSelected: function()
753     {
754         this._clearCurrentExecutionLine();
755
756         var callStackPane = this.sidebarPanes.callstack;
757         var currentFrame = callStackPane.selectedCallFrame;
758         if (!currentFrame)
759             return;
760
761         this.sidebarPanes.scopechain.update(currentFrame);
762         this.sidebarPanes.watchExpressions.refreshExpressions();
763
764         var scriptOrResource = this._sourceIDMap[currentFrame.sourceID];
765         this._showScriptOrResource(scriptOrResource, {line: currentFrame.line});
766
767         this._executionSourceFrame = this._sourceFrameForScriptOrResource(scriptOrResource);
768         if (this._executionSourceFrame)
769             this._executionSourceFrame.executionLine = currentFrame.line;
770     },
771
772     _changeVisibleFile: function(event)
773     {
774         var select = this.filesSelectElement;
775         this._showScriptOrResource(select.options[select.selectedIndex].representedObject);
776     },
777
778     _startSidebarResizeDrag: function(event)
779     {
780         WebInspector.elementDragStart(this.sidebarElement, this._sidebarResizeDrag.bind(this), this._endSidebarResizeDrag.bind(this), event, "col-resize");
781
782         if (event.target === this.sidebarResizeWidgetElement)
783             this._dragOffset = (event.target.offsetWidth - (event.pageX - event.target.totalOffsetLeft));
784         else
785             this._dragOffset = 0;
786     },
787
788     _endSidebarResizeDrag: function(event)
789     {
790         WebInspector.elementDragEnd(event);
791         delete this._dragOffset;
792         this.saveSidebarWidth();
793     },
794
795     _sidebarResizeDrag: function(event)
796     {
797         var x = event.pageX + this._dragOffset;
798         var newWidth = Number.constrain(window.innerWidth - x, Preferences.minScriptsSidebarWidth, window.innerWidth * 0.66);
799         this.setSidebarWidth(newWidth);
800         event.preventDefault();
801     },
802
803     setSidebarWidth: function(newWidth)
804     {
805         this.sidebarElement.style.width = newWidth + "px";
806         this.sidebarButtonsElement.style.width = newWidth + "px";
807         this.viewsContainerElement.style.right = newWidth + "px";
808         this.sidebarResizeWidgetElement.style.right = newWidth + "px";
809         this.sidebarResizeElement.style.right = (newWidth - 3) + "px";
810
811         this.resize();
812     },
813
814     updatePauseOnExceptionsState: function(pauseOnExceptionsState)
815     {
816         if (pauseOnExceptionsState == WebInspector.ScriptsPanel.PauseOnExceptionsState.DontPauseOnExceptions)
817             this._pauseOnExceptionButton.title = WebInspector.UIString("Don't pause on exceptions.\nClick to Pause on all exceptions.");
818         else if (pauseOnExceptionsState == WebInspector.ScriptsPanel.PauseOnExceptionsState.PauseOnAllExceptions)
819             this._pauseOnExceptionButton.title = WebInspector.UIString("Pause on all exceptions.\nClick to Pause on uncaught exceptions.");
820         else if (pauseOnExceptionsState == WebInspector.ScriptsPanel.PauseOnExceptionsState.PauseOnUncaughtExceptions)
821             this._pauseOnExceptionButton.title = WebInspector.UIString("Pause on uncaught exceptions.\nClick to Not pause on exceptions.");
822
823         this._pauseOnExceptionButton.state = pauseOnExceptionsState;
824     },
825
826     _updateDebuggerButtons: function()
827     {
828         if (this._debuggerEnabled) {
829             this.enableToggleButton.title = WebInspector.UIString("Debugging enabled. Click to disable.");
830             this.enableToggleButton.toggled = true;
831             this._pauseOnExceptionButton.visible = true;
832             this.panelEnablerView.visible = false;
833         } else {
834             this.enableToggleButton.title = WebInspector.UIString("Debugging disabled. Click to enable.");
835             this.enableToggleButton.toggled = false;
836             this._pauseOnExceptionButton.visible = false;
837             this.panelEnablerView.visible = true;
838         }
839
840         if (this._paused) {
841             this.pauseButton.addStyleClass("paused");
842
843             this.pauseButton.disabled = false;
844             this.stepOverButton.disabled = false;
845             this.stepIntoButton.disabled = false;
846             this.stepOutButton.disabled = false;
847
848             this.debuggerStatusElement.textContent = WebInspector.UIString("Paused");
849         } else {
850             this.pauseButton.removeStyleClass("paused");
851
852             this.pauseButton.disabled = this._waitingToPause;
853             this.stepOverButton.disabled = true;
854             this.stepIntoButton.disabled = true;
855             this.stepOutButton.disabled = true;
856
857             if (this._waitingToPause)
858                 this.debuggerStatusElement.textContent = WebInspector.UIString("Pausing");
859             else if (this._stepping)
860                 this.debuggerStatusElement.textContent = WebInspector.UIString("Stepping");
861             else
862                 this.debuggerStatusElement.textContent = "";
863         }
864     },
865
866     _updateBackAndForwardButtons: function()
867     {
868         this.backButton.disabled = this._currentBackForwardIndex <= 0;
869         this.forwardButton.disabled = this._currentBackForwardIndex >= (this._backForwardList.length - 1);
870     },
871
872     _clearInterface: function()
873     {
874         this.sidebarPanes.callstack.update(null);
875         this.sidebarPanes.scopechain.update(null);
876
877         this._clearCurrentExecutionLine();
878         this._updateDebuggerButtons();
879     },
880
881     _goBack: function()
882     {
883         if (this._currentBackForwardIndex <= 0) {
884             console.error("Can't go back from index " + this._currentBackForwardIndex);
885             return;
886         }
887
888         this._showScriptOrResource(this._backForwardList[--this._currentBackForwardIndex], {fromBackForwardAction: true});
889         this._updateBackAndForwardButtons();
890     },
891
892     _goForward: function()
893     {
894         if (this._currentBackForwardIndex >= this._backForwardList.length - 1) {
895             console.error("Can't go forward from index " + this._currentBackForwardIndex);
896             return;
897         }
898
899         this._showScriptOrResource(this._backForwardList[++this._currentBackForwardIndex], {fromBackForwardAction: true});
900         this._updateBackAndForwardButtons();
901     },
902
903     _enableDebugging: function()
904     {
905         if (this._debuggerEnabled)
906             return;
907         this._toggleDebugging(this.panelEnablerView.alwaysEnabled);
908     },
909
910     _toggleDebugging: function(optionalAlways)
911     {
912         this._paused = false;
913         this._waitingToPause = false;
914         this._stepping = false;
915
916         if (this._debuggerEnabled)
917             InspectorBackend.disableDebugger(true);
918         else
919             InspectorBackend.enableDebugger(!!optionalAlways);
920     },
921
922     _togglePauseOnExceptions: function()
923     {
924         InspectorBackend.setPauseOnExceptionsState((this._pauseOnExceptionButton.state + 1) % this._pauseOnExceptionButton.states);
925     },
926
927     _togglePause: function()
928     {
929         if (this._paused) {
930             this._paused = false;
931             this._waitingToPause = false;
932             InspectorBackend.resume();
933         } else {
934             this._stepping = false;
935             this._waitingToPause = true;
936             InspectorBackend.pause();
937         }
938
939         this._clearInterface();
940     },
941
942     _stepOverClicked: function()
943     {
944         this._paused = false;
945         this._stepping = true;
946
947         this._clearInterface();
948
949         InspectorBackend.stepOverStatement();
950     },
951
952     _stepIntoClicked: function()
953     {
954         this._paused = false;
955         this._stepping = true;
956
957         this._clearInterface();
958
959         InspectorBackend.stepIntoStatement();
960     },
961
962     _stepOutClicked: function()
963     {
964         this._paused = false;
965         this._stepping = true;
966
967         this._clearInterface();
968
969         InspectorBackend.stepOutOfFunction();
970     },
971
972     toggleBreakpointsClicked: function()
973     {
974         this.toggleBreakpointsButton.toggled = !this.toggleBreakpointsButton.toggled;
975         if (this.toggleBreakpointsButton.toggled) {
976             InspectorBackend.activateBreakpoints();
977             this.toggleBreakpointsButton.title = WebInspector.UIString("Deactivate all breakpoints.");
978             document.getElementById("main-panels").removeStyleClass("breakpoints-deactivated");
979         } else {
980             InspectorBackend.deactivateBreakpoints();
981             this.toggleBreakpointsButton.title = WebInspector.UIString("Activate all breakpoints.");
982             document.getElementById("main-panels").addStyleClass("breakpoints-deactivated");
983         }
984     },
985
986     elementsToRestoreScrollPositionsFor: function()
987     {
988         return [ this.sidebarElement ];
989     },
990
991     _registerShortcuts: function()
992     {
993         var section = WebInspector.shortcutsHelp.section(WebInspector.UIString("Scripts Panel"));
994         var handler, shortcut1, shortcut2;
995         var platformSpecificModifier = WebInspector.KeyboardShortcut.Modifiers.CtrlOrMeta;
996
997         this._shortcuts = {};
998
999         // Continue.
1000         handler = this.pauseButton.click.bind(this.pauseButton);
1001         shortcut1 = WebInspector.KeyboardShortcut.makeDescriptor(WebInspector.KeyboardShortcut.Keys.F8);
1002         this._shortcuts[shortcut1.key] = handler;
1003         shortcut2 = WebInspector.KeyboardShortcut.makeDescriptor(WebInspector.KeyboardShortcut.Keys.Slash, platformSpecificModifier);
1004         this._shortcuts[shortcut2.key] = handler;
1005         section.addAlternateKeys([ shortcut1.name, shortcut2.name ], WebInspector.UIString("Continue"));
1006
1007         // Step over.
1008         handler = this.stepOverButton.click.bind(this.stepOverButton);
1009         shortcut1 = WebInspector.KeyboardShortcut.makeDescriptor(WebInspector.KeyboardShortcut.Keys.F10);
1010         this._shortcuts[shortcut1.key] = handler;
1011         shortcut2 = WebInspector.KeyboardShortcut.makeDescriptor(WebInspector.KeyboardShortcut.Keys.SingleQuote, platformSpecificModifier);
1012         this._shortcuts[shortcut2.key] = handler;
1013         section.addAlternateKeys([ shortcut1.name, shortcut2.name ], WebInspector.UIString("Step over"));
1014
1015         // Step into.
1016         handler = this.stepIntoButton.click.bind(this.stepIntoButton);
1017         shortcut1 = WebInspector.KeyboardShortcut.makeDescriptor(WebInspector.KeyboardShortcut.Keys.F11);
1018         this._shortcuts[shortcut1.key] = handler;
1019         shortcut2 = WebInspector.KeyboardShortcut.makeDescriptor(WebInspector.KeyboardShortcut.Keys.Semicolon, platformSpecificModifier);
1020         this._shortcuts[shortcut2.key] = handler;
1021         section.addAlternateKeys([ shortcut1.name, shortcut2.name ], WebInspector.UIString("Step into"));
1022
1023         // Step out.
1024         handler = this.stepOutButton.click.bind(this.stepOutButton);
1025         shortcut1 = WebInspector.KeyboardShortcut.makeDescriptor(WebInspector.KeyboardShortcut.Keys.F11, WebInspector.KeyboardShortcut.Modifiers.Shift);
1026         this._shortcuts[shortcut1.key] = handler;
1027         shortcut2 = WebInspector.KeyboardShortcut.makeDescriptor(WebInspector.KeyboardShortcut.Keys.Semicolon, WebInspector.KeyboardShortcut.Modifiers.Shift, platformSpecificModifier);
1028         this._shortcuts[shortcut2.key] = handler;
1029         section.addAlternateKeys([ shortcut1.name, shortcut2.name ], WebInspector.UIString("Step out"));
1030
1031         this.sidebarPanes.callstack.registerShortcuts(section);
1032     }
1033 }
1034
1035 WebInspector.ScriptsPanel.prototype.__proto__ = WebInspector.Panel.prototype;
1036