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