1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/browser/devtools/debugger/debugger-controller.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,2229 @@ 1.4 +/* -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 1.5 +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ 1.6 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.9 +"use strict"; 1.10 + 1.11 +const { classes: Cc, interfaces: Ci, utils: Cu } = Components; 1.12 + 1.13 +const DBG_STRINGS_URI = "chrome://browser/locale/devtools/debugger.properties"; 1.14 +const NEW_SOURCE_IGNORED_URLS = ["debugger eval code", "self-hosted", "XStringBundle"]; 1.15 +const NEW_SOURCE_DISPLAY_DELAY = 200; // ms 1.16 +const FETCH_SOURCE_RESPONSE_DELAY = 200; // ms 1.17 +const FETCH_EVENT_LISTENERS_DELAY = 200; // ms 1.18 +const FRAME_STEP_CLEAR_DELAY = 100; // ms 1.19 +const CALL_STACK_PAGE_SIZE = 25; // frames 1.20 + 1.21 +// The panel's window global is an EventEmitter firing the following events: 1.22 +const EVENTS = { 1.23 + // When the debugger's source editor instance finishes loading or unloading. 1.24 + EDITOR_LOADED: "Debugger:EditorLoaded", 1.25 + EDITOR_UNLOADED: "Debugger:EditorUnoaded", 1.26 + 1.27 + // When new sources are received from the debugger server. 1.28 + NEW_SOURCE: "Debugger:NewSource", 1.29 + SOURCES_ADDED: "Debugger:SourcesAdded", 1.30 + 1.31 + // When a source is shown in the source editor. 1.32 + SOURCE_SHOWN: "Debugger:EditorSourceShown", 1.33 + SOURCE_ERROR_SHOWN: "Debugger:EditorSourceErrorShown", 1.34 + 1.35 + // When the editor has shown a source and set the line / column position 1.36 + EDITOR_LOCATION_SET: "Debugger:EditorLocationSet", 1.37 + 1.38 + // When scopes, variables, properties and watch expressions are fetched and 1.39 + // displayed in the variables view. 1.40 + FETCHED_SCOPES: "Debugger:FetchedScopes", 1.41 + FETCHED_VARIABLES: "Debugger:FetchedVariables", 1.42 + FETCHED_PROPERTIES: "Debugger:FetchedProperties", 1.43 + FETCHED_BUBBLE_PROPERTIES: "Debugger:FetchedBubbleProperties", 1.44 + FETCHED_WATCH_EXPRESSIONS: "Debugger:FetchedWatchExpressions", 1.45 + 1.46 + // When a breakpoint has been added or removed on the debugger server. 1.47 + BREAKPOINT_ADDED: "Debugger:BreakpointAdded", 1.48 + BREAKPOINT_REMOVED: "Debugger:BreakpointRemoved", 1.49 + 1.50 + // When a breakpoint has been shown or hidden in the source editor. 1.51 + BREAKPOINT_SHOWN: "Debugger:BreakpointShown", 1.52 + BREAKPOINT_HIDDEN: "Debugger:BreakpointHidden", 1.53 + 1.54 + // When a conditional breakpoint's popup is showing or hiding. 1.55 + CONDITIONAL_BREAKPOINT_POPUP_SHOWING: "Debugger:ConditionalBreakpointPopupShowing", 1.56 + CONDITIONAL_BREAKPOINT_POPUP_HIDING: "Debugger:ConditionalBreakpointPopupHiding", 1.57 + 1.58 + // When event listeners are fetched or event breakpoints are updated. 1.59 + EVENT_LISTENERS_FETCHED: "Debugger:EventListenersFetched", 1.60 + EVENT_BREAKPOINTS_UPDATED: "Debugger:EventBreakpointsUpdated", 1.61 + 1.62 + // When a file search was performed. 1.63 + FILE_SEARCH_MATCH_FOUND: "Debugger:FileSearch:MatchFound", 1.64 + FILE_SEARCH_MATCH_NOT_FOUND: "Debugger:FileSearch:MatchNotFound", 1.65 + 1.66 + // When a function search was performed. 1.67 + FUNCTION_SEARCH_MATCH_FOUND: "Debugger:FunctionSearch:MatchFound", 1.68 + FUNCTION_SEARCH_MATCH_NOT_FOUND: "Debugger:FunctionSearch:MatchNotFound", 1.69 + 1.70 + // When a global text search was performed. 1.71 + GLOBAL_SEARCH_MATCH_FOUND: "Debugger:GlobalSearch:MatchFound", 1.72 + GLOBAL_SEARCH_MATCH_NOT_FOUND: "Debugger:GlobalSearch:MatchNotFound", 1.73 + 1.74 + // After the stackframes are cleared and debugger won't pause anymore. 1.75 + AFTER_FRAMES_CLEARED: "Debugger:AfterFramesCleared", 1.76 + 1.77 + // When the options popup is showing or hiding. 1.78 + OPTIONS_POPUP_SHOWING: "Debugger:OptionsPopupShowing", 1.79 + OPTIONS_POPUP_HIDDEN: "Debugger:OptionsPopupHidden", 1.80 + 1.81 + // When the widgets layout has been changed. 1.82 + LAYOUT_CHANGED: "Debugger:LayoutChanged" 1.83 +}; 1.84 + 1.85 +// Descriptions for what a stack frame represents after the debugger pauses. 1.86 +const FRAME_TYPE = { 1.87 + NORMAL: 0, 1.88 + CONDITIONAL_BREAKPOINT_EVAL: 1, 1.89 + WATCH_EXPRESSIONS_EVAL: 2, 1.90 + PUBLIC_CLIENT_EVAL: 3 1.91 +}; 1.92 + 1.93 +Cu.import("resource://gre/modules/Services.jsm"); 1.94 +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); 1.95 +Cu.import("resource://gre/modules/devtools/event-emitter.js"); 1.96 +Cu.import("resource://gre/modules/Task.jsm"); 1.97 +Cu.import("resource:///modules/devtools/SimpleListWidget.jsm"); 1.98 +Cu.import("resource:///modules/devtools/BreadcrumbsWidget.jsm"); 1.99 +Cu.import("resource:///modules/devtools/SideMenuWidget.jsm"); 1.100 +Cu.import("resource:///modules/devtools/VariablesView.jsm"); 1.101 +Cu.import("resource:///modules/devtools/VariablesViewController.jsm"); 1.102 +Cu.import("resource:///modules/devtools/ViewHelpers.jsm"); 1.103 + 1.104 +const require = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools.require; 1.105 +const promise = require("devtools/toolkit/deprecated-sync-thenables"); 1.106 +const Editor = require("devtools/sourceeditor/editor"); 1.107 +const DebuggerEditor = require("devtools/sourceeditor/debugger.js"); 1.108 +const {Tooltip} = require("devtools/shared/widgets/Tooltip"); 1.109 +const FastListWidget = require("devtools/shared/widgets/FastListWidget"); 1.110 + 1.111 +XPCOMUtils.defineLazyModuleGetter(this, "Parser", 1.112 + "resource:///modules/devtools/Parser.jsm"); 1.113 + 1.114 +XPCOMUtils.defineLazyModuleGetter(this, "devtools", 1.115 + "resource://gre/modules/devtools/Loader.jsm"); 1.116 + 1.117 +XPCOMUtils.defineLazyModuleGetter(this, "DevToolsUtils", 1.118 + "resource://gre/modules/devtools/DevToolsUtils.jsm"); 1.119 + 1.120 +XPCOMUtils.defineLazyModuleGetter(this, "ShortcutUtils", 1.121 + "resource://gre/modules/ShortcutUtils.jsm"); 1.122 + 1.123 +Object.defineProperty(this, "NetworkHelper", { 1.124 + get: function() { 1.125 + return devtools.require("devtools/toolkit/webconsole/network-helper"); 1.126 + }, 1.127 + configurable: true, 1.128 + enumerable: true 1.129 +}); 1.130 + 1.131 +/** 1.132 + * Object defining the debugger controller components. 1.133 + */ 1.134 +let DebuggerController = { 1.135 + /** 1.136 + * Initializes the debugger controller. 1.137 + */ 1.138 + initialize: function() { 1.139 + dumpn("Initializing the DebuggerController"); 1.140 + 1.141 + this.startupDebugger = this.startupDebugger.bind(this); 1.142 + this.shutdownDebugger = this.shutdownDebugger.bind(this); 1.143 + this._onTabNavigated = this._onTabNavigated.bind(this); 1.144 + this._onTabDetached = this._onTabDetached.bind(this); 1.145 + }, 1.146 + 1.147 + /** 1.148 + * Initializes the view. 1.149 + * 1.150 + * @return object 1.151 + * A promise that is resolved when the debugger finishes startup. 1.152 + */ 1.153 + startupDebugger: function() { 1.154 + if (this._startup) { 1.155 + return this._startup; 1.156 + } 1.157 + 1.158 + return this._startup = DebuggerView.initialize(); 1.159 + }, 1.160 + 1.161 + /** 1.162 + * Destroys the view and disconnects the debugger client from the server. 1.163 + * 1.164 + * @return object 1.165 + * A promise that is resolved when the debugger finishes shutdown. 1.166 + */ 1.167 + shutdownDebugger: function() { 1.168 + if (this._shutdown) { 1.169 + return this._shutdown; 1.170 + } 1.171 + 1.172 + return this._shutdown = DebuggerView.destroy().then(() => { 1.173 + DebuggerView.destroy(); 1.174 + this.SourceScripts.disconnect(); 1.175 + this.StackFrames.disconnect(); 1.176 + this.ThreadState.disconnect(); 1.177 + this.Tracer.disconnect(); 1.178 + this.disconnect(); 1.179 + }); 1.180 + }, 1.181 + 1.182 + /** 1.183 + * Initiates remote debugging based on the current target, wiring event 1.184 + * handlers as necessary. 1.185 + * 1.186 + * @return object 1.187 + * A promise that is resolved when the debugger finishes connecting. 1.188 + */ 1.189 + connect: function() { 1.190 + if (this._connection) { 1.191 + return this._connection; 1.192 + } 1.193 + 1.194 + let startedDebugging = promise.defer(); 1.195 + this._connection = startedDebugging.promise; 1.196 + 1.197 + let target = this._target; 1.198 + let { client, form: { chromeDebugger, traceActor, addonActor } } = target; 1.199 + target.on("close", this._onTabDetached); 1.200 + target.on("navigate", this._onTabNavigated); 1.201 + target.on("will-navigate", this._onTabNavigated); 1.202 + this.client = client; 1.203 + 1.204 + if (addonActor) { 1.205 + this._startAddonDebugging(addonActor, startedDebugging.resolve); 1.206 + } else if (target.chrome) { 1.207 + this._startChromeDebugging(chromeDebugger, startedDebugging.resolve); 1.208 + } else { 1.209 + this._startDebuggingTab(startedDebugging.resolve); 1.210 + const startedTracing = promise.defer(); 1.211 + if (Prefs.tracerEnabled && traceActor) { 1.212 + this._startTracingTab(traceActor, startedTracing.resolve); 1.213 + } else { 1.214 + startedTracing.resolve(); 1.215 + } 1.216 + 1.217 + return promise.all([startedDebugging.promise, startedTracing.promise]); 1.218 + } 1.219 + 1.220 + return startedDebugging.promise; 1.221 + }, 1.222 + 1.223 + /** 1.224 + * Disconnects the debugger client and removes event handlers as necessary. 1.225 + */ 1.226 + disconnect: function() { 1.227 + // Return early if the client didn't even have a chance to instantiate. 1.228 + if (!this.client) { 1.229 + return; 1.230 + } 1.231 + 1.232 + this._connection = null; 1.233 + this.client = null; 1.234 + this.activeThread = null; 1.235 + }, 1.236 + 1.237 + /** 1.238 + * Called for each location change in the debugged tab. 1.239 + * 1.240 + * @param string aType 1.241 + * Packet type. 1.242 + * @param object aPacket 1.243 + * Packet received from the server. 1.244 + */ 1.245 + _onTabNavigated: function(aType, aPacket) { 1.246 + switch (aType) { 1.247 + case "will-navigate": { 1.248 + // Reset UI. 1.249 + DebuggerView.handleTabNavigation(); 1.250 + 1.251 + // Discard all the cached sources *before* the target starts navigating. 1.252 + // Sources may be fetched during navigation, in which case we don't 1.253 + // want to hang on to the old source contents. 1.254 + DebuggerController.SourceScripts.clearCache(); 1.255 + DebuggerController.Parser.clearCache(); 1.256 + SourceUtils.clearCache(); 1.257 + 1.258 + // Prevent performing any actions that were scheduled before navigation. 1.259 + clearNamedTimeout("new-source"); 1.260 + clearNamedTimeout("event-breakpoints-update"); 1.261 + clearNamedTimeout("event-listeners-fetch"); 1.262 + break; 1.263 + } 1.264 + case "navigate": { 1.265 + this.ThreadState.handleTabNavigation(); 1.266 + this.StackFrames.handleTabNavigation(); 1.267 + this.SourceScripts.handleTabNavigation(); 1.268 + break; 1.269 + } 1.270 + } 1.271 + }, 1.272 + 1.273 + /** 1.274 + * Called when the debugged tab is closed. 1.275 + */ 1.276 + _onTabDetached: function() { 1.277 + this.shutdownDebugger(); 1.278 + }, 1.279 + 1.280 + /** 1.281 + * Warn if resuming execution produced a wrongOrder error. 1.282 + */ 1.283 + _ensureResumptionOrder: function(aResponse) { 1.284 + if (aResponse.error == "wrongOrder") { 1.285 + DebuggerView.Toolbar.showResumeWarning(aResponse.lastPausedUrl); 1.286 + } 1.287 + }, 1.288 + 1.289 + /** 1.290 + * Sets up a debugging session. 1.291 + * 1.292 + * @param function aCallback 1.293 + * A function to invoke once the client attaches to the active thread. 1.294 + */ 1.295 + _startDebuggingTab: function(aCallback) { 1.296 + this._target.activeTab.attachThread({ 1.297 + useSourceMaps: Prefs.sourceMapsEnabled 1.298 + }, (aResponse, aThreadClient) => { 1.299 + if (!aThreadClient) { 1.300 + Cu.reportError("Couldn't attach to thread: " + aResponse.error); 1.301 + return; 1.302 + } 1.303 + this.activeThread = aThreadClient; 1.304 + 1.305 + this.ThreadState.connect(); 1.306 + this.StackFrames.connect(); 1.307 + this.SourceScripts.connect(); 1.308 + if (aThreadClient.paused) { 1.309 + aThreadClient.resume(this._ensureResumptionOrder); 1.310 + } 1.311 + 1.312 + if (aCallback) { 1.313 + aCallback(); 1.314 + } 1.315 + }); 1.316 + }, 1.317 + 1.318 + /** 1.319 + * Sets up an addon debugging session. 1.320 + * 1.321 + * @param object aAddonActor 1.322 + * The actor for the addon that is being debugged. 1.323 + * @param function aCallback 1.324 + * A function to invoke once the client attaches to the active thread. 1.325 + */ 1.326 + _startAddonDebugging: function(aAddonActor, aCallback) { 1.327 + this.client.attachAddon(aAddonActor, (aResponse) => { 1.328 + return this._startChromeDebugging(aResponse.threadActor, aCallback); 1.329 + }); 1.330 + }, 1.331 + 1.332 + /** 1.333 + * Sets up a chrome debugging session. 1.334 + * 1.335 + * @param object aChromeDebugger 1.336 + * The remote protocol grip of the chrome debugger. 1.337 + * @param function aCallback 1.338 + * A function to invoke once the client attaches to the active thread. 1.339 + */ 1.340 + _startChromeDebugging: function(aChromeDebugger, aCallback) { 1.341 + this.client.attachThread(aChromeDebugger, (aResponse, aThreadClient) => { 1.342 + if (!aThreadClient) { 1.343 + Cu.reportError("Couldn't attach to thread: " + aResponse.error); 1.344 + return; 1.345 + } 1.346 + this.activeThread = aThreadClient; 1.347 + 1.348 + this.ThreadState.connect(); 1.349 + this.StackFrames.connect(); 1.350 + this.SourceScripts.connect(); 1.351 + if (aThreadClient.paused) { 1.352 + aThreadClient.resume(this._ensureResumptionOrder); 1.353 + } 1.354 + 1.355 + if (aCallback) { 1.356 + aCallback(); 1.357 + } 1.358 + }, { useSourceMaps: Prefs.sourceMapsEnabled }); 1.359 + }, 1.360 + 1.361 + /** 1.362 + * Sets up an execution tracing session. 1.363 + * 1.364 + * @param object aTraceActor 1.365 + * The remote protocol grip of the trace actor. 1.366 + * @param function aCallback 1.367 + * A function to invoke once the client attaches to the tracer. 1.368 + */ 1.369 + _startTracingTab: function(aTraceActor, aCallback) { 1.370 + this.client.attachTracer(aTraceActor, (response, traceClient) => { 1.371 + if (!traceClient) { 1.372 + DevToolsUtils.reportException("DebuggerController._startTracingTab", 1.373 + new Error("Failed to attach to tracing actor.")); 1.374 + return; 1.375 + } 1.376 + 1.377 + this.traceClient = traceClient; 1.378 + this.Tracer.connect(); 1.379 + 1.380 + if (aCallback) { 1.381 + aCallback(); 1.382 + } 1.383 + }); 1.384 + }, 1.385 + 1.386 + /** 1.387 + * Detach and reattach to the thread actor with useSourceMaps true, blow 1.388 + * away old sources and get them again. 1.389 + */ 1.390 + reconfigureThread: function(aUseSourceMaps) { 1.391 + this.activeThread.reconfigure({ useSourceMaps: aUseSourceMaps }, aResponse => { 1.392 + if (aResponse.error) { 1.393 + let msg = "Couldn't reconfigure thread: " + aResponse.message; 1.394 + Cu.reportError(msg); 1.395 + dumpn(msg); 1.396 + return; 1.397 + } 1.398 + 1.399 + // Reset the view and fetch all the sources again. 1.400 + DebuggerView.handleTabNavigation(); 1.401 + this.SourceScripts.handleTabNavigation(); 1.402 + 1.403 + // Update the stack frame list. 1.404 + if (this.activeThread.paused) { 1.405 + this.activeThread._clearFrames(); 1.406 + this.activeThread.fillFrames(CALL_STACK_PAGE_SIZE); 1.407 + } 1.408 + }); 1.409 + }, 1.410 + 1.411 + _startup: null, 1.412 + _shutdown: null, 1.413 + _connection: null, 1.414 + client: null, 1.415 + activeThread: null 1.416 +}; 1.417 + 1.418 +/** 1.419 + * ThreadState keeps the UI up to date with the state of the 1.420 + * thread (paused/attached/etc.). 1.421 + */ 1.422 +function ThreadState() { 1.423 + this._update = this._update.bind(this); 1.424 +} 1.425 + 1.426 +ThreadState.prototype = { 1.427 + get activeThread() DebuggerController.activeThread, 1.428 + 1.429 + /** 1.430 + * Connect to the current thread client. 1.431 + */ 1.432 + connect: function() { 1.433 + dumpn("ThreadState is connecting..."); 1.434 + this.activeThread.addListener("paused", this._update); 1.435 + this.activeThread.addListener("resumed", this._update); 1.436 + this.activeThread.pauseOnExceptions(Prefs.pauseOnExceptions, 1.437 + Prefs.ignoreCaughtExceptions); 1.438 + this.handleTabNavigation(); 1.439 + }, 1.440 + 1.441 + /** 1.442 + * Disconnect from the client. 1.443 + */ 1.444 + disconnect: function() { 1.445 + if (!this.activeThread) { 1.446 + return; 1.447 + } 1.448 + dumpn("ThreadState is disconnecting..."); 1.449 + this.activeThread.removeListener("paused", this._update); 1.450 + this.activeThread.removeListener("resumed", this._update); 1.451 + }, 1.452 + 1.453 + /** 1.454 + * Handles any initialization on a tab navigation event issued by the client. 1.455 + */ 1.456 + handleTabNavigation: function() { 1.457 + if (!this.activeThread) { 1.458 + return; 1.459 + } 1.460 + dumpn("Handling tab navigation in the ThreadState"); 1.461 + this._update(); 1.462 + }, 1.463 + 1.464 + /** 1.465 + * Update the UI after a thread state change. 1.466 + */ 1.467 + _update: function(aEvent) { 1.468 + DebuggerView.Toolbar.toggleResumeButtonState(this.activeThread.state); 1.469 + 1.470 + if (gTarget && (aEvent == "paused" || aEvent == "resumed")) { 1.471 + gTarget.emit("thread-" + aEvent); 1.472 + } 1.473 + } 1.474 +}; 1.475 + 1.476 +/** 1.477 + * Keeps the stack frame list up-to-date, using the thread client's 1.478 + * stack frame cache. 1.479 + */ 1.480 +function StackFrames() { 1.481 + this._onPaused = this._onPaused.bind(this); 1.482 + this._onResumed = this._onResumed.bind(this); 1.483 + this._onFrames = this._onFrames.bind(this); 1.484 + this._onFramesCleared = this._onFramesCleared.bind(this); 1.485 + this._onBlackBoxChange = this._onBlackBoxChange.bind(this); 1.486 + this._onPrettyPrintChange = this._onPrettyPrintChange.bind(this); 1.487 + this._afterFramesCleared = this._afterFramesCleared.bind(this); 1.488 + this.evaluate = this.evaluate.bind(this); 1.489 +} 1.490 + 1.491 +StackFrames.prototype = { 1.492 + get activeThread() DebuggerController.activeThread, 1.493 + currentFrameDepth: -1, 1.494 + _currentFrameDescription: FRAME_TYPE.NORMAL, 1.495 + _syncedWatchExpressions: null, 1.496 + _currentWatchExpressions: null, 1.497 + _currentBreakpointLocation: null, 1.498 + _currentEvaluation: null, 1.499 + _currentException: null, 1.500 + _currentReturnedValue: null, 1.501 + 1.502 + /** 1.503 + * Connect to the current thread client. 1.504 + */ 1.505 + connect: function() { 1.506 + dumpn("StackFrames is connecting..."); 1.507 + this.activeThread.addListener("paused", this._onPaused); 1.508 + this.activeThread.addListener("resumed", this._onResumed); 1.509 + this.activeThread.addListener("framesadded", this._onFrames); 1.510 + this.activeThread.addListener("framescleared", this._onFramesCleared); 1.511 + this.activeThread.addListener("blackboxchange", this._onBlackBoxChange); 1.512 + this.activeThread.addListener("prettyprintchange", this._onPrettyPrintChange); 1.513 + this.handleTabNavigation(); 1.514 + }, 1.515 + 1.516 + /** 1.517 + * Disconnect from the client. 1.518 + */ 1.519 + disconnect: function() { 1.520 + if (!this.activeThread) { 1.521 + return; 1.522 + } 1.523 + dumpn("StackFrames is disconnecting..."); 1.524 + this.activeThread.removeListener("paused", this._onPaused); 1.525 + this.activeThread.removeListener("resumed", this._onResumed); 1.526 + this.activeThread.removeListener("framesadded", this._onFrames); 1.527 + this.activeThread.removeListener("framescleared", this._onFramesCleared); 1.528 + this.activeThread.removeListener("blackboxchange", this._onBlackBoxChange); 1.529 + this.activeThread.removeListener("prettyprintchange", this._onPrettyPrintChange); 1.530 + clearNamedTimeout("frames-cleared"); 1.531 + }, 1.532 + 1.533 + /** 1.534 + * Handles any initialization on a tab navigation event issued by the client. 1.535 + */ 1.536 + handleTabNavigation: function() { 1.537 + dumpn("Handling tab navigation in the StackFrames"); 1.538 + // Nothing to do here yet. 1.539 + }, 1.540 + 1.541 + /** 1.542 + * Handler for the thread client's paused notification. 1.543 + * 1.544 + * @param string aEvent 1.545 + * The name of the notification ("paused" in this case). 1.546 + * @param object aPacket 1.547 + * The response packet. 1.548 + */ 1.549 + _onPaused: function(aEvent, aPacket) { 1.550 + switch (aPacket.why.type) { 1.551 + // If paused by a breakpoint, store the breakpoint location. 1.552 + case "breakpoint": 1.553 + this._currentBreakpointLocation = aPacket.frame.where; 1.554 + break; 1.555 + // If paused by a client evaluation, store the evaluated value. 1.556 + case "clientEvaluated": 1.557 + this._currentEvaluation = aPacket.why.frameFinished; 1.558 + break; 1.559 + // If paused by an exception, store the exception value. 1.560 + case "exception": 1.561 + this._currentException = aPacket.why.exception; 1.562 + break; 1.563 + // If paused while stepping out of a frame, store the returned value or 1.564 + // thrown exception. 1.565 + case "resumeLimit": 1.566 + if (!aPacket.why.frameFinished) { 1.567 + break; 1.568 + } else if (aPacket.why.frameFinished.throw) { 1.569 + this._currentException = aPacket.why.frameFinished.throw; 1.570 + } else if (aPacket.why.frameFinished.return) { 1.571 + this._currentReturnedValue = aPacket.why.frameFinished.return; 1.572 + } 1.573 + break; 1.574 + } 1.575 + 1.576 + this.activeThread.fillFrames(CALL_STACK_PAGE_SIZE); 1.577 + DebuggerView.editor.focus(); 1.578 + }, 1.579 + 1.580 + /** 1.581 + * Handler for the thread client's resumed notification. 1.582 + */ 1.583 + _onResumed: function() { 1.584 + // Prepare the watch expression evaluation string for the next pause. 1.585 + if (this._currentFrameDescription != FRAME_TYPE.WATCH_EXPRESSIONS_EVAL) { 1.586 + this._currentWatchExpressions = this._syncedWatchExpressions; 1.587 + } 1.588 + }, 1.589 + 1.590 + /** 1.591 + * Handler for the thread client's framesadded notification. 1.592 + */ 1.593 + _onFrames: function() { 1.594 + // Ignore useless notifications. 1.595 + if (!this.activeThread || !this.activeThread.cachedFrames.length) { 1.596 + return; 1.597 + } 1.598 + 1.599 + let waitForNextPause = false; 1.600 + let breakLocation = this._currentBreakpointLocation; 1.601 + let watchExpressions = this._currentWatchExpressions; 1.602 + let client = DebuggerController.activeThread.client; 1.603 + 1.604 + // We moved conditional breakpoint handling to the server, but 1.605 + // need to support it in the client for a while until most of the 1.606 + // server code in production is updated with it. bug 990137 is 1.607 + // filed to mark this code to be removed. 1.608 + if (!client.mainRoot.traits.conditionalBreakpoints) { 1.609 + // Conditional breakpoints are { breakpoint, expression } tuples. The 1.610 + // boolean evaluation of the expression decides if the active thread 1.611 + // automatically resumes execution or not. 1.612 + if (breakLocation) { 1.613 + // Make sure a breakpoint actually exists at the specified url and line. 1.614 + let breakpointPromise = DebuggerController.Breakpoints._getAdded(breakLocation); 1.615 + if (breakpointPromise) { 1.616 + breakpointPromise.then(({ conditionalExpression: e }) => { if (e) { 1.617 + // Evaluating the current breakpoint's conditional expression will 1.618 + // cause the stack frames to be cleared and active thread to pause, 1.619 + // sending a 'clientEvaluated' packed and adding the frames again. 1.620 + this.evaluate(e, { depth: 0, meta: FRAME_TYPE.CONDITIONAL_BREAKPOINT_EVAL }); 1.621 + waitForNextPause = true; 1.622 + }}); 1.623 + } 1.624 + } 1.625 + // We'll get our evaluation of the current breakpoint's conditional 1.626 + // expression the next time the thread client pauses... 1.627 + if (waitForNextPause) { 1.628 + return; 1.629 + } 1.630 + if (this._currentFrameDescription == FRAME_TYPE.CONDITIONAL_BREAKPOINT_EVAL) { 1.631 + this._currentFrameDescription = FRAME_TYPE.NORMAL; 1.632 + // If the breakpoint's conditional expression evaluation is falsy, 1.633 + // automatically resume execution. 1.634 + if (VariablesView.isFalsy({ value: this._currentEvaluation.return })) { 1.635 + this.activeThread.resume(DebuggerController._ensureResumptionOrder); 1.636 + return; 1.637 + } 1.638 + } 1.639 + } 1.640 + 1.641 + // Watch expressions are evaluated in the context of the topmost frame, 1.642 + // and the results are displayed in the variables view. 1.643 + // TODO: handle all of this server-side: Bug 832470, comment 14. 1.644 + if (watchExpressions) { 1.645 + // Evaluation causes the stack frames to be cleared and active thread to 1.646 + // pause, sending a 'clientEvaluated' packet and adding the frames again. 1.647 + this.evaluate(watchExpressions, { depth: 0, meta: FRAME_TYPE.WATCH_EXPRESSIONS_EVAL }); 1.648 + waitForNextPause = true; 1.649 + } 1.650 + // We'll get our evaluation of the current watch expressions the next time 1.651 + // the thread client pauses... 1.652 + if (waitForNextPause) { 1.653 + return; 1.654 + } 1.655 + if (this._currentFrameDescription == FRAME_TYPE.WATCH_EXPRESSIONS_EVAL) { 1.656 + this._currentFrameDescription = FRAME_TYPE.NORMAL; 1.657 + // If an error was thrown during the evaluation of the watch expressions, 1.658 + // then at least one expression evaluation could not be performed. So 1.659 + // remove the most recent watch expression and try again. 1.660 + if (this._currentEvaluation.throw) { 1.661 + DebuggerView.WatchExpressions.removeAt(0); 1.662 + DebuggerController.StackFrames.syncWatchExpressions(); 1.663 + return; 1.664 + } 1.665 + } 1.666 + 1.667 + // Make sure the debugger view panes are visible, then refill the frames. 1.668 + DebuggerView.showInstrumentsPane(); 1.669 + this._refillFrames(); 1.670 + 1.671 + // No additional processing is necessary for this stack frame. 1.672 + if (this._currentFrameDescription != FRAME_TYPE.NORMAL) { 1.673 + this._currentFrameDescription = FRAME_TYPE.NORMAL; 1.674 + } 1.675 + }, 1.676 + 1.677 + /** 1.678 + * Fill the StackFrames view with the frames we have in the cache, compressing 1.679 + * frames which have black boxed sources into single frames. 1.680 + */ 1.681 + _refillFrames: function() { 1.682 + // Make sure all the previous stackframes are removed before re-adding them. 1.683 + DebuggerView.StackFrames.empty(); 1.684 + 1.685 + for (let frame of this.activeThread.cachedFrames) { 1.686 + let { depth, where: { url, line }, source } = frame; 1.687 + let isBlackBoxed = source ? this.activeThread.source(source).isBlackBoxed : false; 1.688 + let location = NetworkHelper.convertToUnicode(unescape(url)); 1.689 + let title = StackFrameUtils.getFrameTitle(frame); 1.690 + DebuggerView.StackFrames.addFrame(title, location, line, depth, isBlackBoxed); 1.691 + } 1.692 + 1.693 + DebuggerView.StackFrames.selectedDepth = Math.max(this.currentFrameDepth, 0); 1.694 + DebuggerView.StackFrames.dirty = this.activeThread.moreFrames; 1.695 + }, 1.696 + 1.697 + /** 1.698 + * Handler for the thread client's framescleared notification. 1.699 + */ 1.700 + _onFramesCleared: function() { 1.701 + switch (this._currentFrameDescription) { 1.702 + case FRAME_TYPE.NORMAL: 1.703 + this._currentEvaluation = null; 1.704 + this._currentException = null; 1.705 + this._currentReturnedValue = null; 1.706 + break; 1.707 + case FRAME_TYPE.CONDITIONAL_BREAKPOINT_EVAL: 1.708 + this._currentBreakpointLocation = null; 1.709 + break; 1.710 + case FRAME_TYPE.WATCH_EXPRESSIONS_EVAL: 1.711 + this._currentWatchExpressions = null; 1.712 + break; 1.713 + } 1.714 + 1.715 + // After each frame step (in, over, out), framescleared is fired, which 1.716 + // forces the UI to be emptied and rebuilt on framesadded. Most of the times 1.717 + // this is not necessary, and will result in a brief redraw flicker. 1.718 + // To avoid it, invalidate the UI only after a short time if necessary. 1.719 + setNamedTimeout("frames-cleared", FRAME_STEP_CLEAR_DELAY, this._afterFramesCleared); 1.720 + }, 1.721 + 1.722 + /** 1.723 + * Handler for the debugger's blackboxchange notification. 1.724 + */ 1.725 + _onBlackBoxChange: function() { 1.726 + if (this.activeThread.state == "paused") { 1.727 + // Hack to avoid selecting the topmost frame after blackboxing a source. 1.728 + this.currentFrameDepth = NaN; 1.729 + this._refillFrames(); 1.730 + } 1.731 + }, 1.732 + 1.733 + /** 1.734 + * Handler for the debugger's prettyprintchange notification. 1.735 + */ 1.736 + _onPrettyPrintChange: function() { 1.737 + // Makes sure the selected source remains selected 1.738 + // after the fillFrames is called. 1.739 + const source = DebuggerView.Sources.selectedValue; 1.740 + if (this.activeThread.state == "paused") { 1.741 + this.activeThread.fillFrames( 1.742 + CALL_STACK_PAGE_SIZE, 1.743 + () => DebuggerView.Sources.selectedValue = source); 1.744 + } 1.745 + }, 1.746 + 1.747 + /** 1.748 + * Called soon after the thread client's framescleared notification. 1.749 + */ 1.750 + _afterFramesCleared: function() { 1.751 + // Ignore useless notifications. 1.752 + if (this.activeThread.cachedFrames.length) { 1.753 + return; 1.754 + } 1.755 + DebuggerView.editor.clearDebugLocation(); 1.756 + DebuggerView.StackFrames.empty(); 1.757 + DebuggerView.Sources.unhighlightBreakpoint(); 1.758 + DebuggerView.WatchExpressions.toggleContents(true); 1.759 + DebuggerView.Variables.empty(0); 1.760 + 1.761 + window.emit(EVENTS.AFTER_FRAMES_CLEARED); 1.762 + }, 1.763 + 1.764 + /** 1.765 + * Marks the stack frame at the specified depth as selected and updates the 1.766 + * properties view with the stack frame's data. 1.767 + * 1.768 + * @param number aDepth 1.769 + * The depth of the frame in the stack. 1.770 + */ 1.771 + selectFrame: function(aDepth) { 1.772 + // Make sure the frame at the specified depth exists first. 1.773 + let frame = this.activeThread.cachedFrames[this.currentFrameDepth = aDepth]; 1.774 + if (!frame) { 1.775 + return; 1.776 + } 1.777 + 1.778 + // Check if the frame does not represent the evaluation of debuggee code. 1.779 + let { environment, where } = frame; 1.780 + if (!environment) { 1.781 + return; 1.782 + } 1.783 + 1.784 + // Don't change the editor's location if the execution was paused by a 1.785 + // public client evaluation. This is useful for adding overlays on 1.786 + // top of the editor, like a variable inspection popup. 1.787 + let isClientEval = this._currentFrameDescription == FRAME_TYPE.PUBLIC_CLIENT_EVAL; 1.788 + let isPopupShown = DebuggerView.VariableBubble.contentsShown(); 1.789 + if (!isClientEval && !isPopupShown) { 1.790 + // Move the editor's caret to the proper url and line. 1.791 + DebuggerView.setEditorLocation(where.url, where.line); 1.792 + // Highlight the breakpoint at the specified url and line if it exists. 1.793 + DebuggerView.Sources.highlightBreakpoint(where, { noEditorUpdate: true }); 1.794 + } 1.795 + 1.796 + // Don't display the watch expressions textbox inputs in the pane. 1.797 + DebuggerView.WatchExpressions.toggleContents(false); 1.798 + 1.799 + // Start recording any added variables or properties in any scope and 1.800 + // clear existing scopes to create each one dynamically. 1.801 + DebuggerView.Variables.empty(); 1.802 + 1.803 + // If watch expressions evaluation results are available, create a scope 1.804 + // to contain all the values. 1.805 + if (this._syncedWatchExpressions && aDepth == 0) { 1.806 + let label = L10N.getStr("watchExpressionsScopeLabel"); 1.807 + let scope = DebuggerView.Variables.addScope(label); 1.808 + 1.809 + // Customize the scope for holding watch expressions evaluations. 1.810 + scope.descriptorTooltip = false; 1.811 + scope.contextMenuId = "debuggerWatchExpressionsContextMenu"; 1.812 + scope.separatorStr = L10N.getStr("watchExpressionsSeparatorLabel"); 1.813 + scope.switch = DebuggerView.WatchExpressions.switchExpression; 1.814 + scope.delete = DebuggerView.WatchExpressions.deleteExpression; 1.815 + 1.816 + // The evaluation hasn't thrown, so fetch and add the returned results. 1.817 + this._fetchWatchExpressions(scope, this._currentEvaluation.return); 1.818 + 1.819 + // The watch expressions scope is always automatically expanded. 1.820 + scope.expand(); 1.821 + } 1.822 + 1.823 + do { 1.824 + // Create a scope to contain all the inspected variables in the 1.825 + // current environment. 1.826 + let label = StackFrameUtils.getScopeLabel(environment); 1.827 + let scope = DebuggerView.Variables.addScope(label); 1.828 + let innermost = environment == frame.environment; 1.829 + 1.830 + // Handle special additions to the innermost scope. 1.831 + if (innermost) { 1.832 + this._insertScopeFrameReferences(scope, frame); 1.833 + } 1.834 + 1.835 + // Handle the expansion of the scope, lazily populating it with the 1.836 + // variables in the current environment. 1.837 + DebuggerView.Variables.controller.addExpander(scope, environment); 1.838 + 1.839 + // The innermost scope is always automatically expanded, because it 1.840 + // contains the variables in the current stack frame which are likely to 1.841 + // be inspected. 1.842 + if (innermost) { 1.843 + scope.expand(); 1.844 + } 1.845 + } while ((environment = environment.parent)); 1.846 + 1.847 + // Signal that scope environments have been shown. 1.848 + window.emit(EVENTS.FETCHED_SCOPES); 1.849 + }, 1.850 + 1.851 + /** 1.852 + * Loads more stack frames from the debugger server cache. 1.853 + */ 1.854 + addMoreFrames: function() { 1.855 + this.activeThread.fillFrames( 1.856 + this.activeThread.cachedFrames.length + CALL_STACK_PAGE_SIZE); 1.857 + }, 1.858 + 1.859 + /** 1.860 + * Evaluate an expression in the context of the selected frame. 1.861 + * 1.862 + * @param string aExpression 1.863 + * The expression to evaluate. 1.864 + * @param object aOptions [optional] 1.865 + * Additional options for this client evaluation: 1.866 + * - depth: the frame depth used for evaluation, 0 being the topmost. 1.867 + * - meta: some meta-description for what this evaluation represents. 1.868 + * @return object 1.869 + * A promise that is resolved when the evaluation finishes, 1.870 + * or rejected if there was no stack frame available or some 1.871 + * other error occurred. 1.872 + */ 1.873 + evaluate: function(aExpression, aOptions = {}) { 1.874 + let depth = "depth" in aOptions ? aOptions.depth : this.currentFrameDepth; 1.875 + let frame = this.activeThread.cachedFrames[depth]; 1.876 + if (frame == null) { 1.877 + return promise.reject(new Error("No stack frame available.")); 1.878 + } 1.879 + 1.880 + let deferred = promise.defer(); 1.881 + 1.882 + this.activeThread.addOneTimeListener("paused", (aEvent, aPacket) => { 1.883 + let { type, frameFinished } = aPacket.why; 1.884 + if (type == "clientEvaluated") { 1.885 + if (!("terminated" in frameFinished)) { 1.886 + deferred.resolve(frameFinished); 1.887 + } else { 1.888 + deferred.reject(new Error("The execution was abruptly terminated.")); 1.889 + } 1.890 + } else { 1.891 + deferred.reject(new Error("Active thread paused unexpectedly.")); 1.892 + } 1.893 + }); 1.894 + 1.895 + let meta = "meta" in aOptions ? aOptions.meta : FRAME_TYPE.PUBLIC_CLIENT_EVAL; 1.896 + this._currentFrameDescription = meta; 1.897 + this.activeThread.eval(frame.actor, aExpression); 1.898 + 1.899 + return deferred.promise; 1.900 + }, 1.901 + 1.902 + /** 1.903 + * Add nodes for special frame references in the innermost scope. 1.904 + * 1.905 + * @param Scope aScope 1.906 + * The scope where the references will be placed into. 1.907 + * @param object aFrame 1.908 + * The frame to get some references from. 1.909 + */ 1.910 + _insertScopeFrameReferences: function(aScope, aFrame) { 1.911 + // Add any thrown exception. 1.912 + if (this._currentException) { 1.913 + let excRef = aScope.addItem("<exception>", { value: this._currentException }); 1.914 + DebuggerView.Variables.controller.addExpander(excRef, this._currentException); 1.915 + } 1.916 + // Add any returned value. 1.917 + if (this._currentReturnedValue) { 1.918 + let retRef = aScope.addItem("<return>", { value: this._currentReturnedValue }); 1.919 + DebuggerView.Variables.controller.addExpander(retRef, this._currentReturnedValue); 1.920 + } 1.921 + // Add "this". 1.922 + if (aFrame.this) { 1.923 + let thisRef = aScope.addItem("this", { value: aFrame.this }); 1.924 + DebuggerView.Variables.controller.addExpander(thisRef, aFrame.this); 1.925 + } 1.926 + }, 1.927 + 1.928 + /** 1.929 + * Adds the watch expressions evaluation results to a scope in the view. 1.930 + * 1.931 + * @param Scope aScope 1.932 + * The scope where the watch expressions will be placed into. 1.933 + * @param object aExp 1.934 + * The grip of the evaluation results. 1.935 + */ 1.936 + _fetchWatchExpressions: function(aScope, aExp) { 1.937 + // Fetch the expressions only once. 1.938 + if (aScope._fetched) { 1.939 + return; 1.940 + } 1.941 + aScope._fetched = true; 1.942 + 1.943 + // Add nodes for every watch expression in scope. 1.944 + this.activeThread.pauseGrip(aExp).getPrototypeAndProperties(aResponse => { 1.945 + let ownProperties = aResponse.ownProperties; 1.946 + let totalExpressions = DebuggerView.WatchExpressions.itemCount; 1.947 + 1.948 + for (let i = 0; i < totalExpressions; i++) { 1.949 + let name = DebuggerView.WatchExpressions.getString(i); 1.950 + let expVal = ownProperties[i].value; 1.951 + let expRef = aScope.addItem(name, ownProperties[i]); 1.952 + DebuggerView.Variables.controller.addExpander(expRef, expVal); 1.953 + 1.954 + // Revert some of the custom watch expressions scope presentation flags, 1.955 + // so that they don't propagate to child items. 1.956 + expRef.switch = null; 1.957 + expRef.delete = null; 1.958 + expRef.descriptorTooltip = true; 1.959 + expRef.separatorStr = L10N.getStr("variablesSeparatorLabel"); 1.960 + } 1.961 + 1.962 + // Signal that watch expressions have been fetched. 1.963 + window.emit(EVENTS.FETCHED_WATCH_EXPRESSIONS); 1.964 + }); 1.965 + }, 1.966 + 1.967 + /** 1.968 + * Updates a list of watch expressions to evaluate on each pause. 1.969 + * TODO: handle all of this server-side: Bug 832470, comment 14. 1.970 + */ 1.971 + syncWatchExpressions: function() { 1.972 + let list = DebuggerView.WatchExpressions.getAllStrings(); 1.973 + 1.974 + // Sanity check all watch expressions before syncing them. To avoid 1.975 + // having the whole watch expressions array throw because of a single 1.976 + // faulty expression, simply convert it to a string describing the error. 1.977 + // There's no other information necessary to be offered in such cases. 1.978 + let sanitizedExpressions = list.map(aString => { 1.979 + // Reflect.parse throws when it encounters a syntax error. 1.980 + try { 1.981 + Parser.reflectionAPI.parse(aString); 1.982 + return aString; // Watch expression can be executed safely. 1.983 + } catch (e) { 1.984 + return "\"" + e.name + ": " + e.message + "\""; // Syntax error. 1.985 + } 1.986 + }); 1.987 + 1.988 + if (sanitizedExpressions.length) { 1.989 + this._syncedWatchExpressions = 1.990 + this._currentWatchExpressions = 1.991 + "[" + 1.992 + sanitizedExpressions.map(aString => 1.993 + "eval(\"" + 1.994 + "try {" + 1.995 + // Make sure all quotes are escaped in the expression's syntax, 1.996 + // and add a newline after the statement to avoid comments 1.997 + // breaking the code integrity inside the eval block. 1.998 + aString.replace(/"/g, "\\$&") + "\" + " + "'\\n'" + " + \"" + 1.999 + "} catch (e) {" + 1.1000 + "e.name + ': ' + e.message;" + // TODO: Bug 812765, 812764. 1.1001 + "}" + 1.1002 + "\")" 1.1003 + ).join(",") + 1.1004 + "]"; 1.1005 + } else { 1.1006 + this._syncedWatchExpressions = 1.1007 + this._currentWatchExpressions = null; 1.1008 + } 1.1009 + 1.1010 + this.currentFrameDepth = -1; 1.1011 + this._onFrames(); 1.1012 + } 1.1013 +}; 1.1014 + 1.1015 +/** 1.1016 + * Keeps the source script list up-to-date, using the thread client's 1.1017 + * source script cache. 1.1018 + */ 1.1019 +function SourceScripts() { 1.1020 + this._onNewGlobal = this._onNewGlobal.bind(this); 1.1021 + this._onNewSource = this._onNewSource.bind(this); 1.1022 + this._onSourcesAdded = this._onSourcesAdded.bind(this); 1.1023 + this._onBlackBoxChange = this._onBlackBoxChange.bind(this); 1.1024 + this._onPrettyPrintChange = this._onPrettyPrintChange.bind(this); 1.1025 +} 1.1026 + 1.1027 +SourceScripts.prototype = { 1.1028 + get activeThread() DebuggerController.activeThread, 1.1029 + get debuggerClient() DebuggerController.client, 1.1030 + _cache: new Map(), 1.1031 + 1.1032 + /** 1.1033 + * Connect to the current thread client. 1.1034 + */ 1.1035 + connect: function() { 1.1036 + dumpn("SourceScripts is connecting..."); 1.1037 + this.debuggerClient.addListener("newGlobal", this._onNewGlobal); 1.1038 + this.debuggerClient.addListener("newSource", this._onNewSource); 1.1039 + this.activeThread.addListener("blackboxchange", this._onBlackBoxChange); 1.1040 + this.activeThread.addListener("prettyprintchange", this._onPrettyPrintChange); 1.1041 + this.handleTabNavigation(); 1.1042 + }, 1.1043 + 1.1044 + /** 1.1045 + * Disconnect from the client. 1.1046 + */ 1.1047 + disconnect: function() { 1.1048 + if (!this.activeThread) { 1.1049 + return; 1.1050 + } 1.1051 + dumpn("SourceScripts is disconnecting..."); 1.1052 + this.debuggerClient.removeListener("newGlobal", this._onNewGlobal); 1.1053 + this.debuggerClient.removeListener("newSource", this._onNewSource); 1.1054 + this.activeThread.removeListener("blackboxchange", this._onBlackBoxChange); 1.1055 + this.activeThread.addListener("prettyprintchange", this._onPrettyPrintChange); 1.1056 + }, 1.1057 + 1.1058 + /** 1.1059 + * Clears all the cached source contents. 1.1060 + */ 1.1061 + clearCache: function() { 1.1062 + this._cache.clear(); 1.1063 + }, 1.1064 + 1.1065 + /** 1.1066 + * Handles any initialization on a tab navigation event issued by the client. 1.1067 + */ 1.1068 + handleTabNavigation: function() { 1.1069 + if (!this.activeThread) { 1.1070 + return; 1.1071 + } 1.1072 + dumpn("Handling tab navigation in the SourceScripts"); 1.1073 + 1.1074 + // Retrieve the list of script sources known to the server from before 1.1075 + // the client was ready to handle "newSource" notifications. 1.1076 + this.activeThread.getSources(this._onSourcesAdded); 1.1077 + }, 1.1078 + 1.1079 + /** 1.1080 + * Handler for the debugger client's unsolicited newGlobal notification. 1.1081 + */ 1.1082 + _onNewGlobal: function(aNotification, aPacket) { 1.1083 + // TODO: bug 806775, update the globals list using aPacket.hostAnnotations 1.1084 + // from bug 801084. 1.1085 + }, 1.1086 + 1.1087 + /** 1.1088 + * Handler for the debugger client's unsolicited newSource notification. 1.1089 + */ 1.1090 + _onNewSource: function(aNotification, aPacket) { 1.1091 + // Ignore bogus scripts, e.g. generated from 'clientEvaluate' packets. 1.1092 + if (NEW_SOURCE_IGNORED_URLS.indexOf(aPacket.source.url) != -1) { 1.1093 + return; 1.1094 + } 1.1095 + 1.1096 + // Add the source in the debugger view sources container. 1.1097 + DebuggerView.Sources.addSource(aPacket.source, { staged: false }); 1.1098 + 1.1099 + // Select this source if it's the preferred one. 1.1100 + let preferredValue = DebuggerView.Sources.preferredValue; 1.1101 + if (aPacket.source.url == preferredValue) { 1.1102 + DebuggerView.Sources.selectedValue = preferredValue; 1.1103 + } 1.1104 + // ..or the first entry if there's none selected yet after a while 1.1105 + else { 1.1106 + setNamedTimeout("new-source", NEW_SOURCE_DISPLAY_DELAY, () => { 1.1107 + // If after a certain delay the preferred source still wasn't received, 1.1108 + // just give up on waiting and display the first entry. 1.1109 + if (!DebuggerView.Sources.selectedValue) { 1.1110 + DebuggerView.Sources.selectedIndex = 0; 1.1111 + } 1.1112 + }); 1.1113 + } 1.1114 + 1.1115 + // If there are any stored breakpoints for this source, display them again, 1.1116 + // both in the editor and the breakpoints pane. 1.1117 + DebuggerController.Breakpoints.updateEditorBreakpoints(); 1.1118 + DebuggerController.Breakpoints.updatePaneBreakpoints(); 1.1119 + 1.1120 + // Make sure the events listeners are up to date. 1.1121 + if (DebuggerView.instrumentsPaneTab == "events-tab") { 1.1122 + DebuggerController.Breakpoints.DOM.scheduleEventListenersFetch(); 1.1123 + } 1.1124 + 1.1125 + // Signal that a new source has been added. 1.1126 + window.emit(EVENTS.NEW_SOURCE); 1.1127 + }, 1.1128 + 1.1129 + /** 1.1130 + * Callback for the debugger's active thread getSources() method. 1.1131 + */ 1.1132 + _onSourcesAdded: function(aResponse) { 1.1133 + if (aResponse.error) { 1.1134 + let msg = "Error getting sources: " + aResponse.message; 1.1135 + Cu.reportError(msg); 1.1136 + dumpn(msg); 1.1137 + return; 1.1138 + } 1.1139 + 1.1140 + if (aResponse.sources.length === 0) { 1.1141 + DebuggerView.Sources.emptyText = L10N.getStr("noSourcesText"); 1.1142 + window.emit(EVENTS.SOURCES_ADDED); 1.1143 + return; 1.1144 + } 1.1145 + 1.1146 + // Add all the sources in the debugger view sources container. 1.1147 + for (let source of aResponse.sources) { 1.1148 + // Ignore bogus scripts, e.g. generated from 'clientEvaluate' packets. 1.1149 + if (NEW_SOURCE_IGNORED_URLS.indexOf(source.url) == -1) { 1.1150 + DebuggerView.Sources.addSource(source, { staged: true }); 1.1151 + } 1.1152 + } 1.1153 + 1.1154 + // Flushes all the prepared sources into the sources container. 1.1155 + DebuggerView.Sources.commit({ sorted: true }); 1.1156 + 1.1157 + // Select the preferred source if it exists and was part of the response. 1.1158 + let preferredValue = DebuggerView.Sources.preferredValue; 1.1159 + if (DebuggerView.Sources.containsValue(preferredValue)) { 1.1160 + DebuggerView.Sources.selectedValue = preferredValue; 1.1161 + } 1.1162 + // ..or the first entry if there's no one selected yet. 1.1163 + else if (!DebuggerView.Sources.selectedValue) { 1.1164 + DebuggerView.Sources.selectedIndex = 0; 1.1165 + } 1.1166 + 1.1167 + // If there are any stored breakpoints for the sources, display them again, 1.1168 + // both in the editor and the breakpoints pane. 1.1169 + DebuggerController.Breakpoints.updateEditorBreakpoints(); 1.1170 + DebuggerController.Breakpoints.updatePaneBreakpoints(); 1.1171 + 1.1172 + // Signal that sources have been added. 1.1173 + window.emit(EVENTS.SOURCES_ADDED); 1.1174 + }, 1.1175 + 1.1176 + /** 1.1177 + * Handler for the debugger client's 'blackboxchange' notification. 1.1178 + */ 1.1179 + _onBlackBoxChange: function (aEvent, { url, isBlackBoxed }) { 1.1180 + const item = DebuggerView.Sources.getItemByValue(url); 1.1181 + if (item) { 1.1182 + if (isBlackBoxed) { 1.1183 + item.target.classList.add("black-boxed"); 1.1184 + } else { 1.1185 + item.target.classList.remove("black-boxed"); 1.1186 + } 1.1187 + } 1.1188 + DebuggerView.Sources.updateToolbarButtonsState(); 1.1189 + DebuggerView.maybeShowBlackBoxMessage(); 1.1190 + }, 1.1191 + 1.1192 + /** 1.1193 + * Set the black boxed status of the given source. 1.1194 + * 1.1195 + * @param Object aSource 1.1196 + * The source form. 1.1197 + * @param bool aBlackBoxFlag 1.1198 + * True to black box the source, false to un-black box it. 1.1199 + * @returns Promise 1.1200 + * A promize that resolves to [aSource, isBlackBoxed] or rejects to 1.1201 + * [aSource, error]. 1.1202 + */ 1.1203 + setBlackBoxing: function(aSource, aBlackBoxFlag) { 1.1204 + const sourceClient = this.activeThread.source(aSource); 1.1205 + const deferred = promise.defer(); 1.1206 + 1.1207 + sourceClient[aBlackBoxFlag ? "blackBox" : "unblackBox"](aPacket => { 1.1208 + const { error, message } = aPacket; 1.1209 + if (error) { 1.1210 + let msg = "Couldn't toggle black boxing for " + aSource.url + ": " + message; 1.1211 + dumpn(msg); 1.1212 + Cu.reportError(msg); 1.1213 + deferred.reject([aSource, msg]); 1.1214 + } else { 1.1215 + deferred.resolve([aSource, sourceClient.isBlackBoxed]); 1.1216 + } 1.1217 + }); 1.1218 + 1.1219 + return deferred.promise; 1.1220 + }, 1.1221 + 1.1222 + /** 1.1223 + * Toggle the pretty printing of a source's text. All subsequent calls to 1.1224 + * |getText| will return the pretty-toggled text. Nothing will happen for 1.1225 + * non-javascript files. 1.1226 + * 1.1227 + * @param Object aSource 1.1228 + * The source form from the RDP. 1.1229 + * @returns Promise 1.1230 + * A promise that resolves to [aSource, prettyText] or rejects to 1.1231 + * [aSource, error]. 1.1232 + */ 1.1233 + togglePrettyPrint: function(aSource) { 1.1234 + // Only attempt to pretty print JavaScript sources. 1.1235 + if (!SourceUtils.isJavaScript(aSource.url, aSource.contentType)) { 1.1236 + return promise.reject([aSource, "Can't prettify non-javascript files."]); 1.1237 + } 1.1238 + 1.1239 + const sourceClient = this.activeThread.source(aSource); 1.1240 + const wantPretty = !sourceClient.isPrettyPrinted; 1.1241 + 1.1242 + // Only use the existing promise if it is pretty printed. 1.1243 + let textPromise = this._cache.get(aSource.url); 1.1244 + if (textPromise && textPromise.pretty === wantPretty) { 1.1245 + return textPromise; 1.1246 + } 1.1247 + 1.1248 + const deferred = promise.defer(); 1.1249 + deferred.promise.pretty = wantPretty; 1.1250 + this._cache.set(aSource.url, deferred.promise); 1.1251 + 1.1252 + const afterToggle = ({ error, message, source: text, contentType }) => { 1.1253 + if (error) { 1.1254 + // Revert the rejected promise from the cache, so that the original 1.1255 + // source's text may be shown when the source is selected. 1.1256 + this._cache.set(aSource.url, textPromise); 1.1257 + deferred.reject([aSource, message || error]); 1.1258 + return; 1.1259 + } 1.1260 + deferred.resolve([aSource, text, contentType]); 1.1261 + }; 1.1262 + 1.1263 + if (wantPretty) { 1.1264 + sourceClient.prettyPrint(Prefs.editorTabSize, afterToggle); 1.1265 + } else { 1.1266 + sourceClient.disablePrettyPrint(afterToggle); 1.1267 + } 1.1268 + 1.1269 + return deferred.promise; 1.1270 + }, 1.1271 + 1.1272 + /** 1.1273 + * Handler for the debugger's prettyprintchange notification. 1.1274 + */ 1.1275 + _onPrettyPrintChange: function(aEvent, { url }) { 1.1276 + // Remove the cached source AST from the Parser, to avoid getting 1.1277 + // wrong locations when searching for functions. 1.1278 + DebuggerController.Parser.clearSource(url); 1.1279 + }, 1.1280 + 1.1281 + /** 1.1282 + * Gets a specified source's text. 1.1283 + * 1.1284 + * @param object aSource 1.1285 + * The source object coming from the active thread. 1.1286 + * @param function aOnTimeout [optional] 1.1287 + * Function called when the source text takes a long time to fetch, 1.1288 + * but not necessarily failing. Long fetch times don't cause the 1.1289 + * rejection of the returned promise. 1.1290 + * @param number aDelay [optional] 1.1291 + * The amount of time it takes to consider a source slow to fetch. 1.1292 + * If unspecified, it defaults to a predefined value. 1.1293 + * @return object 1.1294 + * A promise that is resolved after the source text has been fetched. 1.1295 + */ 1.1296 + getText: function(aSource, aOnTimeout, aDelay = FETCH_SOURCE_RESPONSE_DELAY) { 1.1297 + // Fetch the source text only once. 1.1298 + let textPromise = this._cache.get(aSource.url); 1.1299 + if (textPromise) { 1.1300 + return textPromise; 1.1301 + } 1.1302 + 1.1303 + let deferred = promise.defer(); 1.1304 + this._cache.set(aSource.url, deferred.promise); 1.1305 + 1.1306 + // If the source text takes a long time to fetch, invoke a callback. 1.1307 + if (aOnTimeout) { 1.1308 + var fetchTimeout = window.setTimeout(() => aOnTimeout(aSource), aDelay); 1.1309 + } 1.1310 + 1.1311 + // Get the source text from the active thread. 1.1312 + this.activeThread.source(aSource) 1.1313 + .source(({ error, message, source: text, contentType }) => { 1.1314 + if (aOnTimeout) { 1.1315 + window.clearTimeout(fetchTimeout); 1.1316 + } 1.1317 + if (error) { 1.1318 + deferred.reject([aSource, message || error]); 1.1319 + } else { 1.1320 + deferred.resolve([aSource, text, contentType]); 1.1321 + } 1.1322 + }); 1.1323 + 1.1324 + return deferred.promise; 1.1325 + }, 1.1326 + 1.1327 + /** 1.1328 + * Starts fetching all the sources, silently. 1.1329 + * 1.1330 + * @param array aUrls 1.1331 + * The urls for the sources to fetch. If fetching a source's text 1.1332 + * takes too long, it will be discarded. 1.1333 + * @return object 1.1334 + * A promise that is resolved after source texts have been fetched. 1.1335 + */ 1.1336 + getTextForSources: function(aUrls) { 1.1337 + let deferred = promise.defer(); 1.1338 + let pending = new Set(aUrls); 1.1339 + let fetched = []; 1.1340 + 1.1341 + // Can't use promise.all, because if one fetch operation is rejected, then 1.1342 + // everything is considered rejected, thus no other subsequent source will 1.1343 + // be getting fetched. We don't want that. Something like Q's allSettled 1.1344 + // would work like a charm here. 1.1345 + 1.1346 + // Try to fetch as many sources as possible. 1.1347 + for (let url of aUrls) { 1.1348 + let sourceItem = DebuggerView.Sources.getItemByValue(url); 1.1349 + let sourceForm = sourceItem.attachment.source; 1.1350 + this.getText(sourceForm, onTimeout).then(onFetch, onError); 1.1351 + } 1.1352 + 1.1353 + /* Called if fetching a source takes too long. */ 1.1354 + function onTimeout(aSource) { 1.1355 + onError([aSource]); 1.1356 + } 1.1357 + 1.1358 + /* Called if fetching a source finishes successfully. */ 1.1359 + function onFetch([aSource, aText, aContentType]) { 1.1360 + // If fetching the source has previously timed out, discard it this time. 1.1361 + if (!pending.has(aSource.url)) { 1.1362 + return; 1.1363 + } 1.1364 + pending.delete(aSource.url); 1.1365 + fetched.push([aSource.url, aText, aContentType]); 1.1366 + maybeFinish(); 1.1367 + } 1.1368 + 1.1369 + /* Called if fetching a source failed because of an error. */ 1.1370 + function onError([aSource, aError]) { 1.1371 + pending.delete(aSource.url); 1.1372 + maybeFinish(); 1.1373 + } 1.1374 + 1.1375 + /* Called every time something interesting happens while fetching sources. */ 1.1376 + function maybeFinish() { 1.1377 + if (pending.size == 0) { 1.1378 + // Sort the fetched sources alphabetically by their url. 1.1379 + deferred.resolve(fetched.sort(([aFirst], [aSecond]) => aFirst > aSecond)); 1.1380 + } 1.1381 + } 1.1382 + 1.1383 + return deferred.promise; 1.1384 + } 1.1385 +}; 1.1386 + 1.1387 +/** 1.1388 + * Tracer update the UI according to the messages exchanged with the tracer 1.1389 + * actor. 1.1390 + */ 1.1391 +function Tracer() { 1.1392 + this._trace = null; 1.1393 + this._idCounter = 0; 1.1394 + this.onTraces = this.onTraces.bind(this); 1.1395 +} 1.1396 + 1.1397 +Tracer.prototype = { 1.1398 + get client() { 1.1399 + return DebuggerController.client; 1.1400 + }, 1.1401 + 1.1402 + get traceClient() { 1.1403 + return DebuggerController.traceClient; 1.1404 + }, 1.1405 + 1.1406 + get tracing() { 1.1407 + return !!this._trace; 1.1408 + }, 1.1409 + 1.1410 + /** 1.1411 + * Hooks up the debugger controller with the tracer client. 1.1412 + */ 1.1413 + connect: function() { 1.1414 + this._stack = []; 1.1415 + this.client.addListener("traces", this.onTraces); 1.1416 + }, 1.1417 + 1.1418 + /** 1.1419 + * Disconnects the debugger controller from the tracer client. Any further 1.1420 + * communcation with the tracer actor will not have any effect on the UI. 1.1421 + */ 1.1422 + disconnect: function() { 1.1423 + this._stack = null; 1.1424 + this.client.removeListener("traces", this.onTraces); 1.1425 + }, 1.1426 + 1.1427 + /** 1.1428 + * Instructs the tracer actor to start tracing. 1.1429 + */ 1.1430 + startTracing: function(aCallback = () => {}) { 1.1431 + DebuggerView.Tracer.selectTab(); 1.1432 + if (this.tracing) { 1.1433 + return; 1.1434 + } 1.1435 + this._trace = "dbg.trace" + Math.random(); 1.1436 + this.traceClient.startTrace([ 1.1437 + "name", 1.1438 + "location", 1.1439 + "parameterNames", 1.1440 + "depth", 1.1441 + "arguments", 1.1442 + "return", 1.1443 + "throw", 1.1444 + "yield" 1.1445 + ], this._trace, (aResponse) => { 1.1446 + const { error } = aResponse; 1.1447 + if (error) { 1.1448 + DevToolsUtils.reportException("Tracer.prototype.startTracing", error); 1.1449 + this._trace = null; 1.1450 + } 1.1451 + 1.1452 + aCallback(aResponse); 1.1453 + }); 1.1454 + }, 1.1455 + 1.1456 + /** 1.1457 + * Instructs the tracer actor to stop tracing. 1.1458 + */ 1.1459 + stopTracing: function(aCallback = () => {}) { 1.1460 + if (!this.tracing) { 1.1461 + return; 1.1462 + } 1.1463 + this.traceClient.stopTrace(this._trace, aResponse => { 1.1464 + const { error } = aResponse; 1.1465 + if (error) { 1.1466 + DevToolsUtils.reportException("Tracer.prototype.stopTracing", error); 1.1467 + } 1.1468 + 1.1469 + this._trace = null; 1.1470 + aCallback(aResponse); 1.1471 + }); 1.1472 + }, 1.1473 + 1.1474 + onTraces: function (aEvent, { traces }) { 1.1475 + const tracesLength = traces.length; 1.1476 + let tracesToShow; 1.1477 + if (tracesLength > TracerView.MAX_TRACES) { 1.1478 + tracesToShow = traces.slice(tracesLength - TracerView.MAX_TRACES, 1.1479 + tracesLength); 1.1480 + DebuggerView.Tracer.empty(); 1.1481 + this._stack.splice(0, this._stack.length); 1.1482 + } else { 1.1483 + tracesToShow = traces; 1.1484 + } 1.1485 + 1.1486 + for (let t of tracesToShow) { 1.1487 + if (t.type == "enteredFrame") { 1.1488 + this._onCall(t); 1.1489 + } else { 1.1490 + this._onReturn(t); 1.1491 + } 1.1492 + } 1.1493 + 1.1494 + DebuggerView.Tracer.commit(); 1.1495 + }, 1.1496 + 1.1497 + /** 1.1498 + * Callback for handling a new call frame. 1.1499 + */ 1.1500 + _onCall: function({ name, location, parameterNames, depth, arguments: args }) { 1.1501 + const item = { 1.1502 + name: name, 1.1503 + location: location, 1.1504 + id: this._idCounter++ 1.1505 + }; 1.1506 + 1.1507 + this._stack.push(item); 1.1508 + DebuggerView.Tracer.addTrace({ 1.1509 + type: "call", 1.1510 + name: name, 1.1511 + location: location, 1.1512 + depth: depth, 1.1513 + parameterNames: parameterNames, 1.1514 + arguments: args, 1.1515 + frameId: item.id 1.1516 + }); 1.1517 + }, 1.1518 + 1.1519 + /** 1.1520 + * Callback for handling an exited frame. 1.1521 + */ 1.1522 + _onReturn: function(aPacket) { 1.1523 + if (!this._stack.length) { 1.1524 + return; 1.1525 + } 1.1526 + 1.1527 + const { name, id, location } = this._stack.pop(); 1.1528 + DebuggerView.Tracer.addTrace({ 1.1529 + type: aPacket.why, 1.1530 + name: name, 1.1531 + location: location, 1.1532 + depth: aPacket.depth, 1.1533 + frameId: id, 1.1534 + returnVal: aPacket.return || aPacket.throw || aPacket.yield 1.1535 + }); 1.1536 + }, 1.1537 + 1.1538 + /** 1.1539 + * Create an object which has the same interface as a normal object client, 1.1540 + * but since we already have all the information for an object that we will 1.1541 + * ever get (the server doesn't create actors when tracing, just firehoses 1.1542 + * data and forgets about it) just return the data immdiately. 1.1543 + * 1.1544 + * @param Object aObject 1.1545 + * The tracer object "grip" (more like a limited snapshot). 1.1546 + * @returns Object 1.1547 + * The synchronous client object. 1.1548 + */ 1.1549 + syncGripClient: function(aObject) { 1.1550 + return { 1.1551 + get isFrozen() { return aObject.frozen; }, 1.1552 + get isSealed() { return aObject.sealed; }, 1.1553 + get isExtensible() { return aObject.extensible; }, 1.1554 + 1.1555 + get ownProperties() { return aObject.ownProperties; }, 1.1556 + get prototype() { return null; }, 1.1557 + 1.1558 + getParameterNames: callback => callback(aObject), 1.1559 + getPrototypeAndProperties: callback => callback(aObject), 1.1560 + getPrototype: callback => callback(aObject), 1.1561 + 1.1562 + getOwnPropertyNames: (callback) => { 1.1563 + callback({ 1.1564 + ownPropertyNames: aObject.ownProperties 1.1565 + ? Object.keys(aObject.ownProperties) 1.1566 + : [] 1.1567 + }); 1.1568 + }, 1.1569 + 1.1570 + getProperty: (property, callback) => { 1.1571 + callback({ 1.1572 + descriptor: aObject.ownProperties 1.1573 + ? aObject.ownProperties[property] 1.1574 + : null 1.1575 + }); 1.1576 + }, 1.1577 + 1.1578 + getDisplayString: callback => callback("[object " + aObject.class + "]"), 1.1579 + 1.1580 + getScope: callback => callback({ 1.1581 + error: "scopeNotAvailable", 1.1582 + message: "Cannot get scopes for traced objects" 1.1583 + }) 1.1584 + }; 1.1585 + }, 1.1586 + 1.1587 + /** 1.1588 + * Wraps object snapshots received from the tracer server so that we can 1.1589 + * differentiate them from long living object grips from the debugger server 1.1590 + * in the variables view. 1.1591 + * 1.1592 + * @param Object aObject 1.1593 + * The object snapshot from the tracer actor. 1.1594 + */ 1.1595 + WrappedObject: function(aObject) { 1.1596 + this.object = aObject; 1.1597 + } 1.1598 +}; 1.1599 + 1.1600 +/** 1.1601 + * Handles breaking on event listeners in the currently debugged target. 1.1602 + */ 1.1603 +function EventListeners() { 1.1604 + this._onEventListeners = this._onEventListeners.bind(this); 1.1605 +} 1.1606 + 1.1607 +EventListeners.prototype = { 1.1608 + /** 1.1609 + * A list of event names on which the debuggee will automatically pause 1.1610 + * when invoked. 1.1611 + */ 1.1612 + activeEventNames: [], 1.1613 + 1.1614 + /** 1.1615 + * Updates the list of events types with listeners that, when invoked, 1.1616 + * will automatically pause the debuggee. The respective events are 1.1617 + * retrieved from the UI. 1.1618 + */ 1.1619 + scheduleEventBreakpointsUpdate: function() { 1.1620 + // Make sure we're not sending a batch of closely repeated requests. 1.1621 + // This can easily happen when toggling all events of a certain type. 1.1622 + setNamedTimeout("event-breakpoints-update", 0, () => { 1.1623 + this.activeEventNames = DebuggerView.EventListeners.getCheckedEvents(); 1.1624 + gThreadClient.pauseOnDOMEvents(this.activeEventNames); 1.1625 + 1.1626 + // Notify that event breakpoints were added/removed on the server. 1.1627 + window.emit(EVENTS.EVENT_BREAKPOINTS_UPDATED); 1.1628 + }); 1.1629 + }, 1.1630 + 1.1631 + /** 1.1632 + * Fetches the currently attached event listeners from the debugee. 1.1633 + */ 1.1634 + scheduleEventListenersFetch: function() { 1.1635 + let getListeners = aCallback => gThreadClient.eventListeners(aResponse => { 1.1636 + if (aResponse.error) { 1.1637 + let msg = "Error getting event listeners: " + aResponse.message; 1.1638 + DevToolsUtils.reportException("scheduleEventListenersFetch", msg); 1.1639 + return; 1.1640 + } 1.1641 + 1.1642 + let outstandingListenersDefinitionSite = aResponse.listeners.map(aListener => { 1.1643 + const deferred = promise.defer(); 1.1644 + 1.1645 + gThreadClient.pauseGrip(aListener.function).getDefinitionSite(aResponse => { 1.1646 + if (aResponse.error) { 1.1647 + const msg = "Error getting function definition site: " + aResponse.message; 1.1648 + DevToolsUtils.reportException("scheduleEventListenersFetch", msg); 1.1649 + } else { 1.1650 + aListener.function.url = aResponse.url; 1.1651 + } 1.1652 + 1.1653 + deferred.resolve(aListener); 1.1654 + }); 1.1655 + 1.1656 + return deferred.promise; 1.1657 + }); 1.1658 + 1.1659 + promise.all(outstandingListenersDefinitionSite).then(aListeners => { 1.1660 + this._onEventListeners(aListeners); 1.1661 + 1.1662 + // Notify that event listeners were fetched and shown in the view, 1.1663 + // and callback to resume the active thread if necessary. 1.1664 + window.emit(EVENTS.EVENT_LISTENERS_FETCHED); 1.1665 + aCallback && aCallback(); 1.1666 + }); 1.1667 + }); 1.1668 + 1.1669 + // Make sure we're not sending a batch of closely repeated requests. 1.1670 + // This can easily happen whenever new sources are fetched. 1.1671 + setNamedTimeout("event-listeners-fetch", FETCH_EVENT_LISTENERS_DELAY, () => { 1.1672 + if (gThreadClient.state != "paused") { 1.1673 + gThreadClient.interrupt(() => getListeners(() => gThreadClient.resume())); 1.1674 + } else { 1.1675 + getListeners(); 1.1676 + } 1.1677 + }); 1.1678 + }, 1.1679 + 1.1680 + /** 1.1681 + * Callback for a debugger's successful active thread eventListeners() call. 1.1682 + */ 1.1683 + _onEventListeners: function(aListeners) { 1.1684 + // Add all the listeners in the debugger view event linsteners container. 1.1685 + for (let listener of aListeners) { 1.1686 + DebuggerView.EventListeners.addListener(listener, { staged: true }); 1.1687 + } 1.1688 + 1.1689 + // Flushes all the prepared events into the event listeners container. 1.1690 + DebuggerView.EventListeners.commit(); 1.1691 + } 1.1692 +}; 1.1693 + 1.1694 +/** 1.1695 + * Handles all the breakpoints in the current debugger. 1.1696 + */ 1.1697 +function Breakpoints() { 1.1698 + this._onEditorBreakpointAdd = this._onEditorBreakpointAdd.bind(this); 1.1699 + this._onEditorBreakpointRemove = this._onEditorBreakpointRemove.bind(this); 1.1700 + this.addBreakpoint = this.addBreakpoint.bind(this); 1.1701 + this.removeBreakpoint = this.removeBreakpoint.bind(this); 1.1702 +} 1.1703 + 1.1704 +Breakpoints.prototype = { 1.1705 + /** 1.1706 + * A map of breakpoint promises as tracked by the debugger frontend. 1.1707 + * The keys consist of a string representation of the breakpoint location. 1.1708 + */ 1.1709 + _added: new Map(), 1.1710 + _removing: new Map(), 1.1711 + _disabled: new Map(), 1.1712 + 1.1713 + /** 1.1714 + * Adds the source editor breakpoint handlers. 1.1715 + * 1.1716 + * @return object 1.1717 + * A promise that is resolved when the breakpoints finishes initializing. 1.1718 + */ 1.1719 + initialize: function() { 1.1720 + DebuggerView.editor.on("breakpointAdded", this._onEditorBreakpointAdd); 1.1721 + DebuggerView.editor.on("breakpointRemoved", this._onEditorBreakpointRemove); 1.1722 + 1.1723 + // Initialization is synchronous, for now. 1.1724 + return promise.resolve(null); 1.1725 + }, 1.1726 + 1.1727 + /** 1.1728 + * Removes the source editor breakpoint handlers & all the added breakpoints. 1.1729 + * 1.1730 + * @return object 1.1731 + * A promise that is resolved when the breakpoints finishes destroying. 1.1732 + */ 1.1733 + destroy: function() { 1.1734 + DebuggerView.editor.off("breakpointAdded", this._onEditorBreakpointAdd); 1.1735 + DebuggerView.editor.off("breakpointRemoved", this._onEditorBreakpointRemove); 1.1736 + 1.1737 + return this.removeAllBreakpoints(); 1.1738 + }, 1.1739 + 1.1740 + /** 1.1741 + * Event handler for new breakpoints that come from the editor. 1.1742 + * 1.1743 + * @param number aLine 1.1744 + * Line number where breakpoint was set. 1.1745 + */ 1.1746 + _onEditorBreakpointAdd: function(_, aLine) { 1.1747 + let url = DebuggerView.Sources.selectedValue; 1.1748 + let location = { url: url, line: aLine + 1 }; 1.1749 + 1.1750 + // Initialize the breakpoint, but don't update the editor, since this 1.1751 + // callback is invoked because a breakpoint was added in the editor itself. 1.1752 + this.addBreakpoint(location, { noEditorUpdate: true }).then(aBreakpointClient => { 1.1753 + // If the breakpoint client has an "requestedLocation" attached, then 1.1754 + // the original requested placement for the breakpoint wasn't accepted. 1.1755 + // In this case, we need to update the editor with the new location. 1.1756 + if (aBreakpointClient.requestedLocation) { 1.1757 + DebuggerView.editor.removeBreakpoint(aBreakpointClient.requestedLocation.line - 1); 1.1758 + DebuggerView.editor.addBreakpoint(aBreakpointClient.location.line - 1); 1.1759 + } 1.1760 + // Notify that we've shown a breakpoint in the source editor. 1.1761 + window.emit(EVENTS.BREAKPOINT_SHOWN); 1.1762 + }); 1.1763 + }, 1.1764 + 1.1765 + /** 1.1766 + * Event handler for breakpoints that are removed from the editor. 1.1767 + * 1.1768 + * @param number aLine 1.1769 + * Line number where breakpoint was removed. 1.1770 + */ 1.1771 + _onEditorBreakpointRemove: function(_, aLine) { 1.1772 + let url = DebuggerView.Sources.selectedValue; 1.1773 + let location = { url: url, line: aLine + 1 }; 1.1774 + 1.1775 + // Destroy the breakpoint, but don't update the editor, since this callback 1.1776 + // is invoked because a breakpoint was removed from the editor itself. 1.1777 + this.removeBreakpoint(location, { noEditorUpdate: true }).then(() => { 1.1778 + // Notify that we've hidden a breakpoint in the source editor. 1.1779 + window.emit(EVENTS.BREAKPOINT_HIDDEN); 1.1780 + }); 1.1781 + }, 1.1782 + 1.1783 + /** 1.1784 + * Update the breakpoints in the editor view. This function takes the list of 1.1785 + * breakpoints in the debugger and adds them back into the editor view. 1.1786 + * This is invoked when the selected script is changed, or when new sources 1.1787 + * are received via the _onNewSource and _onSourcesAdded event listeners. 1.1788 + */ 1.1789 + updateEditorBreakpoints: function() { 1.1790 + for (let breakpointPromise of this._addedOrDisabled) { 1.1791 + breakpointPromise.then(aBreakpointClient => { 1.1792 + let currentSourceUrl = DebuggerView.Sources.selectedValue; 1.1793 + let breakpointUrl = aBreakpointClient.location.url; 1.1794 + 1.1795 + // Update the view only if the breakpoint is in the currently shown source. 1.1796 + if (currentSourceUrl == breakpointUrl) { 1.1797 + this._showBreakpoint(aBreakpointClient, { noPaneUpdate: true }); 1.1798 + } 1.1799 + }); 1.1800 + } 1.1801 + }, 1.1802 + 1.1803 + /** 1.1804 + * Update the breakpoints in the pane view. This function takes the list of 1.1805 + * breakpoints in the debugger and adds them back into the breakpoints pane. 1.1806 + * This is invoked when new sources are received via the _onNewSource and 1.1807 + * _onSourcesAdded event listeners. 1.1808 + */ 1.1809 + updatePaneBreakpoints: function() { 1.1810 + for (let breakpointPromise of this._addedOrDisabled) { 1.1811 + breakpointPromise.then(aBreakpointClient => { 1.1812 + let container = DebuggerView.Sources; 1.1813 + let breakpointUrl = aBreakpointClient.location.url; 1.1814 + 1.1815 + // Update the view only if the breakpoint exists in a known source. 1.1816 + if (container.containsValue(breakpointUrl)) { 1.1817 + this._showBreakpoint(aBreakpointClient, { noEditorUpdate: true }); 1.1818 + } 1.1819 + }); 1.1820 + } 1.1821 + }, 1.1822 + 1.1823 + /** 1.1824 + * Add a breakpoint. 1.1825 + * 1.1826 + * @param object aLocation 1.1827 + * The location where you want the breakpoint. 1.1828 + * This object must have two properties: 1.1829 + * - url: the breakpoint's source location. 1.1830 + * - line: the breakpoint's line number. 1.1831 + * It can also have the following optional properties: 1.1832 + * - condition: only pause if this condition evaluates truthy 1.1833 + * @param object aOptions [optional] 1.1834 + * Additional options or flags supported by this operation: 1.1835 + * - openPopup: tells if the expression popup should be shown. 1.1836 + * - noEditorUpdate: tells if you want to skip editor updates. 1.1837 + * - noPaneUpdate: tells if you want to skip breakpoint pane updates. 1.1838 + * @return object 1.1839 + * A promise that is resolved after the breakpoint is added, or 1.1840 + * rejected if there was an error. 1.1841 + */ 1.1842 + addBreakpoint: Task.async(function*(aLocation, aOptions = {}) { 1.1843 + // Make sure a proper location is available. 1.1844 + if (!aLocation) { 1.1845 + throw new Error("Invalid breakpoint location."); 1.1846 + } 1.1847 + let addedPromise, removingPromise; 1.1848 + 1.1849 + // If the breakpoint was already added, or is currently being added at the 1.1850 + // specified location, then return that promise immediately. 1.1851 + if ((addedPromise = this._getAdded(aLocation))) { 1.1852 + return addedPromise; 1.1853 + } 1.1854 + 1.1855 + // If the breakpoint is currently being removed from the specified location, 1.1856 + // then wait for that to finish. 1.1857 + if ((removingPromise = this._getRemoving(aLocation))) { 1.1858 + yield removingPromise; 1.1859 + } 1.1860 + 1.1861 + let deferred = promise.defer(); 1.1862 + 1.1863 + // Remember the breakpoint initialization promise in the store. 1.1864 + let identifier = this.getIdentifier(aLocation); 1.1865 + this._added.set(identifier, deferred.promise); 1.1866 + 1.1867 + // Try adding the breakpoint. 1.1868 + gThreadClient.setBreakpoint(aLocation, Task.async(function*(aResponse, aBreakpointClient) { 1.1869 + // If the breakpoint response has an "actualLocation" attached, then 1.1870 + // the original requested placement for the breakpoint wasn't accepted. 1.1871 + if (aResponse.actualLocation) { 1.1872 + // Remember the initialization promise for the new location instead. 1.1873 + let oldIdentifier = identifier; 1.1874 + let newIdentifier = identifier = this.getIdentifier(aResponse.actualLocation); 1.1875 + this._added.delete(oldIdentifier); 1.1876 + this._added.set(newIdentifier, deferred.promise); 1.1877 + } 1.1878 + 1.1879 + // By default, new breakpoints are always enabled. Disabled breakpoints 1.1880 + // are, in fact, removed from the server but preserved in the frontend, 1.1881 + // so that they may not be forgotten across target navigations. 1.1882 + let disabledPromise = this._disabled.get(identifier); 1.1883 + if (disabledPromise) { 1.1884 + let aPrevBreakpointClient = yield disabledPromise; 1.1885 + let condition = aPrevBreakpointClient.getCondition(); 1.1886 + this._disabled.delete(identifier); 1.1887 + 1.1888 + if (condition) { 1.1889 + aBreakpointClient = yield aBreakpointClient.setCondition( 1.1890 + gThreadClient, 1.1891 + condition 1.1892 + ); 1.1893 + } 1.1894 + } 1.1895 + 1.1896 + if (aResponse.actualLocation) { 1.1897 + // Store the originally requested location in case it's ever needed 1.1898 + // and update the breakpoint client with the actual location. 1.1899 + aBreakpointClient.requestedLocation = aLocation; 1.1900 + aBreakpointClient.location = aResponse.actualLocation; 1.1901 + } 1.1902 + 1.1903 + // Preserve information about the breakpoint's line text, to display it 1.1904 + // in the sources pane without requiring fetching the source (for example, 1.1905 + // after the target navigated). Note that this will get out of sync 1.1906 + // if the source text contents change. 1.1907 + let line = aBreakpointClient.location.line - 1; 1.1908 + aBreakpointClient.text = DebuggerView.editor.getText(line).trim(); 1.1909 + 1.1910 + // Show the breakpoint in the editor and breakpoints pane, and resolve. 1.1911 + this._showBreakpoint(aBreakpointClient, aOptions); 1.1912 + 1.1913 + // Notify that we've added a breakpoint. 1.1914 + window.emit(EVENTS.BREAKPOINT_ADDED, aBreakpointClient); 1.1915 + deferred.resolve(aBreakpointClient); 1.1916 + }.bind(this))); 1.1917 + 1.1918 + return deferred.promise; 1.1919 + }), 1.1920 + 1.1921 + /** 1.1922 + * Remove a breakpoint. 1.1923 + * 1.1924 + * @param object aLocation 1.1925 + * @see DebuggerController.Breakpoints.addBreakpoint 1.1926 + * @param object aOptions [optional] 1.1927 + * @see DebuggerController.Breakpoints.addBreakpoint 1.1928 + * @return object 1.1929 + * A promise that is resolved after the breakpoint is removed, or 1.1930 + * rejected if there was an error. 1.1931 + */ 1.1932 + removeBreakpoint: function(aLocation, aOptions = {}) { 1.1933 + // Make sure a proper location is available. 1.1934 + if (!aLocation) { 1.1935 + return promise.reject(new Error("Invalid breakpoint location.")); 1.1936 + } 1.1937 + 1.1938 + // If the breakpoint was already removed, or has never even been added, 1.1939 + // then return a resolved promise immediately. 1.1940 + let addedPromise = this._getAdded(aLocation); 1.1941 + if (!addedPromise) { 1.1942 + return promise.resolve(aLocation); 1.1943 + } 1.1944 + 1.1945 + // If the breakpoint is currently being removed from the specified location, 1.1946 + // then return that promise immediately. 1.1947 + let removingPromise = this._getRemoving(aLocation); 1.1948 + if (removingPromise) { 1.1949 + return removingPromise; 1.1950 + } 1.1951 + 1.1952 + let deferred = promise.defer(); 1.1953 + 1.1954 + // Remember the breakpoint removal promise in the store. 1.1955 + let identifier = this.getIdentifier(aLocation); 1.1956 + this._removing.set(identifier, deferred.promise); 1.1957 + 1.1958 + // Retrieve the corresponding breakpoint client first. 1.1959 + addedPromise.then(aBreakpointClient => { 1.1960 + // Try removing the breakpoint. 1.1961 + aBreakpointClient.remove(aResponse => { 1.1962 + // If there was an error removing the breakpoint, reject the promise 1.1963 + // and forget about it that the breakpoint may be re-removed later. 1.1964 + if (aResponse.error) { 1.1965 + deferred.reject(aResponse); 1.1966 + return void this._removing.delete(identifier); 1.1967 + } 1.1968 + 1.1969 + // When a breakpoint is removed, the frontend may wish to preserve some 1.1970 + // details about it, so that it can be easily re-added later. In such 1.1971 + // cases, breakpoints are marked and stored as disabled, so that they 1.1972 + // may not be forgotten across target navigations. 1.1973 + if (aOptions.rememberDisabled) { 1.1974 + aBreakpointClient.disabled = true; 1.1975 + this._disabled.set(identifier, promise.resolve(aBreakpointClient)); 1.1976 + } 1.1977 + 1.1978 + // Forget both the initialization and removal promises from the store. 1.1979 + this._added.delete(identifier); 1.1980 + this._removing.delete(identifier); 1.1981 + 1.1982 + // Hide the breakpoint from the editor and breakpoints pane, and resolve. 1.1983 + this._hideBreakpoint(aLocation, aOptions); 1.1984 + 1.1985 + // Notify that we've removed a breakpoint. 1.1986 + window.emit(EVENTS.BREAKPOINT_REMOVED, aLocation); 1.1987 + deferred.resolve(aLocation); 1.1988 + }); 1.1989 + }); 1.1990 + 1.1991 + return deferred.promise; 1.1992 + }, 1.1993 + 1.1994 + /** 1.1995 + * Removes all the currently enabled breakpoints. 1.1996 + * 1.1997 + * @return object 1.1998 + * A promise that is resolved after all breakpoints are removed, or 1.1999 + * rejected if there was an error. 1.2000 + */ 1.2001 + removeAllBreakpoints: function() { 1.2002 + /* Gets an array of all the existing breakpoints promises. */ 1.2003 + let getActiveBreakpoints = (aPromises, aStore = []) => { 1.2004 + for (let [, breakpointPromise] of aPromises) { 1.2005 + aStore.push(breakpointPromise); 1.2006 + } 1.2007 + return aStore; 1.2008 + } 1.2009 + 1.2010 + /* Gets an array of all the removed breakpoints promises. */ 1.2011 + let getRemovedBreakpoints = (aClients, aStore = []) => { 1.2012 + for (let breakpointClient of aClients) { 1.2013 + aStore.push(this.removeBreakpoint(breakpointClient.location)); 1.2014 + } 1.2015 + return aStore; 1.2016 + } 1.2017 + 1.2018 + // First, populate an array of all the currently added breakpoints promises. 1.2019 + // Then, once all the breakpoints clients are retrieved, populate an array 1.2020 + // of all the removed breakpoints promises and wait for their fulfillment. 1.2021 + return promise.all(getActiveBreakpoints(this._added)).then(aBreakpointClients => { 1.2022 + return promise.all(getRemovedBreakpoints(aBreakpointClients)); 1.2023 + }); 1.2024 + }, 1.2025 + 1.2026 + /** 1.2027 + * Update the condition of a breakpoint. 1.2028 + * 1.2029 + * @param object aLocation 1.2030 + * @see DebuggerController.Breakpoints.addBreakpoint 1.2031 + * @param string aClients 1.2032 + * The condition to set on the breakpoint 1.2033 + * @return object 1.2034 + * A promise that will be resolved with the breakpoint client 1.2035 + */ 1.2036 + updateCondition: function(aLocation, aCondition) { 1.2037 + let addedPromise = this._getAdded(aLocation); 1.2038 + if (!addedPromise) { 1.2039 + return promise.reject(new Error('breakpoint does not exist ' + 1.2040 + 'in specified location')); 1.2041 + } 1.2042 + 1.2043 + var promise = addedPromise.then(aBreakpointClient => { 1.2044 + return aBreakpointClient.setCondition(gThreadClient, aCondition); 1.2045 + }); 1.2046 + 1.2047 + // `setCondition` returns a new breakpoint that has the condition, 1.2048 + // so we need to update the store 1.2049 + this._added.set(this.getIdentifier(aLocation), promise); 1.2050 + return promise; 1.2051 + }, 1.2052 + 1.2053 + /** 1.2054 + * Update the editor and breakpoints pane to show a specified breakpoint. 1.2055 + * 1.2056 + * @param object aBreakpointData 1.2057 + * Information about the breakpoint to be shown. 1.2058 + * This object must have the following properties: 1.2059 + * - location: the breakpoint's source location and line number 1.2060 + * - disabled: the breakpoint's disabled state, boolean 1.2061 + * - text: the breakpoint's line text to be displayed 1.2062 + * @param object aOptions [optional] 1.2063 + * @see DebuggerController.Breakpoints.addBreakpoint 1.2064 + */ 1.2065 + _showBreakpoint: function(aBreakpointData, aOptions = {}) { 1.2066 + let currentSourceUrl = DebuggerView.Sources.selectedValue; 1.2067 + let location = aBreakpointData.location; 1.2068 + 1.2069 + // Update the editor if required. 1.2070 + if (!aOptions.noEditorUpdate && !aBreakpointData.disabled) { 1.2071 + if (location.url == currentSourceUrl) { 1.2072 + DebuggerView.editor.addBreakpoint(location.line - 1); 1.2073 + } 1.2074 + } 1.2075 + 1.2076 + // Update the breakpoints pane if required. 1.2077 + if (!aOptions.noPaneUpdate) { 1.2078 + DebuggerView.Sources.addBreakpoint(aBreakpointData, aOptions); 1.2079 + } 1.2080 + }, 1.2081 + 1.2082 + /** 1.2083 + * Update the editor and breakpoints pane to hide a specified breakpoint. 1.2084 + * 1.2085 + * @param object aLocation 1.2086 + * @see DebuggerController.Breakpoints.addBreakpoint 1.2087 + * @param object aOptions [optional] 1.2088 + * @see DebuggerController.Breakpoints.addBreakpoint 1.2089 + */ 1.2090 + _hideBreakpoint: function(aLocation, aOptions = {}) { 1.2091 + let currentSourceUrl = DebuggerView.Sources.selectedValue; 1.2092 + 1.2093 + // Update the editor if required. 1.2094 + if (!aOptions.noEditorUpdate) { 1.2095 + if (aLocation.url == currentSourceUrl) { 1.2096 + DebuggerView.editor.removeBreakpoint(aLocation.line - 1); 1.2097 + } 1.2098 + } 1.2099 + 1.2100 + // Update the breakpoints pane if required. 1.2101 + if (!aOptions.noPaneUpdate) { 1.2102 + DebuggerView.Sources.removeBreakpoint(aLocation); 1.2103 + } 1.2104 + }, 1.2105 + 1.2106 + /** 1.2107 + * Get a Promise for the BreakpointActor client object which is already added 1.2108 + * or currently being added at the given location. 1.2109 + * 1.2110 + * @param object aLocation 1.2111 + * @see DebuggerController.Breakpoints.addBreakpoint 1.2112 + * @return object | null 1.2113 + * A promise that is resolved after the breakpoint is added, or 1.2114 + * null if no breakpoint was found. 1.2115 + */ 1.2116 + _getAdded: function(aLocation) { 1.2117 + return this._added.get(this.getIdentifier(aLocation)); 1.2118 + }, 1.2119 + 1.2120 + /** 1.2121 + * Get a Promise for the BreakpointActor client object which is currently 1.2122 + * being removed from the given location. 1.2123 + * 1.2124 + * @param object aLocation 1.2125 + * @see DebuggerController.Breakpoints.addBreakpoint 1.2126 + * @return object | null 1.2127 + * A promise that is resolved after the breakpoint is removed, or 1.2128 + * null if no breakpoint was found. 1.2129 + */ 1.2130 + _getRemoving: function(aLocation) { 1.2131 + return this._removing.get(this.getIdentifier(aLocation)); 1.2132 + }, 1.2133 + 1.2134 + /** 1.2135 + * Get an identifier string for a given location. Breakpoint promises are 1.2136 + * identified in the store by a string representation of their location. 1.2137 + * 1.2138 + * @param object aLocation 1.2139 + * The location to serialize to a string. 1.2140 + * @return string 1.2141 + * The identifier string. 1.2142 + */ 1.2143 + getIdentifier: function(aLocation) { 1.2144 + return aLocation.url + ":" + aLocation.line; 1.2145 + } 1.2146 +}; 1.2147 + 1.2148 +/** 1.2149 + * Gets all Promises for the BreakpointActor client objects that are 1.2150 + * either enabled (added to the server) or disabled (removed from the server, 1.2151 + * but for which some details are preserved). 1.2152 + */ 1.2153 +Object.defineProperty(Breakpoints.prototype, "_addedOrDisabled", { 1.2154 + get: function* () { 1.2155 + yield* this._added.values(); 1.2156 + yield* this._disabled.values(); 1.2157 + } 1.2158 +}); 1.2159 + 1.2160 +/** 1.2161 + * Localization convenience methods. 1.2162 + */ 1.2163 +let L10N = new ViewHelpers.L10N(DBG_STRINGS_URI); 1.2164 + 1.2165 +/** 1.2166 + * Shortcuts for accessing various debugger preferences. 1.2167 + */ 1.2168 +let Prefs = new ViewHelpers.Prefs("devtools", { 1.2169 + sourcesWidth: ["Int", "debugger.ui.panes-sources-width"], 1.2170 + instrumentsWidth: ["Int", "debugger.ui.panes-instruments-width"], 1.2171 + panesVisibleOnStartup: ["Bool", "debugger.ui.panes-visible-on-startup"], 1.2172 + variablesSortingEnabled: ["Bool", "debugger.ui.variables-sorting-enabled"], 1.2173 + variablesOnlyEnumVisible: ["Bool", "debugger.ui.variables-only-enum-visible"], 1.2174 + variablesSearchboxVisible: ["Bool", "debugger.ui.variables-searchbox-visible"], 1.2175 + pauseOnExceptions: ["Bool", "debugger.pause-on-exceptions"], 1.2176 + ignoreCaughtExceptions: ["Bool", "debugger.ignore-caught-exceptions"], 1.2177 + sourceMapsEnabled: ["Bool", "debugger.source-maps-enabled"], 1.2178 + prettyPrintEnabled: ["Bool", "debugger.pretty-print-enabled"], 1.2179 + autoPrettyPrint: ["Bool", "debugger.auto-pretty-print"], 1.2180 + tracerEnabled: ["Bool", "debugger.tracer"], 1.2181 + editorTabSize: ["Int", "editor.tabsize"] 1.2182 +}); 1.2183 + 1.2184 +/** 1.2185 + * Convenient way of emitting events from the panel window. 1.2186 + */ 1.2187 +EventEmitter.decorate(this); 1.2188 + 1.2189 +/** 1.2190 + * Preliminary setup for the DebuggerController object. 1.2191 + */ 1.2192 +DebuggerController.initialize(); 1.2193 +DebuggerController.Parser = new Parser(); 1.2194 +DebuggerController.ThreadState = new ThreadState(); 1.2195 +DebuggerController.StackFrames = new StackFrames(); 1.2196 +DebuggerController.SourceScripts = new SourceScripts(); 1.2197 +DebuggerController.Breakpoints = new Breakpoints(); 1.2198 +DebuggerController.Breakpoints.DOM = new EventListeners(); 1.2199 +DebuggerController.Tracer = new Tracer(); 1.2200 + 1.2201 +/** 1.2202 + * Export some properties to the global scope for easier access. 1.2203 + */ 1.2204 +Object.defineProperties(window, { 1.2205 + "gTarget": { 1.2206 + get: function() DebuggerController._target 1.2207 + }, 1.2208 + "gHostType": { 1.2209 + get: function() DebuggerView._hostType 1.2210 + }, 1.2211 + "gClient": { 1.2212 + get: function() DebuggerController.client 1.2213 + }, 1.2214 + "gThreadClient": { 1.2215 + get: function() DebuggerController.activeThread 1.2216 + }, 1.2217 + "gCallStackPageSize": { 1.2218 + get: function() CALL_STACK_PAGE_SIZE 1.2219 + } 1.2220 +}); 1.2221 + 1.2222 +/** 1.2223 + * Helper method for debugging. 1.2224 + * @param string 1.2225 + */ 1.2226 +function dumpn(str) { 1.2227 + if (wantLogging) { 1.2228 + dump("DBG-FRONTEND: " + str + "\n"); 1.2229 + } 1.2230 +} 1.2231 + 1.2232 +let wantLogging = Services.prefs.getBoolPref("devtools.debugger.log");