OSDN Git Service

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