Home | History | Annotate | Download | only in front_end
      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 /**
     27  * @constructor
     28  * @extends {WebInspector.SidebarPane}
     29  */
     30 WebInspector.CallStackSidebarPane = function()
     31 {
     32     WebInspector.SidebarPane.call(this, WebInspector.UIString("Call Stack"));
     33     this.bodyElement.addEventListener("keydown", this._keyDown.bind(this), true);
     34     this.bodyElement.tabIndex = 0;
     35 
     36     if (WebInspector.experimentsSettings.asyncStackTraces.isEnabled()) {
     37         var asyncCheckbox = this.titleElement.appendChild(WebInspector.SettingsTab.createSettingCheckbox(WebInspector.UIString("Async"), WebInspector.settings.enableAsyncStackTraces, true, undefined, WebInspector.UIString("Capture async stack traces")));
     38         asyncCheckbox.classList.add("scripts-callstack-async");
     39         asyncCheckbox.addEventListener("click", consumeEvent, false);
     40 
     41         WebInspector.settings.enableAsyncStackTraces.addChangeListener(this._asyncStackTracesStateChanged, this);
     42     }
     43 }
     44 
     45 WebInspector.CallStackSidebarPane.Events = {
     46     CallFrameRestarted: "CallFrameRestarted",
     47     CallFrameSelected: "CallFrameSelected"
     48 }
     49 
     50 WebInspector.CallStackSidebarPane.prototype = {
     51     /**
     52      * @param {?Array.<!WebInspector.DebuggerModel.CallFrame>} callFrames
     53      * @param {?WebInspector.DebuggerModel.StackTrace} asyncStackTrace
     54      */
     55     update: function(callFrames, asyncStackTrace)
     56     {
     57         this.bodyElement.removeChildren();
     58         delete this._statusMessageElement;
     59         /** @type {!Array.<!WebInspector.CallStackSidebarPane.Placard>} */
     60         this.placards = [];
     61 
     62         if (!callFrames) {
     63             var infoElement = this.bodyElement.createChild("div", "info");
     64             infoElement.textContent = WebInspector.UIString("Not Paused");
     65             return;
     66         }
     67 
     68         this._appendSidebarPlacards(callFrames);
     69 
     70         while (asyncStackTrace) {
     71             var title = "[" + (asyncStackTrace.description || WebInspector.UIString("Async Call")) + "]";
     72             var asyncPlacard = new WebInspector.Placard(title, "");
     73             this.bodyElement.appendChild(asyncPlacard.element);
     74             this._appendSidebarPlacards(asyncStackTrace.callFrames, asyncPlacard);
     75             asyncStackTrace = asyncStackTrace.asyncStackTrace;
     76         }
     77     },
     78 
     79     /**
     80      * @param {!Array.<!WebInspector.DebuggerModel.CallFrame>} callFrames
     81      * @param {!WebInspector.Placard=} asyncPlacard
     82      */
     83     _appendSidebarPlacards: function(callFrames, asyncPlacard)
     84     {
     85         for (var i = 0, n = callFrames.length; i < n; ++i) {
     86             var placard = new WebInspector.CallStackSidebarPane.Placard(callFrames[i], asyncPlacard);
     87             placard.element.addEventListener("click", this._placardSelected.bind(this, placard), false);
     88             placard.element.addEventListener("contextmenu", this._placardContextMenu.bind(this, placard), true);
     89             if (!i && asyncPlacard) {
     90                 asyncPlacard.element.addEventListener("click", this._placardSelected.bind(this, placard), false);
     91                 asyncPlacard.element.addEventListener("contextmenu", this._placardContextMenu.bind(this, placard), true);
     92             }
     93             this.placards.push(placard);
     94             this.bodyElement.appendChild(placard.element);
     95         }
     96     },
     97 
     98     /**
     99      * @param {!WebInspector.CallStackSidebarPane.Placard} placard
    100      */
    101     _placardContextMenu: function(placard, event)
    102     {
    103         var contextMenu = new WebInspector.ContextMenu(event);
    104 
    105         if (!placard._callFrame.isAsync() && WebInspector.debuggerModel.canSetScriptSource())
    106             contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Restart frame" : "Restart Frame"), this._restartFrame.bind(this, placard));
    107 
    108         contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Copy stack trace" : "Copy Stack Trace"), this._copyStackTrace.bind(this));
    109         contextMenu.show();
    110     },
    111 
    112     /**
    113      * @param {!WebInspector.CallStackSidebarPane.Placard} placard
    114      */
    115     _restartFrame: function(placard)
    116     {
    117         placard._callFrame.restart();
    118         this.dispatchEventToListeners(WebInspector.CallStackSidebarPane.Events.CallFrameRestarted, placard._callFrame);
    119     },
    120 
    121     _asyncStackTracesStateChanged: function()
    122     {
    123         var enabled = WebInspector.settings.enableAsyncStackTraces.get();
    124         if (!enabled && this.placards)
    125             this._removeAsyncPlacards();
    126     },
    127 
    128     _removeAsyncPlacards: function()
    129     {
    130         var shouldSelectTopFrame = false;
    131         var lastSyncPlacardIndex = -1;
    132         for (var i = 0; i < this.placards.length; ++i) {
    133             var placard = this.placards[i];
    134             if (placard._asyncPlacard) {
    135                 if (placard.selected)
    136                     shouldSelectTopFrame = true;
    137                 placard._asyncPlacard.element.remove();
    138                 placard.element.remove();
    139             } else {
    140                 lastSyncPlacardIndex = i;
    141             }
    142         }
    143         this.placards.length = lastSyncPlacardIndex + 1;
    144         if (shouldSelectTopFrame)
    145             this._selectPlacardByIndex(0);
    146     },
    147 
    148     /**
    149      * @param {!WebInspector.DebuggerModel.CallFrame} x
    150      */
    151     setSelectedCallFrame: function(x)
    152     {
    153         for (var i = 0; i < this.placards.length; ++i) {
    154             var placard = this.placards[i];
    155             placard.selected = (placard._callFrame === x);
    156         }
    157     },
    158 
    159     /**
    160      * @return {boolean}
    161      */
    162     _selectNextCallFrameOnStack: function()
    163     {
    164         var index = this._selectedCallFrameIndex();
    165         if (index === -1)
    166             return false;
    167         return this._selectPlacardByIndex(index + 1);
    168     },
    169 
    170     /**
    171      * @return {boolean}
    172      */
    173     _selectPreviousCallFrameOnStack: function()
    174     {
    175         var index = this._selectedCallFrameIndex();
    176         if (index === -1)
    177             return false;
    178         return this._selectPlacardByIndex(index - 1);
    179     },
    180 
    181     /**
    182      * @param {number} index
    183      * @return {boolean}
    184      */
    185     _selectPlacardByIndex: function(index)
    186     {
    187         if (index < 0 || index >= this.placards.length)
    188             return false;
    189         this._placardSelected(this.placards[index]);
    190         return true;
    191     },
    192 
    193     /**
    194      * @return {number}
    195      */
    196     _selectedCallFrameIndex: function()
    197     {
    198         var selectedCallFrame = WebInspector.debuggerModel.selectedCallFrame();
    199         if (!selectedCallFrame)
    200             return -1;
    201         for (var i = 0; i < this.placards.length; ++i) {
    202             var placard = this.placards[i];
    203             if (placard._callFrame === selectedCallFrame)
    204                 return i;
    205         }
    206         return -1;
    207     },
    208 
    209     /**
    210      * @param {!WebInspector.CallStackSidebarPane.Placard} placard
    211      */
    212     _placardSelected: function(placard)
    213     {
    214         placard.element.scrollIntoViewIfNeeded();
    215         this.dispatchEventToListeners(WebInspector.CallStackSidebarPane.Events.CallFrameSelected, placard._callFrame);
    216     },
    217 
    218     _copyStackTrace: function()
    219     {
    220         var text = "";
    221         for (var i = 0; i < this.placards.length; ++i) {
    222             if (i && this.placards[i]._asyncPlacard !== this.placards[i - 1]._asyncPlacard)
    223                 text += this.placards[i]._asyncPlacard.title + "\n";
    224             text += this.placards[i].title + " (" + this.placards[i].subtitle + ")\n";
    225         }
    226         InspectorFrontendHost.copyText(text);
    227     },
    228 
    229     /**
    230      * @param {function(!Array.<!WebInspector.KeyboardShortcut.Descriptor>, function(?Event=):boolean)} registerShortcutDelegate
    231      */
    232     registerShortcuts: function(registerShortcutDelegate)
    233     {
    234         registerShortcutDelegate(WebInspector.SourcesPanelDescriptor.ShortcutKeys.NextCallFrame, this._selectNextCallFrameOnStack.bind(this));
    235         registerShortcutDelegate(WebInspector.SourcesPanelDescriptor.ShortcutKeys.PrevCallFrame, this._selectPreviousCallFrameOnStack.bind(this));
    236     },
    237 
    238     /**
    239      * @param {!Element|string} status
    240      */
    241     setStatus: function(status)
    242     {
    243         if (!this._statusMessageElement)
    244             this._statusMessageElement = this.bodyElement.createChild("div", "info");
    245         if (typeof status === "string") {
    246             this._statusMessageElement.textContent = status;
    247         } else {
    248             this._statusMessageElement.removeChildren();
    249             this._statusMessageElement.appendChild(status);
    250         }
    251     },
    252 
    253     _keyDown: function(event)
    254     {
    255         if (event.altKey || event.shiftKey || event.metaKey || event.ctrlKey)
    256             return;
    257         if (event.keyIdentifier === "Up" && this._selectPreviousCallFrameOnStack() || event.keyIdentifier === "Down" && this._selectNextCallFrameOnStack())
    258             event.consume(true);
    259     },
    260 
    261     __proto__: WebInspector.SidebarPane.prototype
    262 }
    263 
    264 /**
    265  * @constructor
    266  * @extends {WebInspector.Placard}
    267  * @param {!WebInspector.DebuggerModel.CallFrame} callFrame
    268  * @param {!WebInspector.Placard=} asyncPlacard
    269  */
    270 WebInspector.CallStackSidebarPane.Placard = function(callFrame, asyncPlacard)
    271 {
    272     WebInspector.Placard.call(this, callFrame.functionName || WebInspector.UIString("(anonymous function)"), "");
    273     callFrame.createLiveLocation(this._update.bind(this));
    274     this._callFrame = callFrame;
    275     this._asyncPlacard = asyncPlacard;
    276 }
    277 
    278 WebInspector.CallStackSidebarPane.Placard.prototype = {
    279     /**
    280      * @param {!WebInspector.UILocation} uiLocation
    281      */
    282     _update: function(uiLocation)
    283     {
    284         this.subtitle = uiLocation.linkText().trimMiddle(100);
    285     },
    286 
    287     __proto__: WebInspector.Placard.prototype
    288 }
    289