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