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     WebInspector.settings.skipStackFramesPattern.addChangeListener(this._blackboxingStateChanged, this);
     41 }
     42 
     43 WebInspector.CallStackSidebarPane.Events = {
     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 = WebInspector.asyncStackTraceLabel(asyncStackTrace.description);
     76             var asyncPlacard = new WebInspector.Placard(title, "");
     77             asyncPlacard.element.addEventListener("click", this._selectNextVisiblePlacard.bind(this, this.placards.length, false), false);
     78             asyncPlacard.element.addEventListener("contextmenu", this._asyncPlacardContextMenu.bind(this, this.placards.length), true);
     79             asyncPlacard.element.classList.add("placard-label");
     80             this.bodyElement.appendChild(asyncPlacard.element);
     81             this._appendSidebarPlacards(asyncStackTrace.callFrames, asyncPlacard);
     82             asyncStackTrace = asyncStackTrace.asyncStackTrace;
     83         }
     84 
     85         if (topStackHidden)
     86             this._revealHiddenPlacards();
     87         if (this._hiddenPlacards) {
     88             var element = document.createElementWithClass("div", "hidden-placards-message");
     89             if (this._hiddenPlacards === 1)
     90                 element.textContent = WebInspector.UIString("1 stack frame is hidden (black-boxed).");
     91             else
     92                 element.textContent = WebInspector.UIString("%d stack frames are hidden (black-boxed).", this._hiddenPlacards);
     93             element.createTextChild(" ");
     94             var showAllLink = element.createChild("span", "node-link");
     95             showAllLink.textContent = WebInspector.UIString("Show");
     96             showAllLink.addEventListener("click", this._revealHiddenPlacards.bind(this), false);
     97             this.bodyElement.insertBefore(element, this.bodyElement.firstChild);
     98             this._hiddenPlacardsMessageElement = element;
     99         }
    100     },
    101 
    102     /**
    103      * @param {!Array.<!WebInspector.DebuggerModel.CallFrame>} callFrames
    104      * @param {!WebInspector.Placard=} asyncPlacard
    105      */
    106     _appendSidebarPlacards: function(callFrames, asyncPlacard)
    107     {
    108         var allPlacardsHidden = true;
    109         for (var i = 0, n = callFrames.length; i < n; ++i) {
    110             var callFrame = callFrames[i];
    111             var placard = new WebInspector.CallStackSidebarPane.Placard(callFrame, asyncPlacard);
    112             placard.element.addEventListener("click", this._placardSelected.bind(this, placard), false);
    113             placard.element.addEventListener("contextmenu", this._placardContextMenu.bind(this, placard), true);
    114             this.placards.push(placard);
    115             this.bodyElement.appendChild(placard.element);
    116 
    117             if (WebInspector.BlackboxSupport.isBlackboxed(callFrame.script.sourceURL, callFrame.script.isContentScript())) {
    118                 placard.setHidden(true);
    119                 placard.element.classList.add("dimmed");
    120                 ++this._hiddenPlacards;
    121             } else {
    122                 allPlacardsHidden = false;
    123             }
    124         }
    125         if (allPlacardsHidden && asyncPlacard)
    126             asyncPlacard.setHidden(true);
    127     },
    128 
    129     _revealHiddenPlacards: function()
    130     {
    131         if (!this._hiddenPlacards)
    132             return;
    133         this._hiddenPlacards = 0;
    134         for (var i = 0; i < this.placards.length; ++i) {
    135             var placard = this.placards[i];
    136             placard.setHidden(false);
    137             if (placard._asyncPlacard)
    138                 placard._asyncPlacard.setHidden(false);
    139         }
    140         if (this._hiddenPlacardsMessageElement) {
    141             this._hiddenPlacardsMessageElement.remove();
    142             delete this._hiddenPlacardsMessageElement;
    143         }
    144     },
    145 
    146     /**
    147      * @param {!WebInspector.CallStackSidebarPane.Placard} placard
    148      * @param {!Event} event
    149      */
    150     _placardContextMenu: function(placard, event)
    151     {
    152         var contextMenu = new WebInspector.ContextMenu(event);
    153 
    154         if (!placard._callFrame.isAsync())
    155             contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Restart frame" : "Restart Frame"), this._restartFrame.bind(this, placard));
    156 
    157         contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Copy stack trace" : "Copy Stack Trace"), this._copyStackTrace.bind(this));
    158 
    159         var script = placard._callFrame.script;
    160         if (!script.isSnippet()) {
    161             contextMenu.appendSeparator();
    162             this.appendBlackboxURLContextMenuItems(contextMenu, script.sourceURL, script.isContentScript());
    163         }
    164 
    165         contextMenu.show();
    166     },
    167 
    168     /**
    169      * @param {number} index
    170      * @param {!Event} event
    171      */
    172     _asyncPlacardContextMenu: function(index, event)
    173     {
    174         for (; index < this.placards.length; ++index) {
    175             var placard = this.placards[index];
    176             if (!placard.isHidden()) {
    177                 this._placardContextMenu(placard, event);
    178                 break;
    179             }
    180         }
    181     },
    182 
    183     /**
    184      * @param {!WebInspector.ContextMenu} contextMenu
    185      * @param {string} url
    186      * @param {boolean} isContentScript
    187      */
    188     appendBlackboxURLContextMenuItems: function(contextMenu, url, isContentScript)
    189     {
    190         var blackboxed = WebInspector.BlackboxSupport.isBlackboxed(url, isContentScript);
    191         if (blackboxed) {
    192             contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Stop blackboxing" : "Stop Blackboxing"), this._handleContextMenuBlackboxURL.bind(this, url, isContentScript, false));
    193         } else {
    194             if (WebInspector.BlackboxSupport.canBlackboxURL(url))
    195                 contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Blackbox script" : "Blackbox Script"), this._handleContextMenuBlackboxURL.bind(this, url, false, true));
    196             if (isContentScript)
    197                 contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Blackbox all content scripts" : "Blackbox All Content Scripts"), this._handleContextMenuBlackboxURL.bind(this, url, true, true));
    198         }
    199     },
    200 
    201     /**
    202      * @param {string} url
    203      * @param {boolean} isContentScript
    204      * @param {boolean} blackbox
    205      */
    206     _handleContextMenuBlackboxURL: function(url, isContentScript, blackbox)
    207     {
    208         if (blackbox) {
    209             if (isContentScript)
    210                 WebInspector.settings.skipContentScripts.set(true);
    211             else
    212                 WebInspector.BlackboxSupport.blackboxURL(url);
    213         } else {
    214             WebInspector.BlackboxSupport.unblackbox(url, isContentScript);
    215         }
    216     },
    217 
    218     _blackboxingStateChanged: function()
    219     {
    220         if (!this._target)
    221             return;
    222         var details = this._target.debuggerModel.debuggerPausedDetails();
    223         if (!details)
    224             return;
    225         this.update(details);
    226         var selectedCallFrame = this._target.debuggerModel.selectedCallFrame();
    227         if (selectedCallFrame)
    228             this.setSelectedCallFrame(selectedCallFrame);
    229     },
    230 
    231     /**
    232      * @param {!WebInspector.CallStackSidebarPane.Placard} placard
    233      */
    234     _restartFrame: function(placard)
    235     {
    236         placard._callFrame.restart();
    237     },
    238 
    239     _asyncStackTracesStateChanged: function()
    240     {
    241         var enabled = WebInspector.settings.enableAsyncStackTraces.get();
    242         if (!enabled && this.placards)
    243             this._removeAsyncPlacards();
    244     },
    245 
    246     _removeAsyncPlacards: function()
    247     {
    248         var shouldSelectTopFrame = false;
    249         var lastSyncPlacardIndex = -1;
    250         for (var i = 0; i < this.placards.length; ++i) {
    251             var placard = this.placards[i];
    252             if (placard._asyncPlacard) {
    253                 if (placard.selected)
    254                     shouldSelectTopFrame = true;
    255                 placard._asyncPlacard.element.remove();
    256                 placard.element.remove();
    257             } else {
    258                 lastSyncPlacardIndex = i;
    259             }
    260         }
    261         this.placards.length = lastSyncPlacardIndex + 1;
    262         if (shouldSelectTopFrame)
    263             this._selectNextVisiblePlacard(0);
    264     },
    265 
    266     /**
    267      * @param {!WebInspector.DebuggerModel.CallFrame} x
    268      */
    269     setSelectedCallFrame: function(x)
    270     {
    271         for (var i = 0; i < this.placards.length; ++i) {
    272             var placard = this.placards[i];
    273             placard.selected = (placard._callFrame === x);
    274             if (placard.selected && placard.isHidden())
    275                 this._revealHiddenPlacards();
    276         }
    277     },
    278 
    279     /**
    280      * @return {boolean}
    281      */
    282     _selectNextCallFrameOnStack: function()
    283     {
    284         var index = this._selectedCallFrameIndex();
    285         if (index === -1)
    286             return false;
    287         return this._selectNextVisiblePlacard(index + 1);
    288     },
    289 
    290     /**
    291      * @return {boolean}
    292      */
    293     _selectPreviousCallFrameOnStack: function()
    294     {
    295         var index = this._selectedCallFrameIndex();
    296         if (index === -1)
    297             return false;
    298         return this._selectNextVisiblePlacard(index - 1, true);
    299     },
    300 
    301     /**
    302      * @param {number} index
    303      * @param {boolean=} backward
    304      * @return {boolean}
    305      */
    306     _selectNextVisiblePlacard: function(index, backward)
    307     {
    308         while (0 <= index && index < this.placards.length) {
    309             var placard = this.placards[index];
    310             if (!placard.isHidden()) {
    311                 this._placardSelected(placard);
    312                 return true;
    313             }
    314             index += backward ? -1 : 1;
    315         }
    316         return false;
    317     },
    318 
    319     /**
    320      * @return {number}
    321      */
    322     _selectedCallFrameIndex: function()
    323     {
    324         var selectedCallFrame = this._target.debuggerModel.selectedCallFrame();
    325         if (!selectedCallFrame)
    326             return -1;
    327         for (var i = 0; i < this.placards.length; ++i) {
    328             var placard = this.placards[i];
    329             if (placard._callFrame === selectedCallFrame)
    330                 return i;
    331         }
    332         return -1;
    333     },
    334 
    335     /**
    336      * @param {!WebInspector.CallStackSidebarPane.Placard} placard
    337      */
    338     _placardSelected: function(placard)
    339     {
    340         placard.element.scrollIntoViewIfNeeded();
    341         this.dispatchEventToListeners(WebInspector.CallStackSidebarPane.Events.CallFrameSelected, placard._callFrame);
    342     },
    343 
    344     _copyStackTrace: function()
    345     {
    346         var text = "";
    347         var lastPlacard = null;
    348         for (var i = 0; i < this.placards.length; ++i) {
    349             var placard = this.placards[i];
    350             if (placard.isHidden())
    351                 continue;
    352             if (lastPlacard && placard._asyncPlacard !== lastPlacard._asyncPlacard)
    353                 text += placard._asyncPlacard.title + "\n";
    354             text += placard.title + " (" + placard.subtitle + ")\n";
    355             lastPlacard = placard;
    356         }
    357         InspectorFrontendHost.copyText(text);
    358     },
    359 
    360     /**
    361      * @param {function(!Array.<!WebInspector.KeyboardShortcut.Descriptor>, function(!Event=):boolean)} registerShortcutDelegate
    362      */
    363     registerShortcuts: function(registerShortcutDelegate)
    364     {
    365         registerShortcutDelegate(WebInspector.ShortcutsScreen.SourcesPanelShortcuts.NextCallFrame, this._selectNextCallFrameOnStack.bind(this));
    366         registerShortcutDelegate(WebInspector.ShortcutsScreen.SourcesPanelShortcuts.PrevCallFrame, this._selectPreviousCallFrameOnStack.bind(this));
    367     },
    368 
    369     /**
    370      * @param {!Element|string} status
    371      */
    372     setStatus: function(status)
    373     {
    374         if (!this._statusMessageElement)
    375             this._statusMessageElement = this.bodyElement.createChild("div", "info");
    376         if (typeof status === "string") {
    377             this._statusMessageElement.textContent = status;
    378         } else {
    379             this._statusMessageElement.removeChildren();
    380             this._statusMessageElement.appendChild(status);
    381         }
    382     },
    383 
    384     _keyDown: function(event)
    385     {
    386         if (event.altKey || event.shiftKey || event.metaKey || event.ctrlKey)
    387             return;
    388         if (event.keyIdentifier === "Up" && this._selectPreviousCallFrameOnStack() || event.keyIdentifier === "Down" && this._selectNextCallFrameOnStack())
    389             event.consume(true);
    390     },
    391 
    392     __proto__: WebInspector.SidebarPane.prototype
    393 }
    394 
    395 /**
    396  * @constructor
    397  * @extends {WebInspector.Placard}
    398  * @param {!WebInspector.DebuggerModel.CallFrame} callFrame
    399  * @param {!WebInspector.Placard=} asyncPlacard
    400  */
    401 WebInspector.CallStackSidebarPane.Placard = function(callFrame, asyncPlacard)
    402 {
    403     WebInspector.Placard.call(this, callFrame.functionName || WebInspector.UIString("(anonymous function)"), "");
    404     WebInspector.debuggerWorkspaceBinding.createCallFrameLiveLocation(callFrame, this._update.bind(this));
    405     this._callFrame = callFrame;
    406     this._asyncPlacard = asyncPlacard;
    407 }
    408 
    409 WebInspector.CallStackSidebarPane.Placard.prototype = {
    410     /**
    411      * @param {!WebInspector.UILocation} uiLocation
    412      */
    413     _update: function(uiLocation)
    414     {
    415         var text = uiLocation.linkText();
    416         this.subtitle = text.trimMiddle(30);
    417         this.subtitleElement.title = text;
    418     },
    419 
    420     __proto__: WebInspector.Placard.prototype
    421 }
    422