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