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