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