Home | History | Annotate | Download | only in sources
      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     var asyncCheckbox = this.titleElement.appendChild(WebInspector.SettingsUI.createSettingCheckbox(WebInspector.UIString("Async"), WebInspector.settings.enableAsyncStackTraces, true, undefined, WebInspector.UIString("Capture async stack traces")));
     37     asyncCheckbox.classList.add("scripts-callstack-async");
     38     asyncCheckbox.addEventListener("click", consumeEvent, false);
     39     WebInspector.settings.enableAsyncStackTraces.addChangeListener(this._asyncStackTracesStateChanged, this);
     40 }
     41 
     42 WebInspector.CallStackSidebarPane.Events = {
     43     CallFrameRestarted: "CallFrameRestarted",
     44     CallFrameSelected: "CallFrameSelected"
     45 }
     46 
     47 WebInspector.CallStackSidebarPane.prototype = {
     48     /**
     49      * @param {?WebInspector.DebuggerPausedDetails} details
     50      */
     51     update: function(details)
     52     {
     53         this.bodyElement.removeChildren();
     54 
     55         if (!details) {
     56             var infoElement = this.bodyElement.createChild("div", "info");
     57             infoElement.textContent = WebInspector.UIString("Not Paused");
     58             return;
     59         }
     60 
     61         this._target = details.target();
     62         var callFrames = details.callFrames;
     63         var asyncStackTrace = details.asyncStackTrace;
     64 
     65         delete this._statusMessageElement;
     66         delete this._hiddenPlacardsMessageElement;
     67         /** @type {!Array.<!WebInspector.CallStackSidebarPane.Placard>} */
     68         this.placards = [];
     69         this._hiddenPlacards = 0;
     70 
     71         this._appendSidebarPlacards(callFrames);
     72         var topStackHidden = (this._hiddenPlacards === this.placards.length);
     73 
     74         while (asyncStackTrace) {
     75             var title = asyncStackTrace.description;
     76             if (title)
     77                 title += " " + WebInspector.UIString("(async)");
     78             else
     79                 title = WebInspector.UIString("Async Call");
     80             var asyncPlacard = new WebInspector.Placard(title, "");
     81             asyncPlacard.element.addEventListener("click", this._selectNextVisiblePlacard.bind(this, this.placards.length, false), false);
     82             asyncPlacard.element.addEventListener("contextmenu", this._asyncPlacardContextMenu.bind(this, this.placards.length), true);
     83             asyncPlacard.element.classList.add("placard-label");
     84             this.bodyElement.appendChild(asyncPlacard.element);
     85             this._appendSidebarPlacards(asyncStackTrace.callFrames, asyncPlacard);
     86             asyncStackTrace = asyncStackTrace.asyncStackTrace;
     87         }
     88 
     89         if (topStackHidden)
     90             this._revealHiddenPlacards();
     91         if (this._hiddenPlacards) {
     92             var element = document.createElementWithClass("div", "hidden-placards-message");
     93             if (this._hiddenPlacards === 1)
     94                 element.textContent = WebInspector.UIString("1 stack frame is hidden (black-boxed).");
     95             else
     96                 element.textContent = WebInspector.UIString("%d stack frames are hidden (black-boxed).", this._hiddenPlacards);
     97             element.createTextChild(" ");
     98             var showAllLink = element.createChild("span", "node-link");
     99             showAllLink.textContent = WebInspector.UIString("Show");
    100             showAllLink.addEventListener("click", this._revealHiddenPlacards.bind(this), false);
    101             this.bodyElement.insertBefore(element, this.bodyElement.firstChild);
    102             this._hiddenPlacardsMessageElement = element;
    103         }
    104     },
    105 
    106     /**
    107      * @param {!Array.<!WebInspector.DebuggerModel.CallFrame>} callFrames
    108      * @param {!WebInspector.Placard=} asyncPlacard
    109      */
    110     _appendSidebarPlacards: function(callFrames, asyncPlacard)
    111     {
    112         var allPlacardsHidden = true;
    113         for (var i = 0, n = callFrames.length; i < n; ++i) {
    114             var callFrame = callFrames[i];
    115             var placard = new WebInspector.CallStackSidebarPane.Placard(callFrame, asyncPlacard);
    116             placard.element.addEventListener("click", this._placardSelected.bind(this, placard), false);
    117             placard.element.addEventListener("contextmenu", this._placardContextMenu.bind(this, placard), true);
    118             this.placards.push(placard);
    119             this.bodyElement.appendChild(placard.element);
    120 
    121             if (callFrame.script.isFramework()) {
    122                 placard.setHidden(true);
    123                 placard.element.classList.add("dimmed");
    124                 ++this._hiddenPlacards;
    125             } else {
    126                 allPlacardsHidden = false;
    127             }
    128         }
    129         if (allPlacardsHidden && asyncPlacard)
    130             asyncPlacard.setHidden(true);
    131     },
    132 
    133     _revealHiddenPlacards: function()
    134     {
    135         if (!this._hiddenPlacards)
    136             return;
    137         this._hiddenPlacards = 0;
    138         for (var i = 0; i < this.placards.length; ++i) {
    139             var placard = this.placards[i];
    140             placard.setHidden(false);
    141             if (placard._asyncPlacard)
    142                 placard._asyncPlacard.setHidden(false);
    143         }
    144         if (this._hiddenPlacardsMessageElement) {
    145             this._hiddenPlacardsMessageElement.remove();
    146             delete this._hiddenPlacardsMessageElement;
    147         }
    148     },
    149 
    150     /**
    151      * @param {!WebInspector.CallStackSidebarPane.Placard} placard
    152      * @param {?Event} event
    153      */
    154     _placardContextMenu: function(placard, event)
    155     {
    156         var contextMenu = new WebInspector.ContextMenu(event);
    157 
    158         if (!placard._callFrame.isAsync())
    159             contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Restart frame" : "Restart Frame"), this._restartFrame.bind(this, placard));
    160 
    161         contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Copy stack trace" : "Copy Stack Trace"), this._copyStackTrace.bind(this));
    162         contextMenu.show();
    163     },
    164 
    165     /**
    166      * @param {number} index
    167      * @param {?Event} event
    168      */
    169     _asyncPlacardContextMenu: function(index, event)
    170     {
    171         for (; index < this.placards.length; ++index) {
    172             var placard = this.placards[index];
    173             if (!placard.isHidden()) {
    174                 this._placardContextMenu(placard, event);
    175                 break;
    176             }
    177         }
    178     },
    179 
    180     /**
    181      * @param {!WebInspector.CallStackSidebarPane.Placard} placard
    182      */
    183     _restartFrame: function(placard)
    184     {
    185         placard._callFrame.restart();
    186         this.dispatchEventToListeners(WebInspector.CallStackSidebarPane.Events.CallFrameRestarted, placard._callFrame);
    187     },
    188 
    189     _asyncStackTracesStateChanged: function()
    190     {
    191         var enabled = WebInspector.settings.enableAsyncStackTraces.get();
    192         if (!enabled && this.placards)
    193             this._removeAsyncPlacards();
    194     },
    195 
    196     _removeAsyncPlacards: function()
    197     {
    198         var shouldSelectTopFrame = false;
    199         var lastSyncPlacardIndex = -1;
    200         for (var i = 0; i < this.placards.length; ++i) {
    201             var placard = this.placards[i];
    202             if (placard._asyncPlacard) {
    203                 if (placard.selected)
    204                     shouldSelectTopFrame = true;
    205                 placard._asyncPlacard.element.remove();
    206                 placard.element.remove();
    207             } else {
    208                 lastSyncPlacardIndex = i;
    209             }
    210         }
    211         this.placards.length = lastSyncPlacardIndex + 1;
    212         if (shouldSelectTopFrame)
    213             this._selectNextVisiblePlacard(0);
    214     },
    215 
    216     /**
    217      * @param {!WebInspector.DebuggerModel.CallFrame} x
    218      */
    219     setSelectedCallFrame: function(x)
    220     {
    221         for (var i = 0; i < this.placards.length; ++i) {
    222             var placard = this.placards[i];
    223             placard.selected = (placard._callFrame === x);
    224             if (placard.selected && placard.isHidden())
    225                 this._revealHiddenPlacards();
    226         }
    227     },
    228 
    229     /**
    230      * @return {boolean}
    231      */
    232     _selectNextCallFrameOnStack: function()
    233     {
    234         var index = this._selectedCallFrameIndex();
    235         if (index === -1)
    236             return false;
    237         return this._selectNextVisiblePlacard(index + 1);
    238     },
    239 
    240     /**
    241      * @return {boolean}
    242      */
    243     _selectPreviousCallFrameOnStack: function()
    244     {
    245         var index = this._selectedCallFrameIndex();
    246         if (index === -1)
    247             return false;
    248         return this._selectNextVisiblePlacard(index - 1, true);
    249     },
    250 
    251     /**
    252      * @param {number} index
    253      * @param {boolean=} backward
    254      * @return {boolean}
    255      */
    256     _selectNextVisiblePlacard: function(index, backward)
    257     {
    258         while (0 <= index && index < this.placards.length) {
    259             var placard = this.placards[index];
    260             if (!placard.isHidden()) {
    261                 this._placardSelected(placard);
    262                 return true;
    263             }
    264             index += backward ? -1 : 1;
    265         }
    266         return false;
    267     },
    268 
    269     /**
    270      * @return {number}
    271      */
    272     _selectedCallFrameIndex: function()
    273     {
    274         var selectedCallFrame = this._target.debuggerModel.selectedCallFrame();
    275         if (!selectedCallFrame)
    276             return -1;
    277         for (var i = 0; i < this.placards.length; ++i) {
    278             var placard = this.placards[i];
    279             if (placard._callFrame === selectedCallFrame)
    280                 return i;
    281         }
    282         return -1;
    283     },
    284 
    285     /**
    286      * @param {!WebInspector.CallStackSidebarPane.Placard} placard
    287      */
    288     _placardSelected: function(placard)
    289     {
    290         placard.element.scrollIntoViewIfNeeded();
    291         this.dispatchEventToListeners(WebInspector.CallStackSidebarPane.Events.CallFrameSelected, placard._callFrame);
    292     },
    293 
    294     _copyStackTrace: function()
    295     {
    296         var text = "";
    297         var lastPlacard = null;
    298         for (var i = 0; i < this.placards.length; ++i) {
    299             var placard = this.placards[i];
    300             if (placard.isHidden())
    301                 continue;
    302             if (lastPlacard && placard._asyncPlacard !== lastPlacard._asyncPlacard)
    303                 text += placard._asyncPlacard.title + "\n";
    304             text += placard.title + " (" + placard.subtitle + ")\n";
    305             lastPlacard = placard;
    306         }
    307         InspectorFrontendHost.copyText(text);
    308     },
    309 
    310     /**
    311      * @param {function(!Array.<!WebInspector.KeyboardShortcut.Descriptor>, function(?Event=):boolean)} registerShortcutDelegate
    312      */
    313     registerShortcuts: function(registerShortcutDelegate)
    314     {
    315         registerShortcutDelegate(WebInspector.ShortcutsScreen.SourcesPanelShortcuts.NextCallFrame, this._selectNextCallFrameOnStack.bind(this));
    316         registerShortcutDelegate(WebInspector.ShortcutsScreen.SourcesPanelShortcuts.PrevCallFrame, this._selectPreviousCallFrameOnStack.bind(this));
    317     },
    318 
    319     /**
    320      * @param {!Element|string} status
    321      */
    322     setStatus: function(status)
    323     {
    324         if (!this._statusMessageElement)
    325             this._statusMessageElement = this.bodyElement.createChild("div", "info");
    326         if (typeof status === "string") {
    327             this._statusMessageElement.textContent = status;
    328         } else {
    329             this._statusMessageElement.removeChildren();
    330             this._statusMessageElement.appendChild(status);
    331         }
    332     },
    333 
    334     _keyDown: function(event)
    335     {
    336         if (event.altKey || event.shiftKey || event.metaKey || event.ctrlKey)
    337             return;
    338         if (event.keyIdentifier === "Up" && this._selectPreviousCallFrameOnStack() || event.keyIdentifier === "Down" && this._selectNextCallFrameOnStack())
    339             event.consume(true);
    340     },
    341 
    342     __proto__: WebInspector.SidebarPane.prototype
    343 }
    344 
    345 /**
    346  * @constructor
    347  * @extends {WebInspector.Placard}
    348  * @param {!WebInspector.DebuggerModel.CallFrame} callFrame
    349  * @param {!WebInspector.Placard=} asyncPlacard
    350  */
    351 WebInspector.CallStackSidebarPane.Placard = function(callFrame, asyncPlacard)
    352 {
    353     WebInspector.Placard.call(this, callFrame.functionName || WebInspector.UIString("(anonymous function)"), "");
    354     callFrame.createLiveLocation(this._update.bind(this));
    355     this._callFrame = callFrame;
    356     this._asyncPlacard = asyncPlacard;
    357 }
    358 
    359 WebInspector.CallStackSidebarPane.Placard.prototype = {
    360     /**
    361      * @param {!WebInspector.UILocation} uiLocation
    362      */
    363     _update: function(uiLocation)
    364     {
    365         this.subtitle = uiLocation.linkText().trimMiddle(100);
    366     },
    367 
    368     __proto__: WebInspector.Placard.prototype
    369 }
    370