• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
26WebInspector.ScriptsPanel = function()
27{
28    WebInspector.Panel.call(this, "scripts");
29
30    this._presentationModel = new WebInspector.DebuggerPresentationModel();
31
32    this.topStatusBar = document.createElement("div");
33    this.topStatusBar.className = "status-bar";
34    this.topStatusBar.id = "scripts-status-bar";
35    this.element.appendChild(this.topStatusBar);
36
37    this.backButton = document.createElement("button");
38    this.backButton.className = "status-bar-item";
39    this.backButton.id = "scripts-back";
40    this.backButton.title = WebInspector.UIString("Show the previous script resource.");
41    this.backButton.disabled = true;
42    this.backButton.appendChild(document.createElement("img"));
43    this.backButton.addEventListener("click", this._goBack.bind(this), false);
44    this.topStatusBar.appendChild(this.backButton);
45
46    this.forwardButton = document.createElement("button");
47    this.forwardButton.className = "status-bar-item";
48    this.forwardButton.id = "scripts-forward";
49    this.forwardButton.title = WebInspector.UIString("Show the next script resource.");
50    this.forwardButton.disabled = true;
51    this.forwardButton.appendChild(document.createElement("img"));
52    this.forwardButton.addEventListener("click", this._goForward.bind(this), false);
53    this.topStatusBar.appendChild(this.forwardButton);
54
55    this._filesSelectElement = document.createElement("select");
56    this._filesSelectElement.className = "status-bar-item";
57    this._filesSelectElement.id = "scripts-files";
58    this._filesSelectElement.addEventListener("change", this._filesSelectChanged.bind(this), false);
59    this.topStatusBar.appendChild(this._filesSelectElement);
60
61    this.functionsSelectElement = document.createElement("select");
62    this.functionsSelectElement.className = "status-bar-item";
63    this.functionsSelectElement.id = "scripts-functions";
64
65    // FIXME: append the functions select element to the top status bar when it is implemented.
66    // this.topStatusBar.appendChild(this.functionsSelectElement);
67
68    this.sidebarButtonsElement = document.createElement("div");
69    this.sidebarButtonsElement.id = "scripts-sidebar-buttons";
70    this.topStatusBar.appendChild(this.sidebarButtonsElement);
71
72    this.pauseButton = document.createElement("button");
73    this.pauseButton.className = "status-bar-item";
74    this.pauseButton.id = "scripts-pause";
75    this.pauseButton.title = WebInspector.UIString("Pause script execution.");
76    this.pauseButton.disabled = true;
77    this.pauseButton.appendChild(document.createElement("img"));
78    this.pauseButton.addEventListener("click", this._togglePause.bind(this), false);
79    this.sidebarButtonsElement.appendChild(this.pauseButton);
80
81    this.stepOverButton = document.createElement("button");
82    this.stepOverButton.className = "status-bar-item";
83    this.stepOverButton.id = "scripts-step-over";
84    this.stepOverButton.title = WebInspector.UIString("Step over next function call.");
85    this.stepOverButton.disabled = true;
86    this.stepOverButton.addEventListener("click", this._stepOverClicked.bind(this), false);
87    this.stepOverButton.appendChild(document.createElement("img"));
88    this.sidebarButtonsElement.appendChild(this.stepOverButton);
89
90    this.stepIntoButton = document.createElement("button");
91    this.stepIntoButton.className = "status-bar-item";
92    this.stepIntoButton.id = "scripts-step-into";
93    this.stepIntoButton.title = WebInspector.UIString("Step into next function call.");
94    this.stepIntoButton.disabled = true;
95    this.stepIntoButton.addEventListener("click", this._stepIntoClicked.bind(this), false);
96    this.stepIntoButton.appendChild(document.createElement("img"));
97    this.sidebarButtonsElement.appendChild(this.stepIntoButton);
98
99    this.stepOutButton = document.createElement("button");
100    this.stepOutButton.className = "status-bar-item";
101    this.stepOutButton.id = "scripts-step-out";
102    this.stepOutButton.title = WebInspector.UIString("Step out of current function.");
103    this.stepOutButton.disabled = true;
104    this.stepOutButton.addEventListener("click", this._stepOutClicked.bind(this), false);
105    this.stepOutButton.appendChild(document.createElement("img"));
106    this.sidebarButtonsElement.appendChild(this.stepOutButton);
107
108    this.toggleBreakpointsButton = new WebInspector.StatusBarButton(WebInspector.UIString("Deactivate all breakpoints."), "toggle-breakpoints");
109    this.toggleBreakpointsButton.toggled = true;
110    this.toggleBreakpointsButton.addEventListener("click", this.toggleBreakpointsClicked.bind(this), false);
111    this.sidebarButtonsElement.appendChild(this.toggleBreakpointsButton.element);
112
113    this.debuggerStatusElement = document.createElement("div");
114    this.debuggerStatusElement.id = "scripts-debugger-status";
115    this.sidebarButtonsElement.appendChild(this.debuggerStatusElement);
116
117    this.viewsContainerElement = document.createElement("div");
118    this.viewsContainerElement.id = "script-resource-views";
119
120    this.sidebarElement = document.createElement("div");
121    this.sidebarElement.id = "scripts-sidebar";
122
123    this.sidebarResizeElement = document.createElement("div");
124    this.sidebarResizeElement.className = "sidebar-resizer-vertical";
125    this.sidebarResizeElement.addEventListener("mousedown", this._startSidebarResizeDrag.bind(this), false);
126
127    this.sidebarResizeWidgetElement = document.createElement("div");
128    this.sidebarResizeWidgetElement.id = "scripts-sidebar-resizer-widget";
129    this.sidebarResizeWidgetElement.addEventListener("mousedown", this._startSidebarResizeDrag.bind(this), false);
130    this.topStatusBar.appendChild(this.sidebarResizeWidgetElement);
131
132    this.sidebarPanes = {};
133    this.sidebarPanes.watchExpressions = new WebInspector.WatchExpressionsSidebarPane();
134    this.sidebarPanes.callstack = new WebInspector.CallStackSidebarPane(this._presentationModel);
135    this.sidebarPanes.scopechain = new WebInspector.ScopeChainSidebarPane();
136    this.sidebarPanes.jsBreakpoints = new WebInspector.JavaScriptBreakpointsSidebarPane(this._presentationModel, this._showSourceLine.bind(this));
137    if (Preferences.nativeInstrumentationEnabled) {
138        this.sidebarPanes.domBreakpoints = WebInspector.domBreakpointsSidebarPane;
139        this.sidebarPanes.xhrBreakpoints = new WebInspector.XHRBreakpointsSidebarPane();
140        this.sidebarPanes.eventListenerBreakpoints = new WebInspector.EventListenerBreakpointsSidebarPane();
141    }
142
143    this.sidebarPanes.workers = new WebInspector.WorkersSidebarPane();
144
145    for (var pane in this.sidebarPanes)
146        this.sidebarElement.appendChild(this.sidebarPanes[pane].element);
147
148    this.sidebarPanes.callstack.expanded = true;
149
150    this.sidebarPanes.scopechain.expanded = true;
151    this.sidebarPanes.jsBreakpoints.expanded = true;
152
153    var panelEnablerHeading = WebInspector.UIString("You need to enable debugging before you can use the Scripts panel.");
154    var panelEnablerDisclaimer = WebInspector.UIString("Enabling debugging will make scripts run slower.");
155    var panelEnablerButton = WebInspector.UIString("Enable Debugging");
156
157    this.panelEnablerView = new WebInspector.PanelEnablerView("scripts", panelEnablerHeading, panelEnablerDisclaimer, panelEnablerButton);
158    this.panelEnablerView.addEventListener("enable clicked", this._enableDebugging, this);
159
160    this.element.appendChild(this.panelEnablerView.element);
161    this.element.appendChild(this.viewsContainerElement);
162    this.element.appendChild(this.sidebarElement);
163    this.element.appendChild(this.sidebarResizeElement);
164
165    this.enableToggleButton = new WebInspector.StatusBarButton("", "enable-toggle-status-bar-item");
166    this.enableToggleButton.addEventListener("click", this._toggleDebugging.bind(this), false);
167    if (Preferences.debuggerAlwaysEnabled)
168        this.enableToggleButton.element.addStyleClass("hidden");
169
170    this._pauseOnExceptionButton = new WebInspector.StatusBarButton("", "scripts-pause-on-exceptions-status-bar-item", 3);
171    this._pauseOnExceptionButton.addEventListener("click", this._togglePauseOnExceptions.bind(this), false);
172
173    this._registerShortcuts();
174
175    this._debuggerEnabled = Preferences.debuggerAlwaysEnabled;
176
177    this.reset();
178
179    WebInspector.debuggerModel.addEventListener(WebInspector.DebuggerModel.Events.DebuggerWasEnabled, this._debuggerWasEnabled, this);
180    WebInspector.debuggerModel.addEventListener(WebInspector.DebuggerModel.Events.DebuggerWasDisabled, this._debuggerWasDisabled, this);
181
182    this._presentationModel.addEventListener(WebInspector.DebuggerPresentationModel.Events.SourceFileAdded, this._sourceFileAdded, this)
183    this._presentationModel.addEventListener(WebInspector.DebuggerPresentationModel.Events.SourceFileChanged, this._sourceFileChanged, this);
184    this._presentationModel.addEventListener(WebInspector.DebuggerPresentationModel.Events.ConsoleMessageAdded, this._consoleMessageAdded, this);
185    this._presentationModel.addEventListener(WebInspector.DebuggerPresentationModel.Events.BreakpointAdded, this._breakpointAdded, this);
186    this._presentationModel.addEventListener(WebInspector.DebuggerPresentationModel.Events.BreakpointRemoved, this._breakpointRemoved, this);
187    this._presentationModel.addEventListener(WebInspector.DebuggerPresentationModel.Events.DebuggerPaused, this._debuggerPaused, this);
188    this._presentationModel.addEventListener(WebInspector.DebuggerPresentationModel.Events.DebuggerResumed, this._debuggerResumed, this);
189    this._presentationModel.addEventListener(WebInspector.DebuggerPresentationModel.Events.CallFrameSelected, this._callFrameSelected, this);
190
191    var enableDebugger = Preferences.debuggerAlwaysEnabled || WebInspector.settings.debuggerEnabled;
192    if (enableDebugger || InspectorFrontendHost.loadSessionSetting("debugger-enabled") === "true")
193        WebInspector.debuggerModel.enableDebugger();
194}
195
196// Keep these in sync with WebCore::ScriptDebugServer
197WebInspector.ScriptsPanel.PauseOnExceptionsState = {
198    DontPauseOnExceptions : "none",
199    PauseOnAllExceptions : "all",
200    PauseOnUncaughtExceptions: "uncaught"
201};
202
203WebInspector.ScriptsPanel.BrowserBreakpointTypes = {
204    DOM: "DOM",
205    EventListener: "EventListener",
206    XHR: "XHR"
207}
208
209WebInspector.ScriptsPanel.prototype = {
210    get toolbarItemLabel()
211    {
212        return WebInspector.UIString("Scripts");
213    },
214
215    get statusBarItems()
216    {
217        return [this.enableToggleButton.element, this._pauseOnExceptionButton.element];
218    },
219
220    get defaultFocusedElement()
221    {
222        return this._filesSelectElement;
223    },
224
225    get paused()
226    {
227        return this._paused;
228    },
229
230    show: function()
231    {
232        WebInspector.Panel.prototype.show.call(this);
233        this.sidebarResizeElement.style.right = (this.sidebarElement.offsetWidth - 3) + "px";
234        if (Preferences.nativeInstrumentationEnabled)
235            this.sidebarElement.insertBefore(this.sidebarPanes.domBreakpoints.element, this.sidebarPanes.xhrBreakpoints.element);
236
237        if (this.visibleView)
238            this.visibleView.show(this.viewsContainerElement);
239    },
240
241    hide: function()
242    {
243        if (this.visibleView)
244            this.visibleView.hide();
245        WebInspector.Panel.prototype.hide.call(this);
246    },
247
248    get breakpointsActivated()
249    {
250        return this.toggleBreakpointsButton.toggled;
251    },
252
253    _sourceFileAdded: function(event)
254    {
255        var sourceFile = event.data;
256
257        if (!sourceFile.url) {
258            // Anonymous sources are shown only when stepping.
259            return;
260        }
261
262        this._addOptionToFilesSelect(sourceFile.id);
263
264        var lastViewedURL = WebInspector.settings.lastViewedScriptFile;
265        if (this._filesSelectElement.length === 1) {
266            // Option we just added is the only option in files select.
267            // We have to show corresponding source frame immediately.
268            this._showSourceFrameAndAddToHistory(sourceFile.id);
269            // Restore original value of lastViewedScriptFile because
270            // source frame was shown as a result of initial load.
271            WebInspector.settings.lastViewedScriptFile = lastViewedURL;
272        } else if (sourceFile.url === lastViewedURL)
273            this._showSourceFrameAndAddToHistory(sourceFile.id);
274    },
275
276    _addOptionToFilesSelect: function(sourceFileId)
277    {
278        var sourceFile = this._presentationModel.sourceFile(sourceFileId);
279        var select = this._filesSelectElement;
280        var option = document.createElement("option");
281        option.text = sourceFile.url ? WebInspector.displayNameForURL(sourceFile.url) : WebInspector.UIString("(program)");
282        if (sourceFile.isContentScript)
283            option.addStyleClass("extension-script");
284        function optionCompare(a, b)
285        {
286            if (a.text === b.text)
287                return 0;
288            return a.text < b.text ? -1 : 1;
289        }
290        var insertionIndex = insertionIndexForObjectInListSortedByFunction(option, select.childNodes, optionCompare);
291        if (insertionIndex < 0)
292            select.appendChild(option);
293        else
294            select.insertBefore(option, select.childNodes.item(insertionIndex));
295
296        option._sourceFileId = sourceFileId;
297        this._sourceFileIdToFilesSelectOption[sourceFileId] = option;
298    },
299
300    setScriptSourceIsBeingEdited: function(sourceFileId, inEditMode)
301    {
302        var option = this._sourceFileIdToFilesSelectOption[sourceFileId];
303        if (!option)
304            return;
305        if (inEditMode)
306            option.text = option.text.replace(/[^*]$/, "$&*");
307        else
308            option.text = option.text.replace(/[*]$/, "");
309    },
310
311    addConsoleMessage: function(message)
312    {
313        if (message.isErrorOrWarning() && message.message)
314            this._presentationModel.addConsoleMessage(message);
315    },
316
317    clearConsoleMessages: function()
318    {
319        this._presentationModel.clearConsoleMessages();
320        for (var sourceFileId in this._sourceFileIdToSourceFrame)
321            this._sourceFileIdToSourceFrame[sourceFileId].clearMessages();
322    },
323
324    _consoleMessageAdded: function(event)
325    {
326        var message = event.data;
327
328        var sourceFrame = this._sourceFileIdToSourceFrame[message.sourceFileId];
329        if (sourceFrame && sourceFrame.loaded)
330            sourceFrame.addMessageToSource(message.lineNumber, message.originalMessage);
331    },
332
333    _breakpointAdded: function(event)
334    {
335        var breakpoint = event.data;
336
337        var sourceFrame = this._sourceFileIdToSourceFrame[breakpoint.sourceFileId];
338        if (sourceFrame && sourceFrame.loaded)
339            sourceFrame.addBreakpoint(breakpoint.lineNumber, breakpoint.resolved, breakpoint.condition, breakpoint.enabled);
340
341        this.sidebarPanes.jsBreakpoints.addBreakpoint(breakpoint);
342    },
343
344    _breakpointRemoved: function(event)
345    {
346        var breakpoint = event.data;
347
348        var sourceFrame = this._sourceFileIdToSourceFrame[breakpoint.sourceFileId];
349        if (sourceFrame && sourceFrame.loaded)
350            sourceFrame.removeBreakpoint(breakpoint.lineNumber);
351
352        this.sidebarPanes.jsBreakpoints.removeBreakpoint(breakpoint.sourceFileId, breakpoint.lineNumber);
353    },
354
355    evaluateInSelectedCallFrame: function(code, objectGroup, includeCommandLineAPI, callback)
356    {
357        var selectedCallFrame = this._presentationModel.selectedCallFrame;
358        selectedCallFrame.evaluate(code, objectGroup, includeCommandLineAPI, callback);
359    },
360
361    getSelectedCallFrameVariables: function(callback)
362    {
363        var result = { this: true };
364
365        var selectedCallFrame = this._presentationModel.selectedCallFrame;
366        if (!selectedCallFrame)
367            callback(result);
368
369        var pendingRequests = 0;
370
371        function propertiesCollected(properties)
372        {
373            for (var i = 0; properties && i < properties.length; ++i)
374                result[properties[i].name] = true;
375            if (--pendingRequests == 0)
376                callback(result);
377        }
378
379        for (var i = 0; i < selectedCallFrame.scopeChain.length; ++i) {
380            var scope = selectedCallFrame.scopeChain[i];
381            var object = WebInspector.RemoteObject.fromPayload(scope.object);
382            pendingRequests++;
383            object.getAllProperties(propertiesCollected);
384        }
385    },
386
387    _debuggerPaused: function(event)
388    {
389        var callFrames = event.data.callFrames;
390        var details = event.data.details;
391
392        this._paused = true;
393        this._waitingToPause = false;
394        this._stepping = false;
395
396        this._updateDebuggerButtons();
397
398        WebInspector.currentPanel = this;
399
400        this.sidebarPanes.callstack.update(callFrames, details);
401        this.sidebarPanes.callstack.selectedCallFrame = this._presentationModel.selectedCallFrame;
402
403        if (details.eventType === WebInspector.DebuggerEventTypes.NativeBreakpoint) {
404            if (details.eventData.breakpointType === WebInspector.ScriptsPanel.BrowserBreakpointTypes.DOM) {
405                this.sidebarPanes.domBreakpoints.highlightBreakpoint(details.eventData);
406                function didCreateBreakpointHitStatusMessage(element)
407                {
408                    this.sidebarPanes.callstack.setStatus(element);
409                }
410                this.sidebarPanes.domBreakpoints.createBreakpointHitStatusMessage(details.eventData, didCreateBreakpointHitStatusMessage.bind(this));
411            } else if (details.eventData.breakpointType === WebInspector.ScriptsPanel.BrowserBreakpointTypes.EventListener) {
412                var eventName = details.eventData.eventName;
413                this.sidebarPanes.eventListenerBreakpoints.highlightBreakpoint(details.eventData.eventName);
414                var eventNameForUI = WebInspector.EventListenerBreakpointsSidebarPane.eventNameForUI(eventName);
415                this.sidebarPanes.callstack.setStatus(WebInspector.UIString("Paused on a \"%s\" Event Listener.", eventNameForUI));
416            } else if (details.eventData.breakpointType === WebInspector.ScriptsPanel.BrowserBreakpointTypes.XHR) {
417                this.sidebarPanes.xhrBreakpoints.highlightBreakpoint(details.eventData.breakpointURL);
418                this.sidebarPanes.callstack.setStatus(WebInspector.UIString("Paused on a XMLHttpRequest."));
419            }
420        } else {
421            function didGetSourceLocation(sourceFileId, lineNumber)
422            {
423                if (!sourceFileId || !this._presentationModel.findBreakpoint(sourceFileId, lineNumber))
424                    return;
425                this.sidebarPanes.jsBreakpoints.highlightBreakpoint(sourceFileId, lineNumber);
426                this.sidebarPanes.callstack.setStatus(WebInspector.UIString("Paused on a JavaScript breakpoint."));
427            }
428            callFrames[0].sourceLine(didGetSourceLocation.bind(this));
429        }
430
431        window.focus();
432        InspectorFrontendHost.bringToFront();
433    },
434
435    _debuggerResumed: function()
436    {
437        this._paused = false;
438        this._waitingToPause = false;
439        this._stepping = false;
440
441        this._clearInterface();
442    },
443
444    _debuggerWasEnabled: function()
445    {
446        this._setPauseOnExceptions(WebInspector.settings.pauseOnExceptionStateString);
447
448        if (this._debuggerEnabled)
449            return;
450
451        InspectorFrontendHost.saveSessionSetting("debugger-enabled", "true");
452        this._debuggerEnabled = true;
453        this.reset(true);
454    },
455
456    _debuggerWasDisabled: function()
457    {
458        if (!this._debuggerEnabled)
459            return;
460
461        InspectorFrontendHost.saveSessionSetting("debugger-enabled", "false");
462        this._debuggerEnabled = false;
463        this.reset(true);
464    },
465
466    reset: function(preserveItems)
467    {
468        this.visibleView = null;
469
470        delete this.currentQuery;
471        this.searchCanceled();
472
473        this._debuggerResumed();
474
475        this._backForwardList = [];
476        this._currentBackForwardIndex = -1;
477        this._updateBackAndForwardButtons();
478
479        this._sourceFileIdToSourceFrame = {};
480        this._sourceFileIdToFilesSelectOption = {};
481        this._filesSelectElement.removeChildren();
482        this.functionsSelectElement.removeChildren();
483        this.viewsContainerElement.removeChildren();
484
485        this.sidebarPanes.jsBreakpoints.reset();
486        this.sidebarPanes.watchExpressions.refreshExpressions();
487        if (!preserveItems)
488            this.sidebarPanes.workers.reset();
489    },
490
491    get visibleView()
492    {
493        return this._visibleView;
494    },
495
496    set visibleView(x)
497    {
498        if (this._visibleView === x)
499            return;
500
501        if (this._visibleView)
502            this._visibleView.hide();
503
504        this._visibleView = x;
505
506        if (x)
507            x.show(this.viewsContainerElement);
508    },
509
510    canShowAnchorLocation: function(anchor)
511    {
512        return this._debuggerEnabled && this._presentationModel.sourceFileForScriptURL(anchor.href);
513    },
514
515    showAnchorLocation: function(anchor)
516    {
517        function didRequestSourceMapping(mapping)
518        {
519            var lineNumber = mapping.scriptLocationToSourceLine({lineNumber:anchor.getAttribute("line_number") - 1, columnNumber:0});
520            this._showSourceLine(sourceFile.id, lineNumber);
521        }
522        var sourceFile = this._presentationModel.sourceFileForScriptURL(anchor.href);
523        sourceFile.requestSourceMapping(didRequestSourceMapping.bind(this));
524    },
525
526    _showSourceLine: function(sourceFileId, lineNumber)
527    {
528        var sourceFrame = this._showSourceFrameAndAddToHistory(sourceFileId);
529        sourceFrame.highlightLine(lineNumber);
530    },
531
532    handleShortcut: function(event)
533    {
534        var shortcut = WebInspector.KeyboardShortcut.makeKeyFromEvent(event);
535        var handler = this._shortcuts[shortcut];
536        if (handler) {
537            handler(event);
538            event.handled = true;
539        } else
540            this.sidebarPanes.callstack.handleShortcut(event);
541    },
542
543    _showSourceFrameAndAddToHistory: function(sourceFileId)
544    {
545        var sourceFrame = this._showSourceFrame(sourceFileId);
546
547        var oldIndex = this._currentBackForwardIndex;
548        if (oldIndex >= 0)
549            this._backForwardList.splice(oldIndex + 1, this._backForwardList.length - oldIndex);
550
551        // Check for a previous entry of the same object in _backForwardList.
552        // If one is found, remove it.
553        var previousEntryIndex = this._backForwardList.indexOf(sourceFileId);
554        if (previousEntryIndex !== -1)
555            this._backForwardList.splice(previousEntryIndex, 1);
556
557        this._backForwardList.push(sourceFileId);
558        this._currentBackForwardIndex = this._backForwardList.length - 1;
559
560        this._updateBackAndForwardButtons();
561
562        return sourceFrame;
563    },
564
565    _showSourceFrame: function(sourceFileId)
566    {
567        var index = this._sourceFileIdToFilesSelectOption[sourceFileId].index;
568        this._filesSelectElement.selectedIndex = index;
569
570        var sourceFrame = this._sourceFrameForSourceFileId(sourceFileId);
571        this.visibleView = sourceFrame;
572
573        var sourceFile = this._presentationModel.sourceFile(sourceFileId);
574        if (sourceFile.url)
575            WebInspector.settings.lastViewedScriptFile = sourceFile.url;
576
577        return sourceFrame;
578    },
579
580    _sourceFrameForSourceFileId: function(sourceFileId)
581    {
582        var sourceFrame = this._sourceFileIdToSourceFrame[sourceFileId];
583        return sourceFrame || this._createSourceFrame(sourceFileId);
584    },
585
586    _createSourceFrame: function(sourceFileId)
587    {
588        var sourceFile = this._presentationModel.sourceFile(sourceFileId);
589        var delegate = new WebInspector.SourceFrameDelegateForScriptsPanel(this._presentationModel, sourceFileId);
590        var sourceFrame = new WebInspector.SourceFrame(delegate, sourceFile.url);
591        sourceFrame._sourceFileId = sourceFileId;
592        sourceFrame.addEventListener(WebInspector.SourceFrame.Events.Loaded, this._sourceFrameLoaded, this);
593        this._sourceFileIdToSourceFrame[sourceFileId] = sourceFrame;
594        return sourceFrame;
595    },
596
597    _sourceFileChanged: function(event)
598    {
599        var sourceFileId = event.data.id;
600
601        var oldSourceFrame = this._sourceFileIdToSourceFrame[sourceFileId];
602        if (!oldSourceFrame)
603            return;
604        oldSourceFrame.removeEventListener(WebInspector.SourceFrame.Events.Loaded, this._sourceFrameLoaded, this);
605        delete this._sourceFileIdToSourceFrame[sourceFileId];
606        if (this.visibleView !== oldSourceFrame)
607            return;
608
609        var newSourceFrame = this._createSourceFrame(sourceFileId)
610        newSourceFrame.scrollTop = oldSourceFrame.scrollTop;
611        this.visibleView = newSourceFrame;
612    },
613
614    _sourceFrameLoaded: function(event)
615    {
616        var sourceFrame = event.target;
617        var sourceFileId = sourceFrame._sourceFileId;
618        var sourceFile = this._presentationModel.sourceFile(sourceFileId);
619
620        var messages = sourceFile.messages;
621        for (var i = 0; i < messages.length; ++i) {
622            var message = messages[i];
623            sourceFrame.addMessageToSource(message.lineNumber, message.originalMessage);
624        }
625
626        var breakpoints = this._presentationModel.breakpointsForSourceFileId(sourceFileId);
627        for (var i = 0; i < breakpoints.length; ++i) {
628            var breakpoint = breakpoints[i];
629            sourceFrame.addBreakpoint(breakpoint.lineNumber, breakpoint.resolved, breakpoint.condition, breakpoint.enabled);
630        }
631    },
632
633    _clearCurrentExecutionLine: function()
634    {
635        if (this._executionSourceFrame)
636            this._executionSourceFrame.clearExecutionLine();
637        delete this._executionSourceFrame;
638    },
639
640    _callFrameSelected: function(event)
641    {
642        var callFrame = event.data;
643
644        this._clearCurrentExecutionLine();
645
646        if (!callFrame)
647            return;
648
649        this.sidebarPanes.scopechain.update(callFrame);
650        this.sidebarPanes.watchExpressions.refreshExpressions();
651        this.sidebarPanes.callstack.selectedCallFrame = this._presentationModel.selectedCallFrame;
652
653        function didGetSourceLocation(sourceFileId, lineNumber)
654        {
655            if (!sourceFileId)
656                return;
657
658            if (!(sourceFileId in this._sourceFileIdToFilesSelectOption)) {
659                // Anonymous scripts are not added to files select by default.
660                this._addOptionToFilesSelect(sourceFileId);
661            }
662            var sourceFrame = this._showSourceFrameAndAddToHistory(sourceFileId);
663            sourceFrame.setExecutionLine(lineNumber);
664            this._executionSourceFrame = sourceFrame;
665        }
666        callFrame.sourceLine(didGetSourceLocation.bind(this));
667    },
668
669    _filesSelectChanged: function()
670    {
671        var sourceFileId = this._filesSelectElement[this._filesSelectElement.selectedIndex]._sourceFileId;
672        this._showSourceFrameAndAddToHistory(sourceFileId);
673    },
674
675    _startSidebarResizeDrag: function(event)
676    {
677        WebInspector.elementDragStart(this.sidebarElement, this._sidebarResizeDrag.bind(this), this._endSidebarResizeDrag.bind(this), event, "col-resize");
678
679        if (event.target === this.sidebarResizeWidgetElement)
680            this._dragOffset = (event.target.offsetWidth - (event.pageX - event.target.totalOffsetLeft));
681        else
682            this._dragOffset = 0;
683    },
684
685    _endSidebarResizeDrag: function(event)
686    {
687        WebInspector.elementDragEnd(event);
688        delete this._dragOffset;
689        this.saveSidebarWidth();
690    },
691
692    _sidebarResizeDrag: function(event)
693    {
694        var x = event.pageX + this._dragOffset;
695        var newWidth = Number.constrain(window.innerWidth - x, Preferences.minScriptsSidebarWidth, window.innerWidth * 0.66);
696        this.setSidebarWidth(newWidth);
697        event.preventDefault();
698    },
699
700    setSidebarWidth: function(newWidth)
701    {
702        this.sidebarElement.style.width = newWidth + "px";
703        this.sidebarButtonsElement.style.width = newWidth + "px";
704        this.viewsContainerElement.style.right = newWidth + "px";
705        this.sidebarResizeWidgetElement.style.right = newWidth + "px";
706        this.sidebarResizeElement.style.right = (newWidth - 3) + "px";
707
708        this.resize();
709    },
710
711    _setPauseOnExceptions: function(pauseOnExceptionsState)
712    {
713        pauseOnExceptionsState = pauseOnExceptionsState || WebInspector.ScriptsPanel.PauseOnExceptionsState.DontPauseOnExceptions;
714        function callback(error)
715        {
716            if (error)
717                return;
718            if (pauseOnExceptionsState == WebInspector.ScriptsPanel.PauseOnExceptionsState.DontPauseOnExceptions)
719                this._pauseOnExceptionButton.title = WebInspector.UIString("Don't pause on exceptions.\nClick to Pause on all exceptions.");
720            else if (pauseOnExceptionsState == WebInspector.ScriptsPanel.PauseOnExceptionsState.PauseOnAllExceptions)
721                this._pauseOnExceptionButton.title = WebInspector.UIString("Pause on all exceptions.\nClick to Pause on uncaught exceptions.");
722            else if (pauseOnExceptionsState == WebInspector.ScriptsPanel.PauseOnExceptionsState.PauseOnUncaughtExceptions)
723                this._pauseOnExceptionButton.title = WebInspector.UIString("Pause on uncaught exceptions.\nClick to Not pause on exceptions.");
724
725            this._pauseOnExceptionButton.state = pauseOnExceptionsState;
726            WebInspector.settings.pauseOnExceptionStateString = pauseOnExceptionsState;
727        }
728        DebuggerAgent.setPauseOnExceptions(pauseOnExceptionsState, callback.bind(this));
729    },
730
731    _updateDebuggerButtons: function()
732    {
733        if (this._debuggerEnabled) {
734            this.enableToggleButton.title = WebInspector.UIString("Debugging enabled. Click to disable.");
735            this.enableToggleButton.toggled = true;
736            this._pauseOnExceptionButton.visible = true;
737            this.panelEnablerView.visible = false;
738        } else {
739            this.enableToggleButton.title = WebInspector.UIString("Debugging disabled. Click to enable.");
740            this.enableToggleButton.toggled = false;
741            this._pauseOnExceptionButton.visible = false;
742            this.panelEnablerView.visible = true;
743        }
744
745        if (this._paused) {
746            this.pauseButton.addStyleClass("paused");
747
748            this.pauseButton.disabled = false;
749            this.stepOverButton.disabled = false;
750            this.stepIntoButton.disabled = false;
751            this.stepOutButton.disabled = false;
752
753            this.debuggerStatusElement.textContent = WebInspector.UIString("Paused");
754        } else {
755            this.pauseButton.removeStyleClass("paused");
756
757            this.pauseButton.disabled = this._waitingToPause;
758            this.stepOverButton.disabled = true;
759            this.stepIntoButton.disabled = true;
760            this.stepOutButton.disabled = true;
761
762            if (this._waitingToPause)
763                this.debuggerStatusElement.textContent = WebInspector.UIString("Pausing");
764            else if (this._stepping)
765                this.debuggerStatusElement.textContent = WebInspector.UIString("Stepping");
766            else
767                this.debuggerStatusElement.textContent = "";
768        }
769    },
770
771    _updateBackAndForwardButtons: function()
772    {
773        this.backButton.disabled = this._currentBackForwardIndex <= 0;
774        this.forwardButton.disabled = this._currentBackForwardIndex >= (this._backForwardList.length - 1);
775    },
776
777    _clearInterface: function()
778    {
779        this.sidebarPanes.callstack.update(null);
780        this.sidebarPanes.scopechain.update(null);
781        this.sidebarPanes.jsBreakpoints.clearBreakpointHighlight();
782        if (Preferences.nativeInstrumentationEnabled) {
783            this.sidebarPanes.domBreakpoints.clearBreakpointHighlight();
784            this.sidebarPanes.eventListenerBreakpoints.clearBreakpointHighlight();
785            this.sidebarPanes.xhrBreakpoints.clearBreakpointHighlight();
786        }
787
788        this._clearCurrentExecutionLine();
789        this._updateDebuggerButtons();
790    },
791
792    _goBack: function()
793    {
794        if (this._currentBackForwardIndex <= 0) {
795            console.error("Can't go back from index " + this._currentBackForwardIndex);
796            return;
797        }
798
799        this._showSourceFrame(this._backForwardList[--this._currentBackForwardIndex]);
800        this._updateBackAndForwardButtons();
801    },
802
803    _goForward: function()
804    {
805        if (this._currentBackForwardIndex >= this._backForwardList.length - 1) {
806            console.error("Can't go forward from index " + this._currentBackForwardIndex);
807            return;
808        }
809
810        this._showSourceFrame(this._backForwardList[++this._currentBackForwardIndex]);
811        this._updateBackAndForwardButtons();
812    },
813
814    _enableDebugging: function()
815    {
816        if (this._debuggerEnabled)
817            return;
818        this._toggleDebugging(this.panelEnablerView.alwaysEnabled);
819    },
820
821    _toggleDebugging: function(optionalAlways)
822    {
823        this._paused = false;
824        this._waitingToPause = false;
825        this._stepping = false;
826
827        if (this._debuggerEnabled) {
828            WebInspector.settings.debuggerEnabled = false;
829            WebInspector.debuggerModel.disableDebugger();
830        } else {
831            WebInspector.settings.debuggerEnabled = !!optionalAlways;
832            WebInspector.debuggerModel.enableDebugger();
833        }
834    },
835
836    _togglePauseOnExceptions: function()
837    {
838        var nextStateMap = {};
839        var stateEnum = WebInspector.ScriptsPanel.PauseOnExceptionsState;
840        nextStateMap[stateEnum.DontPauseOnExceptions] = stateEnum.PauseOnAllExceptions;
841        nextStateMap[stateEnum.PauseOnAllExceptions] = stateEnum.PauseOnUncaughtExceptions;
842        nextStateMap[stateEnum.PauseOnUncaughtExceptions] = stateEnum.DontPauseOnExceptions;
843        this._setPauseOnExceptions(nextStateMap[this._pauseOnExceptionButton.state]);
844    },
845
846    _togglePause: function()
847    {
848        if (this._paused) {
849            this._paused = false;
850            this._waitingToPause = false;
851            DebuggerAgent.resume();
852        } else {
853            this._stepping = false;
854            this._waitingToPause = true;
855            DebuggerAgent.pause();
856        }
857
858        this._clearInterface();
859    },
860
861    _stepOverClicked: function()
862    {
863        this._paused = false;
864        this._stepping = true;
865
866        this._clearInterface();
867
868        DebuggerAgent.stepOver();
869    },
870
871    _stepIntoClicked: function()
872    {
873        this._paused = false;
874        this._stepping = true;
875
876        this._clearInterface();
877
878        DebuggerAgent.stepInto();
879    },
880
881    _stepOutClicked: function()
882    {
883        this._paused = false;
884        this._stepping = true;
885
886        this._clearInterface();
887
888        DebuggerAgent.stepOut();
889    },
890
891    toggleBreakpointsClicked: function()
892    {
893        this.toggleBreakpointsButton.toggled = !this.toggleBreakpointsButton.toggled;
894        if (this.toggleBreakpointsButton.toggled) {
895            DebuggerAgent.setBreakpointsActive(true);
896            this.toggleBreakpointsButton.title = WebInspector.UIString("Deactivate all breakpoints.");
897            document.getElementById("main-panels").removeStyleClass("breakpoints-deactivated");
898        } else {
899            DebuggerAgent.setBreakpointsActive(false);
900            this.toggleBreakpointsButton.title = WebInspector.UIString("Activate all breakpoints.");
901            document.getElementById("main-panels").addStyleClass("breakpoints-deactivated");
902        }
903    },
904
905    elementsToRestoreScrollPositionsFor: function()
906    {
907        return [ this.sidebarElement ];
908    },
909
910    _registerShortcuts: function()
911    {
912        var section = WebInspector.shortcutsHelp.section(WebInspector.UIString("Scripts Panel"));
913        var handler, shortcut1, shortcut2;
914        var platformSpecificModifier = WebInspector.KeyboardShortcut.Modifiers.CtrlOrMeta;
915
916        this._shortcuts = {};
917
918        // Continue.
919        handler = this.pauseButton.click.bind(this.pauseButton);
920        shortcut1 = WebInspector.KeyboardShortcut.makeDescriptor(WebInspector.KeyboardShortcut.Keys.F8);
921        this._shortcuts[shortcut1.key] = handler;
922        shortcut2 = WebInspector.KeyboardShortcut.makeDescriptor(WebInspector.KeyboardShortcut.Keys.Slash, platformSpecificModifier);
923        this._shortcuts[shortcut2.key] = handler;
924        section.addAlternateKeys([ shortcut1.name, shortcut2.name ], WebInspector.UIString("Continue"));
925
926        // Step over.
927        handler = this.stepOverButton.click.bind(this.stepOverButton);
928        shortcut1 = WebInspector.KeyboardShortcut.makeDescriptor(WebInspector.KeyboardShortcut.Keys.F10);
929        this._shortcuts[shortcut1.key] = handler;
930        shortcut2 = WebInspector.KeyboardShortcut.makeDescriptor(WebInspector.KeyboardShortcut.Keys.SingleQuote, platformSpecificModifier);
931        this._shortcuts[shortcut2.key] = handler;
932        section.addAlternateKeys([ shortcut1.name, shortcut2.name ], WebInspector.UIString("Step over"));
933
934        // Step into.
935        handler = this.stepIntoButton.click.bind(this.stepIntoButton);
936        shortcut1 = WebInspector.KeyboardShortcut.makeDescriptor(WebInspector.KeyboardShortcut.Keys.F11);
937        this._shortcuts[shortcut1.key] = handler;
938        shortcut2 = WebInspector.KeyboardShortcut.makeDescriptor(WebInspector.KeyboardShortcut.Keys.Semicolon, platformSpecificModifier);
939        this._shortcuts[shortcut2.key] = handler;
940        section.addAlternateKeys([ shortcut1.name, shortcut2.name ], WebInspector.UIString("Step into"));
941
942        // Step out.
943        handler = this.stepOutButton.click.bind(this.stepOutButton);
944        shortcut1 = WebInspector.KeyboardShortcut.makeDescriptor(WebInspector.KeyboardShortcut.Keys.F11, WebInspector.KeyboardShortcut.Modifiers.Shift);
945        this._shortcuts[shortcut1.key] = handler;
946        shortcut2 = WebInspector.KeyboardShortcut.makeDescriptor(WebInspector.KeyboardShortcut.Keys.Semicolon, WebInspector.KeyboardShortcut.Modifiers.Shift, platformSpecificModifier);
947        this._shortcuts[shortcut2.key] = handler;
948        section.addAlternateKeys([ shortcut1.name, shortcut2.name ], WebInspector.UIString("Step out"));
949
950        var isMac = WebInspector.isMac();
951        if (isMac)
952            shortcut1 = WebInspector.KeyboardShortcut.makeDescriptor("l", WebInspector.KeyboardShortcut.Modifiers.Meta);
953        else
954            shortcut1 = WebInspector.KeyboardShortcut.makeDescriptor("g", WebInspector.KeyboardShortcut.Modifiers.Ctrl);
955        this._shortcuts[shortcut1.key] = this.showGoToLineDialog.bind(this);
956        section.addAlternateKeys([ shortcut1.name ], WebInspector.UIString("Go to Line"));
957        this.sidebarPanes.callstack.registerShortcuts(section);
958    },
959
960    searchCanceled: function()
961    {
962        if (this._searchView)
963            this._searchView.searchCanceled();
964
965        delete this._searchView;
966        delete this._searchQuery;
967    },
968
969    performSearch: function(query)
970    {
971        WebInspector.searchController.updateSearchMatchesCount(0, this);
972
973        if (!this.visibleView)
974            return;
975
976        // Call searchCanceled since it will reset everything we need before doing a new search.
977        this.searchCanceled();
978
979        this._searchView = this.visibleView;
980        this._searchQuery = query;
981
982        function finishedCallback(view, searchMatches)
983        {
984            if (!searchMatches)
985                return;
986
987            WebInspector.searchController.updateSearchMatchesCount(searchMatches, this);
988            view.jumpToFirstSearchResult();
989        }
990
991        this._searchView.performSearch(query, finishedCallback.bind(this));
992    },
993
994    jumpToNextSearchResult: function()
995    {
996        if (!this._searchView)
997            return;
998
999        if (this._searchView !== this.visibleView) {
1000            this.performSearch(this._searchQuery);
1001            return;
1002        }
1003
1004        if (this._searchView.showingLastSearchResult())
1005            this._searchView.jumpToFirstSearchResult();
1006        else
1007            this._searchView.jumpToNextSearchResult();
1008    },
1009
1010    jumpToPreviousSearchResult: function()
1011    {
1012        if (!this._searchView)
1013            return;
1014
1015        if (this._searchView !== this.visibleView) {
1016            this.performSearch(this._searchQuery);
1017            if (this._searchView)
1018                this._searchView.jumpToLastSearchResult();
1019            return;
1020        }
1021
1022        if (this._searchView.showingFirstSearchResult())
1023            this._searchView.jumpToLastSearchResult();
1024        else
1025            this._searchView.jumpToPreviousSearchResult();
1026    },
1027
1028    showGoToLineDialog: function(e)
1029    {
1030         var view = this.visibleView;
1031         if (view)
1032             WebInspector.GoToLineDialog.show(view);
1033    }
1034}
1035
1036WebInspector.ScriptsPanel.prototype.__proto__ = WebInspector.Panel.prototype;
1037
1038
1039WebInspector.SourceFrameDelegateForScriptsPanel = function(model, sourceFileId)
1040{
1041    WebInspector.SourceFrameDelegate.call(this);
1042    this._model = model;
1043    this._sourceFileId = sourceFileId;
1044    this._popoverObjectGroup = "popover";
1045}
1046
1047WebInspector.SourceFrameDelegateForScriptsPanel.prototype = {
1048    requestContent: function(callback)
1049    {
1050        this._model.requestSourceFileContent(this._sourceFileId, callback);
1051    },
1052
1053    debuggingSupported: function()
1054    {
1055        return true;
1056    },
1057
1058    setBreakpoint: function(lineNumber, condition, enabled)
1059    {
1060        this._model.setBreakpoint(this._sourceFileId, lineNumber, condition, enabled);
1061
1062        if (!WebInspector.panels.scripts.breakpointsActivated)
1063            WebInspector.panels.scripts.toggleBreakpointsClicked();
1064    },
1065
1066    updateBreakpoint: function(lineNumber, condition, enabled)
1067    {
1068        this._model.updateBreakpoint(this._sourceFileId, lineNumber, condition, enabled);
1069    },
1070
1071    removeBreakpoint: function(lineNumber)
1072    {
1073        this._model.removeBreakpoint(this._sourceFileId, lineNumber);
1074    },
1075
1076    findBreakpoint: function(lineNumber)
1077    {
1078        return this._model.findBreakpoint(this._sourceFileId, lineNumber);
1079    },
1080
1081    continueToLine: function(lineNumber)
1082    {
1083        this._model.continueToLine(this._sourceFileId, lineNumber);
1084    },
1085
1086    canEditScriptSource: function()
1087    {
1088        return this._model.canEditScriptSource(this._sourceFileId);
1089    },
1090
1091    editScriptSource: function(text, callback)
1092    {
1093        this._model.editScriptSource(this._sourceFileId, text, callback);
1094    },
1095
1096    setScriptSourceIsBeingEdited: function(inEditMode)
1097    {
1098        WebInspector.panels.scripts.setScriptSourceIsBeingEdited(this._sourceFileId, inEditMode);
1099    },
1100
1101    debuggerPaused: function()
1102    {
1103        return WebInspector.panels.scripts.paused;
1104    },
1105
1106    evaluateInSelectedCallFrame: function(string, callback)
1107    {
1108        WebInspector.panels.scripts.evaluateInSelectedCallFrame(string, this._popoverObjectGroup, false, callback);
1109    },
1110
1111    releaseEvaluationResult: function()
1112    {
1113        RuntimeAgent.releaseObjectGroup(this._popoverObjectGroup);
1114    },
1115
1116    toggleFormatSourceFiles: function()
1117    {
1118        WebInspector.panels.scripts.reset();
1119        this._model.toggleFormatSourceFiles();
1120    },
1121
1122    formatSourceFilesToggled: function()
1123    {
1124        return this._model.formatSourceFilesToggled();
1125    }
1126}
1127
1128WebInspector.SourceFrameDelegateForScriptsPanel.prototype.__proto__ = WebInspector.SourceFrameDelegate.prototype;
1129