2009-07-07 Yury Semikhatsky <yurys@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);
29
30     this.element.addStyleClass("scripts");
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._changeVisibleFile.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.sidebarButtonsElement = document.createElement("div");
69     this.sidebarButtonsElement.id = "scripts-sidebar-buttons";
70     this.topStatusBar.appendChild(this.sidebarButtonsElement);
71
72     this.pauseButton = document.createElement("button");
73     this.pauseButton.className = "status-bar-item";
74     this.pauseButton.id = "scripts-pause";
75     this.pauseButton.title = WebInspector.UIString("Pause script execution.");
76     this.pauseButton.disabled = true;
77     this.pauseButton.appendChild(document.createElement("img"));
78     this.pauseButton.addEventListener("click", this._togglePause.bind(this), false);
79     this.sidebarButtonsElement.appendChild(this.pauseButton);
80
81     this.stepOverButton = document.createElement("button");
82     this.stepOverButton.className = "status-bar-item";
83     this.stepOverButton.id = "scripts-step-over";
84     this.stepOverButton.title = WebInspector.UIString("Step over next function call.");
85     this.stepOverButton.disabled = true;
86     this.stepOverButton.addEventListener("click", this._stepOverClicked.bind(this), false);
87     this.stepOverButton.appendChild(document.createElement("img"));
88     this.sidebarButtonsElement.appendChild(this.stepOverButton);
89
90     this.stepIntoButton = document.createElement("button");
91     this.stepIntoButton.className = "status-bar-item";
92     this.stepIntoButton.id = "scripts-step-into";
93     this.stepIntoButton.title = WebInspector.UIString("Step into next function call.");
94     this.stepIntoButton.disabled = true;
95     this.stepIntoButton.addEventListener("click", this._stepIntoClicked.bind(this), false);
96     this.stepIntoButton.appendChild(document.createElement("img"));
97     this.sidebarButtonsElement.appendChild(this.stepIntoButton);
98
99     this.stepOutButton = document.createElement("button");
100     this.stepOutButton.className = "status-bar-item";
101     this.stepOutButton.id = "scripts-step-out";
102     this.stepOutButton.title = WebInspector.UIString("Step out of current function.");
103     this.stepOutButton.disabled = true;
104     this.stepOutButton.addEventListener("click", this._stepOutClicked.bind(this), false);
105     this.stepOutButton.appendChild(document.createElement("img"));
106     this.sidebarButtonsElement.appendChild(this.stepOutButton);
107
108     this.debuggerStatusElement = document.createElement("div");
109     this.debuggerStatusElement.id = "scripts-debugger-status";
110     this.sidebarButtonsElement.appendChild(this.debuggerStatusElement);
111
112     this.viewsContainerElement = document.createElement("div");
113     this.viewsContainerElement.id = "script-resource-views";
114
115     this.sidebarElement = document.createElement("div");
116     this.sidebarElement.id = "scripts-sidebar";
117
118     this.sidebarResizeElement = document.createElement("div");
119     this.sidebarResizeElement.className = "sidebar-resizer-vertical";
120     this.sidebarResizeElement.addEventListener("mousedown", this._startSidebarResizeDrag.bind(this), false);
121
122     this.sidebarResizeWidgetElement = document.createElement("div");
123     this.sidebarResizeWidgetElement.id = "scripts-sidebar-resizer-widget";
124     this.sidebarResizeWidgetElement.addEventListener("mousedown", this._startSidebarResizeDrag.bind(this), false);
125     this.topStatusBar.appendChild(this.sidebarResizeWidgetElement);
126
127     this.sidebarPanes = {};
128     this.sidebarPanes.callstack = new WebInspector.CallStackSidebarPane();
129     this.sidebarPanes.scopechain = new WebInspector.ScopeChainSidebarPane();
130     this.sidebarPanes.breakpoints = new WebInspector.BreakpointsSidebarPane();
131
132     for (var pane in this.sidebarPanes)
133         this.sidebarElement.appendChild(this.sidebarPanes[pane].element);
134
135     // FIXME: remove the following line of code when the Breakpoints pane has content.
136     this.sidebarElement.removeChild(this.sidebarPanes.breakpoints.element);
137
138     this.sidebarPanes.callstack.expanded = true;
139     this.sidebarPanes.callstack.addEventListener("call frame selected", this._callFrameSelected, this);
140
141     this.sidebarPanes.scopechain.expanded = true;
142
143     var panelEnablerHeading = WebInspector.UIString("You need to enable debugging before you can use the Scripts panel.");
144     var panelEnablerDisclaimer = WebInspector.UIString("Enabling debugging will make scripts run slower.");
145     var panelEnablerButton = WebInspector.UIString("Enable Debugging");
146
147     this.panelEnablerView = new WebInspector.PanelEnablerView("scripts", panelEnablerHeading, panelEnablerDisclaimer, panelEnablerButton);
148     this.panelEnablerView.addEventListener("enable clicked", this._enableDebugging, this);
149
150     this.element.appendChild(this.panelEnablerView.element);
151     this.element.appendChild(this.viewsContainerElement);
152     this.element.appendChild(this.sidebarElement);
153     this.element.appendChild(this.sidebarResizeElement);
154
155     this.enableToggleButton = document.createElement("button");
156     this.enableToggleButton.className = "enable-toggle-status-bar-item status-bar-item";
157     this.enableToggleButton.addEventListener("click", this._toggleDebugging.bind(this), false);
158
159     this.pauseOnExceptionButton = document.createElement("button");
160     this.pauseOnExceptionButton.id = "scripts-pause-on-exceptions-status-bar-item";
161     this.pauseOnExceptionButton.className = "status-bar-item";
162     this.pauseOnExceptionButton.addEventListener("click", this._togglePauseOnExceptions.bind(this), false);
163
164     this._breakpointsURLMap = {};
165
166     this._shortcuts = {};
167
168     var isMac = InspectorController.platform().indexOf("mac-") === 0;
169     var platformSpecificModifier = isMac ? WebInspector.KeyboardShortcut.Modifiers.Meta : WebInspector.KeyboardShortcut.Modifiers.Ctrl;
170
171     // Continue.
172     var handler = this.pauseButton.click.bind(this.pauseButton);
173     var shortcut = WebInspector.KeyboardShortcut.makeKey(WebInspector.KeyboardShortcut.KeyCodes.F8);
174     this._shortcuts[shortcut] = handler;
175     var shortcut = WebInspector.KeyboardShortcut.makeKey(WebInspector.KeyboardShortcut.KeyCodes.Slash, platformSpecificModifier);
176     this._shortcuts[shortcut] = handler;
177
178     // Step over.
179     var handler = this.stepOverButton.click.bind(this.stepOverButton);
180     var shortcut = WebInspector.KeyboardShortcut.makeKey(WebInspector.KeyboardShortcut.KeyCodes.F10);
181     this._shortcuts[shortcut] = handler;
182     var shortcut = WebInspector.KeyboardShortcut.makeKey(WebInspector.KeyboardShortcut.KeyCodes.SingleQuote, platformSpecificModifier);
183     this._shortcuts[shortcut] = handler;
184
185     // Step into.
186     var handler = this.stepIntoButton.click.bind(this.stepIntoButton);
187     var shortcut = WebInspector.KeyboardShortcut.makeKey(WebInspector.KeyboardShortcut.KeyCodes.F11);
188     this._shortcuts[shortcut] = handler;
189     var shortcut = WebInspector.KeyboardShortcut.makeKey(WebInspector.KeyboardShortcut.KeyCodes.Semicolon, platformSpecificModifier);
190     this._shortcuts[shortcut] = handler;
191
192     // Step out.
193     var handler = this.stepOutButton.click.bind(this.stepOutButton);
194     var shortcut = WebInspector.KeyboardShortcut.makeKey(WebInspector.KeyboardShortcut.KeyCodes.F11, WebInspector.KeyboardShortcut.Modifiers.Shift);
195     this._shortcuts[shortcut] = handler;
196     var shortcut = WebInspector.KeyboardShortcut.makeKey(WebInspector.KeyboardShortcut.KeyCodes.Semicolon, WebInspector.KeyboardShortcut.Modifiers.Shift, platformSpecificModifier);
197     this._shortcuts[shortcut] = handler;
198
199     this.reset();
200 }
201
202 WebInspector.ScriptsPanel.prototype = {
203     toolbarItemClass: "scripts",
204
205     get toolbarItemLabel()
206     {
207         return WebInspector.UIString("Scripts");
208     },
209
210     get statusBarItems()
211     {
212         return [this.enableToggleButton, this.pauseOnExceptionButton];
213     },
214
215     get paused()
216     {
217         return this._paused;
218     },
219
220     show: function()
221     {
222         WebInspector.Panel.prototype.show.call(this);
223         this.sidebarResizeElement.style.right = (this.sidebarElement.offsetWidth - 3) + "px";
224
225         if (this.visibleView) {
226             if (this.visibleView instanceof WebInspector.ResourceView)
227                 this.visibleView.headersVisible = false;
228             this.visibleView.show(this.viewsContainerElement);
229         }
230
231         // Hide any views that are visible that are not this panel's current visible view.
232         // This can happen when a ResourceView is visible in the Resources panel then switched
233         // to the this panel.
234         for (var sourceID in this._sourceIDMap) {
235             var scriptOrResource = this._sourceIDMap[sourceID];
236             var view = this._sourceViewForScriptOrResource(scriptOrResource);
237             if (!view || view === this.visibleView)
238                 continue;
239             view.visible = false;
240         }
241         if (this._attachDebuggerWhenShown) {
242             InspectorController.enableDebuggerFromFrontend(false);
243             delete this._attachDebuggerWhenShown;
244         }
245     },
246
247     get searchableViews()
248     {
249         var views = [];
250
251         const visibleView = this.visibleView;
252         if (visibleView && visibleView.performSearch) {
253             visibleView.alreadySearching = true;
254             views.push(visibleView);
255         }
256
257         for (var sourceID in this._sourceIDMap) {
258             var scriptOrResource = this._sourceIDMap[sourceID];
259             var view = this._sourceViewForScriptOrResource(scriptOrResource);
260             if (!view || !view.performSearch || view.alreadySearching)
261                 continue;
262
263             view.alreadySearching = true;
264             views.push(view);
265         }
266
267         for (var i = 0; i < views.length; ++i)
268             delete views[i].alreadySearching;
269
270         return views;
271     },
272
273     addScript: function(sourceID, sourceURL, source, startingLine, errorLine, errorMessage)
274     {
275         var script = new WebInspector.Script(sourceID, sourceURL, source, startingLine, errorLine, errorMessage);
276
277         if (sourceURL in WebInspector.resourceURLMap) {
278             var resource = WebInspector.resourceURLMap[sourceURL];
279             resource.addScript(script);
280         }
281
282         if (sourceURL in this._breakpointsURLMap && sourceID) {
283             var breakpoints = this._breakpointsURLMap[sourceURL];
284             var breakpointsLength = breakpoints.length;
285             for (var i = 0; i < breakpointsLength; ++i) {
286                 var breakpoint = breakpoints[i];
287                 if (startingLine <= breakpoint.line) {
288                     breakpoint.sourceID = sourceID;
289                     if (breakpoint.enabled)
290                         InspectorController.addBreakpoint(breakpoint.sourceID, breakpoint.line);
291                 }
292             }
293         }
294
295         if (sourceID)
296             this._sourceIDMap[sourceID] = (resource || script);
297
298         this._addScriptToFilesMenu(script);
299     },
300
301     addBreakpoint: function(breakpoint)
302     {
303         this.sidebarPanes.breakpoints.addBreakpoint(breakpoint);
304
305         var sourceFrame;
306         if (breakpoint.url) {
307             if (!(breakpoint.url in this._breakpointsURLMap))
308                 this._breakpointsURLMap[breakpoint.url] = [];
309             this._breakpointsURLMap[breakpoint.url].unshift(breakpoint);
310
311             if (breakpoint.url in WebInspector.resourceURLMap) {
312                 var resource = WebInspector.resourceURLMap[breakpoint.url];
313                 sourceFrame = this._sourceFrameForScriptOrResource(resource);
314             }
315         }
316
317         if (breakpoint.sourceID && !sourceFrame) {
318             var object = this._sourceIDMap[breakpoint.sourceID]
319             sourceFrame = this._sourceFrameForScriptOrResource(object);
320         }
321
322         if (sourceFrame)
323             sourceFrame.addBreakpoint(breakpoint);
324     },
325
326     removeBreakpoint: function(breakpoint)
327     {
328         this.sidebarPanes.breakpoints.removeBreakpoint(breakpoint);
329
330         var sourceFrame;
331         if (breakpoint.url && breakpoint.url in this._breakpointsURLMap) {
332             var breakpoints = this._breakpointsURLMap[breakpoint.url];
333             breakpoints.remove(breakpoint);
334             if (!breakpoints.length)
335                 delete this._breakpointsURLMap[breakpoint.url];
336
337             if (breakpoint.url in WebInspector.resourceURLMap) {
338                 var resource = WebInspector.resourceURLMap[breakpoint.url];
339                 sourceFrame = this._sourceFrameForScriptOrResource(resource);
340             }
341         }
342
343         if (breakpoint.sourceID && !sourceFrame) {
344             var object = this._sourceIDMap[breakpoint.sourceID]
345             sourceFrame = this._sourceFrameForScriptOrResource(object);
346         }
347
348         if (sourceFrame)
349             sourceFrame.removeBreakpoint(breakpoint);
350     },
351
352     evaluateInSelectedCallFrame: function(code, updateInterface, callback)
353     {
354         var selectedCallFrame = this.sidebarPanes.callstack.selectedCallFrame;
355         if (!this._paused || !selectedCallFrame)
356             return;
357
358         if (typeof updateInterface === "undefined")
359             updateInterface = true;
360
361         var self = this;
362         function updatingCallbackWrapper(result)
363         {
364             callback(result);
365             if (updateInterface)
366                 self.sidebarPanes.scopechain.update(selectedCallFrame);
367         }        
368         this.doEvalInCallFrame(selectedCallFrame, code, updatingCallbackWrapper);
369     },
370
371     doEvalInCallFrame: function(callFrame, code, callback)
372     {
373         var panel = this;
374         function delayedEvaluation()
375         {
376             if (!code) {
377                 // Evaluate into properties in scope of the selected call frame.
378                 callback(panel._variablesInScope(callFrame));
379                 return;
380             }
381             try {
382                 callback(callFrame.evaluate(code));
383             } catch (e) {
384                 callback(e, true);
385             }
386         }
387         setTimeout(delayedEvaluation, 0);
388     },
389
390     _variablesInScope: function(callFrame)
391     {
392         var result = {};
393         var scopeChain = callFrame.scopeChain;
394         for (var i = 0; i < scopeChain.length; ++i) {
395             var scopeObject = scopeChain[i];
396             for (var property in scopeObject)
397                 result[property] = true;
398         }
399         return result;
400     },
401
402     debuggerPaused: function()
403     {
404         this._paused = true;
405         this._waitingToPause = false;
406         this._stepping = false;
407
408         this._updateDebuggerButtons();
409
410         var callStackPane = this.sidebarPanes.callstack;
411         var currentFrame = InspectorController.currentCallFrame();
412         callStackPane.update(currentFrame, this._sourceIDMap);
413         callStackPane.selectedCallFrame = currentFrame;
414
415         WebInspector.currentPanel = this;
416         window.focus();
417     },
418
419     debuggerResumed: function()
420     {
421         this._paused = false;
422         this._waitingToPause = false;
423         this._stepping = false;
424
425         this._clearInterface();
426     },
427
428     attachDebuggerWhenShown: function()
429     {
430         if (this.element.parentElement) {
431             InspectorController.enableDebuggerFromFrontend(false);
432         } else {
433             this._attachDebuggerWhenShown = true;
434         }
435     },
436
437     debuggerWasEnabled: function()
438     {
439         this.reset();
440     },
441
442     debuggerWasDisabled: function()
443     {
444         this.reset();
445     },
446
447     reset: function()
448     {
449         this.visibleView = null;
450
451         delete this.currentQuery;
452         this.searchCanceled();
453
454         if (!InspectorController.debuggerEnabled()) {
455             this._paused = false;
456             this._waitingToPause = false;
457             this._stepping = false;
458         }
459
460         this._clearInterface();
461
462         this._backForwardList = [];
463         this._currentBackForwardIndex = -1;
464         this._updateBackAndForwardButtons();
465
466         this._scriptsForURLsInFilesSelect = {};
467         this.filesSelectElement.removeChildren();
468         this.functionsSelectElement.removeChildren();
469         this.viewsContainerElement.removeChildren();
470
471         if (this._sourceIDMap) {
472             for (var sourceID in this._sourceIDMap) {
473                 var object = this._sourceIDMap[sourceID];
474                 if (object instanceof WebInspector.Resource)
475                     object.removeAllScripts();
476             }
477         }
478
479         this._sourceIDMap = {};
480     },
481
482     get visibleView()
483     {
484         return this._visibleView;
485     },
486
487     set visibleView(x)
488     {
489         if (this._visibleView === x)
490             return;
491
492         if (this._visibleView)
493             this._visibleView.hide();
494
495         this._visibleView = x;
496
497         if (x)
498             x.show(this.viewsContainerElement);
499     },
500
501     canShowResource: function(resource)
502     {
503         return resource && resource.scripts.length && InspectorController.debuggerEnabled();
504     },
505
506     showScript: function(script, line)
507     {
508         this._showScriptOrResource(script, line, true);
509     },
510
511     showResource: function(resource, line)
512     {
513         this._showScriptOrResource(resource, line, true);
514     },
515
516     showView: function(view)
517     {
518         if (!view)
519             return;
520         this._showScriptOrResource((view.resource || view.script));
521     },
522
523     handleKeyEvent: function(event)
524     {
525         var shortcut = WebInspector.KeyboardShortcut.makeKeyFromEvent(event);
526         var handler = this._shortcuts[shortcut];
527         if (handler) {
528             handler(event);
529             event.preventDefault();
530             event.handled = true;
531         } else {
532             this.sidebarPanes.callstack.handleKeyEvent(event);
533         }
534     },
535
536     scriptViewForScript: function(script)
537     {
538         if (!script)
539             return null;
540         if (!script._scriptView)
541             script._scriptView = new WebInspector.ScriptView(script);
542         return script._scriptView;
543     },
544
545     sourceFrameForScript: function(script)
546     {
547         var view = this.scriptViewForScript(script);
548         if (!view)
549             return null;
550
551         // Setting up the source frame requires that we be attached.
552         if (!this.element.parentNode)
553             this.attach();
554
555         view.setupSourceFrameIfNeeded();
556         return view.sourceFrame;
557     },
558
559     _sourceViewForScriptOrResource: function(scriptOrResource)
560     {
561         if (scriptOrResource instanceof WebInspector.Resource) {
562             if (!WebInspector.panels.resources)
563                 return null;
564             return WebInspector.panels.resources.resourceViewForResource(scriptOrResource);
565         }
566         if (scriptOrResource instanceof WebInspector.Script)
567             return this.scriptViewForScript(scriptOrResource);
568     },
569
570     _sourceFrameForScriptOrResource: function(scriptOrResource)
571     {
572         if (scriptOrResource instanceof WebInspector.Resource) {
573             if (!WebInspector.panels.resources)
574                 return null;
575             return WebInspector.panels.resources.sourceFrameForResource(scriptOrResource);
576         }
577         if (scriptOrResource instanceof WebInspector.Script)
578             return this.sourceFrameForScript(scriptOrResource);
579     },
580
581     _showScriptOrResource: function(scriptOrResource, line, shouldHighlightLine, fromBackForwardAction)
582     {
583         if (!scriptOrResource)
584             return;
585
586         var view;
587         if (scriptOrResource instanceof WebInspector.Resource) {
588             if (!WebInspector.panels.resources)
589                 return null;
590             view = WebInspector.panels.resources.resourceViewForResource(scriptOrResource);
591             view.headersVisible = false;
592
593             if (scriptOrResource.url in this._breakpointsURLMap) {
594                 var sourceFrame = this._sourceFrameForScriptOrResource(scriptOrResource);
595                 if (sourceFrame && !sourceFrame.breakpoints.length) {
596                     var breakpoints = this._breakpointsURLMap[scriptOrResource.url];
597                     var breakpointsLength = breakpoints.length;
598                     for (var i = 0; i < breakpointsLength; ++i)
599                         sourceFrame.addBreakpoint(breakpoints[i]);
600                 }
601             }
602         } else if (scriptOrResource instanceof WebInspector.Script)
603             view = this.scriptViewForScript(scriptOrResource);
604
605         if (!view)
606             return;
607
608         if (!fromBackForwardAction) {
609             var oldIndex = this._currentBackForwardIndex;
610             if (oldIndex >= 0)
611                 this._backForwardList.splice(oldIndex + 1, this._backForwardList.length - oldIndex);
612
613             // Check for a previous entry of the same object in _backForwardList.
614             // If one is found, remove it and update _currentBackForwardIndex to match.
615             var previousEntryIndex = this._backForwardList.indexOf(scriptOrResource);
616             if (previousEntryIndex !== -1) {
617                 this._backForwardList.splice(previousEntryIndex, 1);
618                 --this._currentBackForwardIndex;
619             }
620
621             this._backForwardList.push(scriptOrResource);
622             ++this._currentBackForwardIndex;
623
624             this._updateBackAndForwardButtons();
625         }
626
627         this.visibleView = view;
628
629         if (line) {
630             if (view.revealLine)
631                 view.revealLine(line);
632             if (view.highlightLine && shouldHighlightLine)
633                 view.highlightLine(line);
634         }
635
636         var option;
637         if (scriptOrResource instanceof WebInspector.Script) {
638             option = scriptOrResource.filesSelectOption;
639             console.assert(option);
640         } else {
641             var url = scriptOrResource.url;
642             var script = this._scriptsForURLsInFilesSelect[url];
643             if (script)
644                option = script.filesSelectOption;
645         }
646
647         if (option)
648             this.filesSelectElement.selectedIndex = option.index;
649     },
650
651     _addScriptToFilesMenu: function(script)
652     {
653         if (script.resource && this._scriptsForURLsInFilesSelect[script.sourceURL])
654             return;
655
656         this._scriptsForURLsInFilesSelect[script.sourceURL] = script;
657
658         var select = this.filesSelectElement;
659
660         var option = document.createElement("option");
661         option.representedObject = (script.resource || script);
662         option.text = (script.sourceURL ? WebInspector.displayNameForURL(script.sourceURL) : WebInspector.UIString("(program)"));
663
664         function optionCompare(a, b)
665         {
666             var aTitle = a.text.toLowerCase();
667             var bTitle = b.text.toLowerCase();
668             if (aTitle < bTitle)
669                 return -1;
670             else if (aTitle > bTitle)
671                 return 1;
672
673             var aSourceID = a.representedObject.sourceID;
674             var bSourceID = b.representedObject.sourceID;
675             if (aSourceID < bSourceID)
676                 return -1;
677             else if (aSourceID > bSourceID)
678                 return 1;
679             return 0;
680         }
681
682         var insertionIndex = insertionIndexForObjectInListSortedByFunction(option, select.childNodes, optionCompare);
683         if (insertionIndex < 0)
684             select.appendChild(option);
685         else
686             select.insertBefore(option, select.childNodes.item(insertionIndex));
687
688         script.filesSelectOption = option;
689
690         // Call _showScriptOrResource if the option we just appended ended up being selected.
691         // This will happen for the first item added to the menu.
692         if (select.options[select.selectedIndex] === option)
693             this._showScriptOrResource(option.representedObject);
694     },
695
696     _clearCurrentExecutionLine: function()
697     {
698         if (this._executionSourceFrame)
699             this._executionSourceFrame.executionLine = 0;
700         delete this._executionSourceFrame;
701     },
702
703     _callFrameSelected: function()
704     {
705         this._clearCurrentExecutionLine();
706
707         var callStackPane = this.sidebarPanes.callstack;
708         var currentFrame = callStackPane.selectedCallFrame;
709         if (!currentFrame)
710             return;
711
712         this.sidebarPanes.scopechain.update(currentFrame);
713
714         var scriptOrResource = this._sourceIDMap[currentFrame.sourceID];
715         this._showScriptOrResource(scriptOrResource, currentFrame.line);
716
717         this._executionSourceFrame = this._sourceFrameForScriptOrResource(scriptOrResource);
718         if (this._executionSourceFrame)
719             this._executionSourceFrame.executionLine = currentFrame.line;
720     },
721
722     _changeVisibleFile: function(event)
723     {
724         var select = this.filesSelectElement;
725         this._showScriptOrResource(select.options[select.selectedIndex].representedObject);
726     },
727
728     _startSidebarResizeDrag: function(event)
729     {
730         WebInspector.elementDragStart(this.sidebarElement, this._sidebarResizeDrag.bind(this), this._endSidebarResizeDrag.bind(this), event, "col-resize");
731
732         if (event.target === this.sidebarResizeWidgetElement)
733             this._dragOffset = (event.target.offsetWidth - (event.pageX - event.target.totalOffsetLeft));
734         else
735             this._dragOffset = 0;
736     },
737
738     _endSidebarResizeDrag: function(event)
739     {
740         WebInspector.elementDragEnd(event);
741
742         delete this._dragOffset;
743     },
744
745     _sidebarResizeDrag: function(event)
746     {
747         var x = event.pageX + this._dragOffset;
748         var newWidth = Number.constrain(window.innerWidth - x, Preferences.minScriptsSidebarWidth, window.innerWidth * 0.66);
749
750         this.sidebarElement.style.width = newWidth + "px";
751         this.sidebarButtonsElement.style.width = newWidth + "px";
752         this.viewsContainerElement.style.right = newWidth + "px";
753         this.sidebarResizeWidgetElement.style.right = newWidth + "px";
754         this.sidebarResizeElement.style.right = (newWidth - 3) + "px";
755
756         event.preventDefault();
757     },
758
759     _updatePauseOnExceptionsButton: function()
760     {
761         if (InspectorController.pauseOnExceptions()) {
762             this.pauseOnExceptionButton.title = WebInspector.UIString("Don't pause on exceptions.");
763             this.pauseOnExceptionButton.addStyleClass("toggled-on");
764         } else {
765             this.pauseOnExceptionButton.title = WebInspector.UIString("Pause on exceptions.");
766             this.pauseOnExceptionButton.removeStyleClass("toggled-on");
767         }
768     },
769
770     _updateDebuggerButtons: function()
771     {
772         if (InspectorController.debuggerEnabled()) {
773             this.enableToggleButton.title = WebInspector.UIString("Debugging enabled. Click to disable.");
774             this.enableToggleButton.addStyleClass("toggled-on");
775             this.pauseOnExceptionButton.removeStyleClass("hidden");
776             this.panelEnablerView.visible = false;
777         } else {
778             this.enableToggleButton.title = WebInspector.UIString("Debugging disabled. Click to enable.");
779             this.enableToggleButton.removeStyleClass("toggled-on");
780             this.pauseOnExceptionButton.addStyleClass("hidden");
781             this.panelEnablerView.visible = true;
782         }
783
784         this._updatePauseOnExceptionsButton();
785
786         if (this._paused) {
787             this.pauseButton.addStyleClass("paused");
788
789             this.pauseButton.disabled = false;
790             this.stepOverButton.disabled = false;
791             this.stepIntoButton.disabled = false;
792             this.stepOutButton.disabled = false;
793
794             this.debuggerStatusElement.textContent = WebInspector.UIString("Paused");
795         } else {
796             this.pauseButton.removeStyleClass("paused");
797
798             this.pauseButton.disabled = this._waitingToPause;
799             this.stepOverButton.disabled = true;
800             this.stepIntoButton.disabled = true;
801             this.stepOutButton.disabled = true;
802
803             if (this._waitingToPause)
804                 this.debuggerStatusElement.textContent = WebInspector.UIString("Pausing");
805             else if (this._stepping)
806                 this.debuggerStatusElement.textContent = WebInspector.UIString("Stepping");
807             else
808                 this.debuggerStatusElement.textContent = "";
809         }
810     },
811
812     _updateBackAndForwardButtons: function()
813     {
814         this.backButton.disabled = this._currentBackForwardIndex <= 0;
815         this.forwardButton.disabled = this._currentBackForwardIndex >= (this._backForwardList.length - 1);
816     },
817
818     _clearInterface: function()
819     {
820         this.sidebarPanes.callstack.update(null);
821         this.sidebarPanes.scopechain.update(null);
822
823         this._clearCurrentExecutionLine();
824         this._updateDebuggerButtons();
825     },
826
827     _goBack: function()
828     {
829         if (this._currentBackForwardIndex <= 0) {
830             console.error("Can't go back from index " + this._currentBackForwardIndex);
831             return;
832         }
833
834         this._showScriptOrResource(this._backForwardList[--this._currentBackForwardIndex], null, false, true);
835         this._updateBackAndForwardButtons();
836     },
837
838     _goForward: function()
839     {
840         if (this._currentBackForwardIndex >= this._backForwardList.length - 1) {
841             console.error("Can't go forward from index " + this._currentBackForwardIndex);
842             return;
843         }
844
845         this._showScriptOrResource(this._backForwardList[++this._currentBackForwardIndex], null, false, true);
846         this._updateBackAndForwardButtons();
847     },
848
849     _enableDebugging: function()
850     {
851         if (InspectorController.debuggerEnabled())
852             return;
853         this._toggleDebugging(this.panelEnablerView.alwaysEnabled);
854     },
855
856     _toggleDebugging: function(optionalAlways)
857     {
858         this._paused = false;
859         this._waitingToPause = false;
860         this._stepping = false;
861
862         if (InspectorController.debuggerEnabled())
863             InspectorController.disableDebugger(true);
864         else
865             InspectorController.enableDebuggerFromFrontend(!!optionalAlways);
866     },
867
868     _togglePauseOnExceptions: function()
869     {
870         InspectorController.setPauseOnExceptions(!InspectorController.pauseOnExceptions());
871         this._updatePauseOnExceptionsButton();
872     },
873
874     _togglePause: function()
875     {
876         if (this._paused) {
877             this._paused = false;
878             this._waitingToPause = false;
879             InspectorController.resumeDebugger();
880         } else {
881             this._stepping = false;
882             this._waitingToPause = true;
883             InspectorController.pauseInDebugger();
884         }
885
886         this._clearInterface();
887     },
888
889     _stepOverClicked: function()
890     {
891         this._paused = false;
892         this._stepping = true;
893
894         this._clearInterface();
895
896         InspectorController.stepOverStatementInDebugger();
897     },
898
899     _stepIntoClicked: function()
900     {
901         this._paused = false;
902         this._stepping = true;
903
904         this._clearInterface();
905
906         InspectorController.stepIntoStatementInDebugger();
907     },
908
909     _stepOutClicked: function()
910     {
911         this._paused = false;
912         this._stepping = true;
913
914         this._clearInterface();
915
916         InspectorController.stepOutOfFunctionInDebugger();
917     }
918 }
919
920 WebInspector.ScriptsPanel.prototype.__proto__ = WebInspector.Panel.prototype;