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