1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/toolkit/devtools/server/actors/script.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,5604 @@ 1.4 +/* -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; js-indent-level: 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 + 1.10 +"use strict"; 1.11 + 1.12 +let B2G_ID = "{3c2e2abc-06d4-11e1-ac3b-374f68613e61}"; 1.13 + 1.14 +let TYPED_ARRAY_CLASSES = ["Uint8Array", "Uint8ClampedArray", "Uint16Array", 1.15 + "Uint32Array", "Int8Array", "Int16Array", "Int32Array", "Float32Array", 1.16 + "Float64Array"]; 1.17 + 1.18 +// Number of items to preview in objects, arrays, maps, sets, lists, 1.19 +// collections, etc. 1.20 +let OBJECT_PREVIEW_MAX_ITEMS = 10; 1.21 + 1.22 +let addonManager = null; 1.23 + 1.24 +/** 1.25 + * This is a wrapper around amIAddonManager.mapURIToAddonID which always returns 1.26 + * false on B2G to avoid loading the add-on manager there and reports any 1.27 + * exceptions rather than throwing so that the caller doesn't have to worry 1.28 + * about them. 1.29 + */ 1.30 +function mapURIToAddonID(uri, id) { 1.31 + if (Services.appinfo.ID == B2G_ID) { 1.32 + return false; 1.33 + } 1.34 + 1.35 + if (!addonManager) { 1.36 + addonManager = Cc["@mozilla.org/addons/integration;1"]. 1.37 + getService(Ci.amIAddonManager); 1.38 + } 1.39 + 1.40 + try { 1.41 + return addonManager.mapURIToAddonID(uri, id); 1.42 + } 1.43 + catch (e) { 1.44 + DevtoolsUtils.reportException("mapURIToAddonID", e); 1.45 + return false; 1.46 + } 1.47 +} 1.48 + 1.49 +/** 1.50 + * BreakpointStore objects keep track of all breakpoints that get set so that we 1.51 + * can reset them when the same script is introduced to the thread again (such 1.52 + * as after a refresh). 1.53 + */ 1.54 +function BreakpointStore() { 1.55 + this._size = 0; 1.56 + 1.57 + // If we have a whole-line breakpoint set at LINE in URL, then 1.58 + // 1.59 + // this._wholeLineBreakpoints[URL][LINE] 1.60 + // 1.61 + // is an object 1.62 + // 1.63 + // { url, line[, actor] } 1.64 + // 1.65 + // where the `actor` property is optional. 1.66 + this._wholeLineBreakpoints = Object.create(null); 1.67 + 1.68 + // If we have a breakpoint set at LINE, COLUMN in URL, then 1.69 + // 1.70 + // this._breakpoints[URL][LINE][COLUMN] 1.71 + // 1.72 + // is an object 1.73 + // 1.74 + // { url, line, column[, actor] } 1.75 + // 1.76 + // where the `actor` property is optional. 1.77 + this._breakpoints = Object.create(null); 1.78 +} 1.79 + 1.80 +BreakpointStore.prototype = { 1.81 + _size: null, 1.82 + get size() { return this._size; }, 1.83 + 1.84 + /** 1.85 + * Add a breakpoint to the breakpoint store. 1.86 + * 1.87 + * @param Object aBreakpoint 1.88 + * The breakpoint to be added (not copied). It is an object with the 1.89 + * following properties: 1.90 + * - url 1.91 + * - line 1.92 + * - column (optional; omission implies that the breakpoint is for 1.93 + * the whole line) 1.94 + * - condition (optional) 1.95 + * - actor (optional) 1.96 + */ 1.97 + addBreakpoint: function (aBreakpoint) { 1.98 + let { url, line, column } = aBreakpoint; 1.99 + let updating = false; 1.100 + 1.101 + if (column != null) { 1.102 + if (!this._breakpoints[url]) { 1.103 + this._breakpoints[url] = []; 1.104 + } 1.105 + if (!this._breakpoints[url][line]) { 1.106 + this._breakpoints[url][line] = []; 1.107 + } 1.108 + this._breakpoints[url][line][column] = aBreakpoint; 1.109 + } else { 1.110 + // Add a breakpoint that breaks on the whole line. 1.111 + if (!this._wholeLineBreakpoints[url]) { 1.112 + this._wholeLineBreakpoints[url] = []; 1.113 + } 1.114 + this._wholeLineBreakpoints[url][line] = aBreakpoint; 1.115 + } 1.116 + 1.117 + this._size++; 1.118 + }, 1.119 + 1.120 + /** 1.121 + * Remove a breakpoint from the breakpoint store. 1.122 + * 1.123 + * @param Object aBreakpoint 1.124 + * The breakpoint to be removed. It is an object with the following 1.125 + * properties: 1.126 + * - url 1.127 + * - line 1.128 + * - column (optional) 1.129 + */ 1.130 + removeBreakpoint: function ({ url, line, column }) { 1.131 + if (column != null) { 1.132 + if (this._breakpoints[url]) { 1.133 + if (this._breakpoints[url][line]) { 1.134 + if (this._breakpoints[url][line][column]) { 1.135 + delete this._breakpoints[url][line][column]; 1.136 + this._size--; 1.137 + 1.138 + // If this was the last breakpoint on this line, delete the line from 1.139 + // `this._breakpoints[url]` as well. Otherwise `_iterLines` will yield 1.140 + // this line even though we no longer have breakpoints on 1.141 + // it. Furthermore, we use Object.keys() instead of just checking 1.142 + // `this._breakpoints[url].length` directly, because deleting 1.143 + // properties from sparse arrays doesn't update the `length` property 1.144 + // like adding them does. 1.145 + if (Object.keys(this._breakpoints[url][line]).length === 0) { 1.146 + delete this._breakpoints[url][line]; 1.147 + } 1.148 + } 1.149 + } 1.150 + } 1.151 + } else { 1.152 + if (this._wholeLineBreakpoints[url]) { 1.153 + if (this._wholeLineBreakpoints[url][line]) { 1.154 + delete this._wholeLineBreakpoints[url][line]; 1.155 + this._size--; 1.156 + } 1.157 + } 1.158 + } 1.159 + }, 1.160 + 1.161 + /** 1.162 + * Get a breakpoint from the breakpoint store. Will throw an error if the 1.163 + * breakpoint is not found. 1.164 + * 1.165 + * @param Object aLocation 1.166 + * The location of the breakpoint you are retrieving. It is an object 1.167 + * with the following properties: 1.168 + * - url 1.169 + * - line 1.170 + * - column (optional) 1.171 + */ 1.172 + getBreakpoint: function (aLocation) { 1.173 + let { url, line, column } = aLocation; 1.174 + dbg_assert(url != null); 1.175 + dbg_assert(line != null); 1.176 + 1.177 + var foundBreakpoint = this.hasBreakpoint(aLocation); 1.178 + if (foundBreakpoint == null) { 1.179 + throw new Error("No breakpoint at url = " + url 1.180 + + ", line = " + line 1.181 + + ", column = " + column); 1.182 + } 1.183 + 1.184 + return foundBreakpoint; 1.185 + }, 1.186 + 1.187 + /** 1.188 + * Checks if the breakpoint store has a requested breakpoint. 1.189 + * 1.190 + * @param Object aLocation 1.191 + * The location of the breakpoint you are retrieving. It is an object 1.192 + * with the following properties: 1.193 + * - url 1.194 + * - line 1.195 + * - column (optional) 1.196 + * @returns The stored breakpoint if it exists, null otherwise. 1.197 + */ 1.198 + hasBreakpoint: function (aLocation) { 1.199 + let { url, line, column } = aLocation; 1.200 + dbg_assert(url != null); 1.201 + dbg_assert(line != null); 1.202 + for (let bp of this.findBreakpoints(aLocation)) { 1.203 + // We will get whole line breakpoints before individual columns, so just 1.204 + // return the first one and if they didn't specify a column then they will 1.205 + // get the whole line breakpoint, and otherwise we will find the correct 1.206 + // one. 1.207 + return bp; 1.208 + } 1.209 + 1.210 + return null; 1.211 + }, 1.212 + 1.213 + /** 1.214 + * Iterate over the breakpoints in this breakpoint store. You can optionally 1.215 + * provide search parameters to filter the set of breakpoints down to those 1.216 + * that match your parameters. 1.217 + * 1.218 + * @param Object aSearchParams 1.219 + * Optional. An object with the following properties: 1.220 + * - url 1.221 + * - line (optional; requires the url property) 1.222 + * - column (optional; requires the line property) 1.223 + */ 1.224 + findBreakpoints: function* (aSearchParams={}) { 1.225 + if (aSearchParams.column != null) { 1.226 + dbg_assert(aSearchParams.line != null); 1.227 + } 1.228 + if (aSearchParams.line != null) { 1.229 + dbg_assert(aSearchParams.url != null); 1.230 + } 1.231 + 1.232 + for (let url of this._iterUrls(aSearchParams.url)) { 1.233 + for (let line of this._iterLines(url, aSearchParams.line)) { 1.234 + // Always yield whole line breakpoints first. See comment in 1.235 + // |BreakpointStore.prototype.hasBreakpoint|. 1.236 + if (aSearchParams.column == null 1.237 + && this._wholeLineBreakpoints[url] 1.238 + && this._wholeLineBreakpoints[url][line]) { 1.239 + yield this._wholeLineBreakpoints[url][line]; 1.240 + } 1.241 + for (let column of this._iterColumns(url, line, aSearchParams.column)) { 1.242 + yield this._breakpoints[url][line][column]; 1.243 + } 1.244 + } 1.245 + } 1.246 + }, 1.247 + 1.248 + _iterUrls: function* (aUrl) { 1.249 + if (aUrl) { 1.250 + if (this._breakpoints[aUrl] || this._wholeLineBreakpoints[aUrl]) { 1.251 + yield aUrl; 1.252 + } 1.253 + } else { 1.254 + for (let url of Object.keys(this._wholeLineBreakpoints)) { 1.255 + yield url; 1.256 + } 1.257 + for (let url of Object.keys(this._breakpoints)) { 1.258 + if (url in this._wholeLineBreakpoints) { 1.259 + continue; 1.260 + } 1.261 + yield url; 1.262 + } 1.263 + } 1.264 + }, 1.265 + 1.266 + _iterLines: function* (aUrl, aLine) { 1.267 + if (aLine != null) { 1.268 + if ((this._wholeLineBreakpoints[aUrl] 1.269 + && this._wholeLineBreakpoints[aUrl][aLine]) 1.270 + || (this._breakpoints[aUrl] && this._breakpoints[aUrl][aLine])) { 1.271 + yield aLine; 1.272 + } 1.273 + } else { 1.274 + const wholeLines = this._wholeLineBreakpoints[aUrl] 1.275 + ? Object.keys(this._wholeLineBreakpoints[aUrl]) 1.276 + : []; 1.277 + const columnLines = this._breakpoints[aUrl] 1.278 + ? Object.keys(this._breakpoints[aUrl]) 1.279 + : []; 1.280 + 1.281 + const lines = wholeLines.concat(columnLines).sort(); 1.282 + 1.283 + let lastLine; 1.284 + for (let line of lines) { 1.285 + if (line === lastLine) { 1.286 + continue; 1.287 + } 1.288 + yield line; 1.289 + lastLine = line; 1.290 + } 1.291 + } 1.292 + }, 1.293 + 1.294 + _iterColumns: function* (aUrl, aLine, aColumn) { 1.295 + if (!this._breakpoints[aUrl] || !this._breakpoints[aUrl][aLine]) { 1.296 + return; 1.297 + } 1.298 + 1.299 + if (aColumn != null) { 1.300 + if (this._breakpoints[aUrl][aLine][aColumn]) { 1.301 + yield aColumn; 1.302 + } 1.303 + } else { 1.304 + for (let column in this._breakpoints[aUrl][aLine]) { 1.305 + yield column; 1.306 + } 1.307 + } 1.308 + }, 1.309 +}; 1.310 + 1.311 +/** 1.312 + * Manages pushing event loops and automatically pops and exits them in the 1.313 + * correct order as they are resolved. 1.314 + * 1.315 + * @param nsIJSInspector inspector 1.316 + * The underlying JS inspector we use to enter and exit nested event 1.317 + * loops. 1.318 + * @param ThreadActor thread 1.319 + * The thread actor instance that owns this EventLoopStack. 1.320 + * @param DebuggerServerConnection connection 1.321 + * The remote protocol connection associated with this event loop stack. 1.322 + * @param Object hooks 1.323 + * An object with the following properties: 1.324 + * - url: The URL string of the debuggee we are spinning an event loop 1.325 + * for. 1.326 + * - preNest: function called before entering a nested event loop 1.327 + * - postNest: function called after exiting a nested event loop 1.328 + */ 1.329 +function EventLoopStack({ inspector, thread, connection, hooks }) { 1.330 + this._inspector = inspector; 1.331 + this._hooks = hooks; 1.332 + this._thread = thread; 1.333 + this._connection = connection; 1.334 +} 1.335 + 1.336 +EventLoopStack.prototype = { 1.337 + /** 1.338 + * The number of nested event loops on the stack. 1.339 + */ 1.340 + get size() { 1.341 + return this._inspector.eventLoopNestLevel; 1.342 + }, 1.343 + 1.344 + /** 1.345 + * The URL of the debuggee who pushed the event loop on top of the stack. 1.346 + */ 1.347 + get lastPausedUrl() { 1.348 + let url = null; 1.349 + if (this.size > 0) { 1.350 + try { 1.351 + url = this._inspector.lastNestRequestor.url 1.352 + } catch (e) { 1.353 + // The tab's URL getter may throw if the tab is destroyed by the time 1.354 + // this code runs, but we don't really care at this point. 1.355 + dumpn(e); 1.356 + } 1.357 + } 1.358 + return url; 1.359 + }, 1.360 + 1.361 + /** 1.362 + * The DebuggerServerConnection of the debugger who pushed the event loop on 1.363 + * top of the stack 1.364 + */ 1.365 + get lastConnection() { 1.366 + return this._inspector.lastNestRequestor._connection; 1.367 + }, 1.368 + 1.369 + /** 1.370 + * Push a new nested event loop onto the stack. 1.371 + * 1.372 + * @returns EventLoop 1.373 + */ 1.374 + push: function () { 1.375 + return new EventLoop({ 1.376 + inspector: this._inspector, 1.377 + thread: this._thread, 1.378 + connection: this._connection, 1.379 + hooks: this._hooks 1.380 + }); 1.381 + } 1.382 +}; 1.383 + 1.384 +/** 1.385 + * An object that represents a nested event loop. It is used as the nest 1.386 + * requestor with nsIJSInspector instances. 1.387 + * 1.388 + * @param nsIJSInspector inspector 1.389 + * The JS Inspector that runs nested event loops. 1.390 + * @param ThreadActor thread 1.391 + * The thread actor that is creating this nested event loop. 1.392 + * @param DebuggerServerConnection connection 1.393 + * The remote protocol connection associated with this event loop. 1.394 + * @param Object hooks 1.395 + * The same hooks object passed into EventLoopStack during its 1.396 + * initialization. 1.397 + */ 1.398 +function EventLoop({ inspector, thread, connection, hooks }) { 1.399 + this._inspector = inspector; 1.400 + this._thread = thread; 1.401 + this._hooks = hooks; 1.402 + this._connection = connection; 1.403 + 1.404 + this.enter = this.enter.bind(this); 1.405 + this.resolve = this.resolve.bind(this); 1.406 +} 1.407 + 1.408 +EventLoop.prototype = { 1.409 + entered: false, 1.410 + resolved: false, 1.411 + get url() { return this._hooks.url; }, 1.412 + 1.413 + /** 1.414 + * Enter this nested event loop. 1.415 + */ 1.416 + enter: function () { 1.417 + let nestData = this._hooks.preNest 1.418 + ? this._hooks.preNest() 1.419 + : null; 1.420 + 1.421 + this.entered = true; 1.422 + this._inspector.enterNestedEventLoop(this); 1.423 + 1.424 + // Keep exiting nested event loops while the last requestor is resolved. 1.425 + if (this._inspector.eventLoopNestLevel > 0) { 1.426 + const { resolved } = this._inspector.lastNestRequestor; 1.427 + if (resolved) { 1.428 + this._inspector.exitNestedEventLoop(); 1.429 + } 1.430 + } 1.431 + 1.432 + dbg_assert(this._thread.state === "running", 1.433 + "Should be in the running state"); 1.434 + 1.435 + if (this._hooks.postNest) { 1.436 + this._hooks.postNest(nestData); 1.437 + } 1.438 + }, 1.439 + 1.440 + /** 1.441 + * Resolve this nested event loop. 1.442 + * 1.443 + * @returns boolean 1.444 + * True if we exited this nested event loop because it was on top of 1.445 + * the stack, false if there is another nested event loop above this 1.446 + * one that hasn't resolved yet. 1.447 + */ 1.448 + resolve: function () { 1.449 + if (!this.entered) { 1.450 + throw new Error("Can't resolve an event loop before it has been entered!"); 1.451 + } 1.452 + if (this.resolved) { 1.453 + throw new Error("Already resolved this nested event loop!"); 1.454 + } 1.455 + this.resolved = true; 1.456 + if (this === this._inspector.lastNestRequestor) { 1.457 + this._inspector.exitNestedEventLoop(); 1.458 + return true; 1.459 + } 1.460 + return false; 1.461 + }, 1.462 +}; 1.463 + 1.464 +/** 1.465 + * JSD2 actors. 1.466 + */ 1.467 +/** 1.468 + * Creates a ThreadActor. 1.469 + * 1.470 + * ThreadActors manage a JSInspector object and manage execution/inspection 1.471 + * of debuggees. 1.472 + * 1.473 + * @param aHooks object 1.474 + * An object with preNest and postNest methods for calling when entering 1.475 + * and exiting a nested event loop. 1.476 + * @param aGlobal object [optional] 1.477 + * An optional (for content debugging only) reference to the content 1.478 + * window. 1.479 + */ 1.480 +function ThreadActor(aHooks, aGlobal) 1.481 +{ 1.482 + this._state = "detached"; 1.483 + this._frameActors = []; 1.484 + this._hooks = aHooks; 1.485 + this.global = aGlobal; 1.486 + // A map of actorID -> actor for breakpoints created and managed by the server. 1.487 + this._hiddenBreakpoints = new Map(); 1.488 + 1.489 + this.findGlobals = this.globalManager.findGlobals.bind(this); 1.490 + this.onNewGlobal = this.globalManager.onNewGlobal.bind(this); 1.491 + this.onNewSource = this.onNewSource.bind(this); 1.492 + this._allEventsListener = this._allEventsListener.bind(this); 1.493 + 1.494 + this._options = { 1.495 + useSourceMaps: false 1.496 + }; 1.497 + 1.498 + this._gripDepth = 0; 1.499 +} 1.500 + 1.501 +/** 1.502 + * The breakpoint store must be shared across instances of ThreadActor so that 1.503 + * page reloads don't blow away all of our breakpoints. 1.504 + */ 1.505 +ThreadActor.breakpointStore = new BreakpointStore(); 1.506 + 1.507 +ThreadActor.prototype = { 1.508 + // Used by the ObjectActor to keep track of the depth of grip() calls. 1.509 + _gripDepth: null, 1.510 + 1.511 + actorPrefix: "context", 1.512 + 1.513 + get state() { return this._state; }, 1.514 + get attached() this.state == "attached" || 1.515 + this.state == "running" || 1.516 + this.state == "paused", 1.517 + 1.518 + get breakpointStore() { return ThreadActor.breakpointStore; }, 1.519 + 1.520 + get threadLifetimePool() { 1.521 + if (!this._threadLifetimePool) { 1.522 + this._threadLifetimePool = new ActorPool(this.conn); 1.523 + this.conn.addActorPool(this._threadLifetimePool); 1.524 + this._threadLifetimePool.objectActors = new WeakMap(); 1.525 + } 1.526 + return this._threadLifetimePool; 1.527 + }, 1.528 + 1.529 + get sources() { 1.530 + if (!this._sources) { 1.531 + this._sources = new ThreadSources(this, this._options.useSourceMaps, 1.532 + this._allowSource, this.onNewSource); 1.533 + } 1.534 + return this._sources; 1.535 + }, 1.536 + 1.537 + get youngestFrame() { 1.538 + if (this.state != "paused") { 1.539 + return null; 1.540 + } 1.541 + return this.dbg.getNewestFrame(); 1.542 + }, 1.543 + 1.544 + _prettyPrintWorker: null, 1.545 + get prettyPrintWorker() { 1.546 + if (!this._prettyPrintWorker) { 1.547 + this._prettyPrintWorker = new ChromeWorker( 1.548 + "resource://gre/modules/devtools/server/actors/pretty-print-worker.js"); 1.549 + 1.550 + this._prettyPrintWorker.addEventListener( 1.551 + "error", this._onPrettyPrintError, false); 1.552 + 1.553 + if (dumpn.wantLogging) { 1.554 + this._prettyPrintWorker.addEventListener("message", this._onPrettyPrintMsg, false); 1.555 + 1.556 + const postMsg = this._prettyPrintWorker.postMessage; 1.557 + this._prettyPrintWorker.postMessage = data => { 1.558 + dumpn("Sending message to prettyPrintWorker: " 1.559 + + JSON.stringify(data, null, 2) + "\n"); 1.560 + return postMsg.call(this._prettyPrintWorker, data); 1.561 + }; 1.562 + } 1.563 + } 1.564 + return this._prettyPrintWorker; 1.565 + }, 1.566 + 1.567 + _onPrettyPrintError: function ({ message, filename, lineno }) { 1.568 + reportError(new Error(message + " @ " + filename + ":" + lineno)); 1.569 + }, 1.570 + 1.571 + _onPrettyPrintMsg: function ({ data }) { 1.572 + dumpn("Received message from prettyPrintWorker: " 1.573 + + JSON.stringify(data, null, 2) + "\n"); 1.574 + }, 1.575 + 1.576 + /** 1.577 + * Keep track of all of the nested event loops we use to pause the debuggee 1.578 + * when we hit a breakpoint/debugger statement/etc in one place so we can 1.579 + * resolve them when we get resume packets. We have more than one (and keep 1.580 + * them in a stack) because we can pause within client evals. 1.581 + */ 1.582 + _threadPauseEventLoops: null, 1.583 + _pushThreadPause: function () { 1.584 + if (!this._threadPauseEventLoops) { 1.585 + this._threadPauseEventLoops = []; 1.586 + } 1.587 + const eventLoop = this._nestedEventLoops.push(); 1.588 + this._threadPauseEventLoops.push(eventLoop); 1.589 + eventLoop.enter(); 1.590 + }, 1.591 + _popThreadPause: function () { 1.592 + const eventLoop = this._threadPauseEventLoops.pop(); 1.593 + dbg_assert(eventLoop, "Should have an event loop."); 1.594 + eventLoop.resolve(); 1.595 + }, 1.596 + 1.597 + /** 1.598 + * Remove all debuggees and clear out the thread's sources. 1.599 + */ 1.600 + clearDebuggees: function () { 1.601 + if (this.dbg) { 1.602 + this.dbg.removeAllDebuggees(); 1.603 + } 1.604 + this._sources = null; 1.605 + }, 1.606 + 1.607 + /** 1.608 + * Add a debuggee global to the Debugger object. 1.609 + * 1.610 + * @returns the Debugger.Object that corresponds to the global. 1.611 + */ 1.612 + addDebuggee: function (aGlobal) { 1.613 + let globalDebugObject; 1.614 + try { 1.615 + globalDebugObject = this.dbg.addDebuggee(aGlobal); 1.616 + } catch (e) { 1.617 + // Ignore attempts to add the debugger's compartment as a debuggee. 1.618 + dumpn("Ignoring request to add the debugger's compartment as a debuggee"); 1.619 + } 1.620 + return globalDebugObject; 1.621 + }, 1.622 + 1.623 + /** 1.624 + * Initialize the Debugger. 1.625 + */ 1.626 + _initDebugger: function () { 1.627 + this.dbg = new Debugger(); 1.628 + this.dbg.uncaughtExceptionHook = this.uncaughtExceptionHook.bind(this); 1.629 + this.dbg.onDebuggerStatement = this.onDebuggerStatement.bind(this); 1.630 + this.dbg.onNewScript = this.onNewScript.bind(this); 1.631 + this.dbg.onNewGlobalObject = this.globalManager.onNewGlobal.bind(this); 1.632 + // Keep the debugger disabled until a client attaches. 1.633 + this.dbg.enabled = this._state != "detached"; 1.634 + }, 1.635 + 1.636 + /** 1.637 + * Remove a debuggee global from the JSInspector. 1.638 + */ 1.639 + removeDebugee: function (aGlobal) { 1.640 + try { 1.641 + this.dbg.removeDebuggee(aGlobal); 1.642 + } catch(ex) { 1.643 + // XXX: This debuggee has code currently executing on the stack, 1.644 + // we need to save this for later. 1.645 + } 1.646 + }, 1.647 + 1.648 + /** 1.649 + * Add the provided window and all windows in its frame tree as debuggees. 1.650 + * 1.651 + * @returns the Debugger.Object that corresponds to the window. 1.652 + */ 1.653 + _addDebuggees: function (aWindow) { 1.654 + let globalDebugObject = this.addDebuggee(aWindow); 1.655 + let frames = aWindow.frames; 1.656 + if (frames) { 1.657 + for (let i = 0; i < frames.length; i++) { 1.658 + this._addDebuggees(frames[i]); 1.659 + } 1.660 + } 1.661 + return globalDebugObject; 1.662 + }, 1.663 + 1.664 + /** 1.665 + * An object that will be used by ThreadActors to tailor their behavior 1.666 + * depending on the debugging context being required (chrome or content). 1.667 + */ 1.668 + globalManager: { 1.669 + findGlobals: function () { 1.670 + const { gDevToolsExtensions: { 1.671 + getContentGlobals 1.672 + } } = Cu.import("resource://gre/modules/devtools/DevToolsExtensions.jsm", {}); 1.673 + 1.674 + this.globalDebugObject = this._addDebuggees(this.global); 1.675 + 1.676 + // global may not be a window 1.677 + try { 1.678 + getContentGlobals({ 1.679 + 'inner-window-id': getInnerId(this.global) 1.680 + }).forEach(this.addDebuggee.bind(this)); 1.681 + } 1.682 + catch(e) {} 1.683 + }, 1.684 + 1.685 + /** 1.686 + * A function that the engine calls when a new global object 1.687 + * (for example a sandbox) has been created. 1.688 + * 1.689 + * @param aGlobal Debugger.Object 1.690 + * The new global object that was created. 1.691 + */ 1.692 + onNewGlobal: function (aGlobal) { 1.693 + let useGlobal = (aGlobal.hostAnnotations && 1.694 + aGlobal.hostAnnotations.type == "document" && 1.695 + aGlobal.hostAnnotations.element === this.global); 1.696 + 1.697 + // check if the global is a sdk page-mod sandbox 1.698 + if (!useGlobal) { 1.699 + let metadata = {}; 1.700 + let id = ""; 1.701 + try { 1.702 + id = getInnerId(this.global); 1.703 + metadata = Cu.getSandboxMetadata(aGlobal.unsafeDereference()); 1.704 + } 1.705 + catch (e) {} 1.706 + 1.707 + useGlobal = (metadata['inner-window-id'] && metadata['inner-window-id'] == id); 1.708 + } 1.709 + 1.710 + // Content debugging only cares about new globals in the contant window, 1.711 + // like iframe children. 1.712 + if (useGlobal) { 1.713 + this.addDebuggee(aGlobal); 1.714 + // Notify the client. 1.715 + this.conn.send({ 1.716 + from: this.actorID, 1.717 + type: "newGlobal", 1.718 + // TODO: after bug 801084 lands see if we need to JSONify this. 1.719 + hostAnnotations: aGlobal.hostAnnotations 1.720 + }); 1.721 + } 1.722 + } 1.723 + }, 1.724 + 1.725 + disconnect: function () { 1.726 + dumpn("in ThreadActor.prototype.disconnect"); 1.727 + if (this._state == "paused") { 1.728 + this.onResume(); 1.729 + } 1.730 + 1.731 + this.clearDebuggees(); 1.732 + this.conn.removeActorPool(this._threadLifetimePool); 1.733 + this._threadLifetimePool = null; 1.734 + 1.735 + if (this._prettyPrintWorker) { 1.736 + this._prettyPrintWorker.removeEventListener( 1.737 + "error", this._onPrettyPrintError, false); 1.738 + this._prettyPrintWorker.removeEventListener( 1.739 + "message", this._onPrettyPrintMsg, false); 1.740 + this._prettyPrintWorker.terminate(); 1.741 + this._prettyPrintWorker = null; 1.742 + } 1.743 + 1.744 + if (!this.dbg) { 1.745 + return; 1.746 + } 1.747 + this.dbg.enabled = false; 1.748 + this.dbg = null; 1.749 + }, 1.750 + 1.751 + /** 1.752 + * Disconnect the debugger and put the actor in the exited state. 1.753 + */ 1.754 + exit: function () { 1.755 + this.disconnect(); 1.756 + this._state = "exited"; 1.757 + }, 1.758 + 1.759 + // Request handlers 1.760 + onAttach: function (aRequest) { 1.761 + if (this.state === "exited") { 1.762 + return { type: "exited" }; 1.763 + } 1.764 + 1.765 + if (this.state !== "detached") { 1.766 + return { error: "wrongState", 1.767 + message: "Current state is " + this.state }; 1.768 + } 1.769 + 1.770 + this._state = "attached"; 1.771 + 1.772 + update(this._options, aRequest.options || {}); 1.773 + 1.774 + // Initialize an event loop stack. This can't be done in the constructor, 1.775 + // because this.conn is not yet initialized by the actor pool at that time. 1.776 + this._nestedEventLoops = new EventLoopStack({ 1.777 + inspector: DebuggerServer.xpcInspector, 1.778 + hooks: this._hooks, 1.779 + connection: this.conn, 1.780 + thread: this 1.781 + }); 1.782 + 1.783 + if (!this.dbg) { 1.784 + this._initDebugger(); 1.785 + } 1.786 + this.findGlobals(); 1.787 + this.dbg.enabled = true; 1.788 + try { 1.789 + // Put ourselves in the paused state. 1.790 + let packet = this._paused(); 1.791 + if (!packet) { 1.792 + return { error: "notAttached" }; 1.793 + } 1.794 + packet.why = { type: "attached" }; 1.795 + 1.796 + this._restoreBreakpoints(); 1.797 + 1.798 + // Send the response to the attach request now (rather than 1.799 + // returning it), because we're going to start a nested event loop 1.800 + // here. 1.801 + this.conn.send(packet); 1.802 + 1.803 + // Start a nested event loop. 1.804 + this._pushThreadPause(); 1.805 + 1.806 + // We already sent a response to this request, don't send one 1.807 + // now. 1.808 + return null; 1.809 + } catch (e) { 1.810 + reportError(e); 1.811 + return { error: "notAttached", message: e.toString() }; 1.812 + } 1.813 + }, 1.814 + 1.815 + onDetach: function (aRequest) { 1.816 + this.disconnect(); 1.817 + this._state = "detached"; 1.818 + 1.819 + dumpn("ThreadActor.prototype.onDetach: returning 'detached' packet"); 1.820 + return { 1.821 + type: "detached" 1.822 + }; 1.823 + }, 1.824 + 1.825 + onReconfigure: function (aRequest) { 1.826 + if (this.state == "exited") { 1.827 + return { error: "wrongState" }; 1.828 + } 1.829 + 1.830 + update(this._options, aRequest.options || {}); 1.831 + // Clear existing sources, so they can be recreated on next access. 1.832 + this._sources = null; 1.833 + 1.834 + return {}; 1.835 + }, 1.836 + 1.837 + /** 1.838 + * Pause the debuggee, by entering a nested event loop, and return a 'paused' 1.839 + * packet to the client. 1.840 + * 1.841 + * @param Debugger.Frame aFrame 1.842 + * The newest debuggee frame in the stack. 1.843 + * @param object aReason 1.844 + * An object with a 'type' property containing the reason for the pause. 1.845 + * @param function onPacket 1.846 + * Hook to modify the packet before it is sent. Feel free to return a 1.847 + * promise. 1.848 + */ 1.849 + _pauseAndRespond: function (aFrame, aReason, onPacket=function (k) { return k; }) { 1.850 + try { 1.851 + let packet = this._paused(aFrame); 1.852 + if (!packet) { 1.853 + return undefined; 1.854 + } 1.855 + packet.why = aReason; 1.856 + 1.857 + this.sources.getOriginalLocation(packet.frame.where).then(aOrigPosition => { 1.858 + packet.frame.where = aOrigPosition; 1.859 + resolve(onPacket(packet)) 1.860 + .then(null, error => { 1.861 + reportError(error); 1.862 + return { 1.863 + error: "unknownError", 1.864 + message: error.message + "\n" + error.stack 1.865 + }; 1.866 + }) 1.867 + .then(packet => { 1.868 + this.conn.send(packet); 1.869 + }); 1.870 + }); 1.871 + 1.872 + this._pushThreadPause(); 1.873 + } catch(e) { 1.874 + reportError(e, "Got an exception during TA__pauseAndRespond: "); 1.875 + } 1.876 + 1.877 + return undefined; 1.878 + }, 1.879 + 1.880 + /** 1.881 + * Handle resume requests that include a forceCompletion request. 1.882 + * 1.883 + * @param Object aRequest 1.884 + * The request packet received over the RDP. 1.885 + * @returns A response packet. 1.886 + */ 1.887 + _forceCompletion: function (aRequest) { 1.888 + // TODO: remove this when Debugger.Frame.prototype.pop is implemented in 1.889 + // bug 736733. 1.890 + return { 1.891 + error: "notImplemented", 1.892 + message: "forced completion is not yet implemented." 1.893 + }; 1.894 + }, 1.895 + 1.896 + _makeOnEnterFrame: function ({ pauseAndRespond }) { 1.897 + return aFrame => { 1.898 + const generatedLocation = getFrameLocation(aFrame); 1.899 + let { url } = this.synchronize(this.sources.getOriginalLocation( 1.900 + generatedLocation)); 1.901 + 1.902 + return this.sources.isBlackBoxed(url) 1.903 + ? undefined 1.904 + : pauseAndRespond(aFrame); 1.905 + }; 1.906 + }, 1.907 + 1.908 + _makeOnPop: function ({ thread, pauseAndRespond, createValueGrip }) { 1.909 + return function (aCompletion) { 1.910 + // onPop is called with 'this' set to the current frame. 1.911 + 1.912 + const generatedLocation = getFrameLocation(this); 1.913 + const { url } = thread.synchronize(thread.sources.getOriginalLocation( 1.914 + generatedLocation)); 1.915 + 1.916 + if (thread.sources.isBlackBoxed(url)) { 1.917 + return undefined; 1.918 + } 1.919 + 1.920 + // Note that we're popping this frame; we need to watch for 1.921 + // subsequent step events on its caller. 1.922 + this.reportedPop = true; 1.923 + 1.924 + return pauseAndRespond(this, aPacket => { 1.925 + aPacket.why.frameFinished = {}; 1.926 + if (!aCompletion) { 1.927 + aPacket.why.frameFinished.terminated = true; 1.928 + } else if (aCompletion.hasOwnProperty("return")) { 1.929 + aPacket.why.frameFinished.return = createValueGrip(aCompletion.return); 1.930 + } else if (aCompletion.hasOwnProperty("yield")) { 1.931 + aPacket.why.frameFinished.return = createValueGrip(aCompletion.yield); 1.932 + } else { 1.933 + aPacket.why.frameFinished.throw = createValueGrip(aCompletion.throw); 1.934 + } 1.935 + return aPacket; 1.936 + }); 1.937 + }; 1.938 + }, 1.939 + 1.940 + _makeOnStep: function ({ thread, pauseAndRespond, startFrame, 1.941 + startLocation }) { 1.942 + return function () { 1.943 + // onStep is called with 'this' set to the current frame. 1.944 + 1.945 + const generatedLocation = getFrameLocation(this); 1.946 + const newLocation = thread.synchronize(thread.sources.getOriginalLocation( 1.947 + generatedLocation)); 1.948 + 1.949 + // Cases when we should pause because we have executed enough to consider 1.950 + // a "step" to have occured: 1.951 + // 1.952 + // 1.1. We change frames. 1.953 + // 1.2. We change URLs (can happen without changing frames thanks to 1.954 + // source mapping). 1.955 + // 1.3. We change lines. 1.956 + // 1.957 + // Cases when we should always continue execution, even if one of the 1.958 + // above cases is true: 1.959 + // 1.960 + // 2.1. We are in a source mapped region, but inside a null mapping 1.961 + // (doesn't correlate to any region of original source) 1.962 + // 2.2. The source we are in is black boxed. 1.963 + 1.964 + // Cases 2.1 and 2.2 1.965 + if (newLocation.url == null 1.966 + || thread.sources.isBlackBoxed(newLocation.url)) { 1.967 + return undefined; 1.968 + } 1.969 + 1.970 + // Cases 1.1, 1.2 and 1.3 1.971 + if (this !== startFrame 1.972 + || startLocation.url !== newLocation.url 1.973 + || startLocation.line !== newLocation.line) { 1.974 + return pauseAndRespond(this); 1.975 + } 1.976 + 1.977 + // Otherwise, let execution continue (we haven't executed enough code to 1.978 + // consider this a "step" yet). 1.979 + return undefined; 1.980 + }; 1.981 + }, 1.982 + 1.983 + /** 1.984 + * Define the JS hook functions for stepping. 1.985 + */ 1.986 + _makeSteppingHooks: function (aStartLocation) { 1.987 + // Bind these methods and state because some of the hooks are called 1.988 + // with 'this' set to the current frame. Rather than repeating the 1.989 + // binding in each _makeOnX method, just do it once here and pass it 1.990 + // in to each function. 1.991 + const steppingHookState = { 1.992 + pauseAndRespond: (aFrame, onPacket=(k)=>k) => { 1.993 + this._pauseAndRespond(aFrame, { type: "resumeLimit" }, onPacket); 1.994 + }, 1.995 + createValueGrip: this.createValueGrip.bind(this), 1.996 + thread: this, 1.997 + startFrame: this.youngestFrame, 1.998 + startLocation: aStartLocation 1.999 + }; 1.1000 + 1.1001 + return { 1.1002 + onEnterFrame: this._makeOnEnterFrame(steppingHookState), 1.1003 + onPop: this._makeOnPop(steppingHookState), 1.1004 + onStep: this._makeOnStep(steppingHookState) 1.1005 + }; 1.1006 + }, 1.1007 + 1.1008 + /** 1.1009 + * Handle attaching the various stepping hooks we need to attach when we 1.1010 + * receive a resume request with a resumeLimit property. 1.1011 + * 1.1012 + * @param Object aRequest 1.1013 + * The request packet received over the RDP. 1.1014 + * @returns A promise that resolves to true once the hooks are attached, or is 1.1015 + * rejected with an error packet. 1.1016 + */ 1.1017 + _handleResumeLimit: function (aRequest) { 1.1018 + let steppingType = aRequest.resumeLimit.type; 1.1019 + if (["step", "next", "finish"].indexOf(steppingType) == -1) { 1.1020 + return reject({ error: "badParameterType", 1.1021 + message: "Unknown resumeLimit type" }); 1.1022 + } 1.1023 + 1.1024 + const generatedLocation = getFrameLocation(this.youngestFrame); 1.1025 + return this.sources.getOriginalLocation(generatedLocation) 1.1026 + .then(originalLocation => { 1.1027 + const { onEnterFrame, onPop, onStep } = this._makeSteppingHooks(originalLocation); 1.1028 + 1.1029 + // Make sure there is still a frame on the stack if we are to continue 1.1030 + // stepping. 1.1031 + let stepFrame = this._getNextStepFrame(this.youngestFrame); 1.1032 + if (stepFrame) { 1.1033 + switch (steppingType) { 1.1034 + case "step": 1.1035 + this.dbg.onEnterFrame = onEnterFrame; 1.1036 + // Fall through. 1.1037 + case "next": 1.1038 + if (stepFrame.script) { 1.1039 + stepFrame.onStep = onStep; 1.1040 + } 1.1041 + stepFrame.onPop = onPop; 1.1042 + break; 1.1043 + case "finish": 1.1044 + stepFrame.onPop = onPop; 1.1045 + } 1.1046 + } 1.1047 + 1.1048 + return true; 1.1049 + }); 1.1050 + }, 1.1051 + 1.1052 + /** 1.1053 + * Clear the onStep and onPop hooks from the given frame and all of the frames 1.1054 + * below it. 1.1055 + * 1.1056 + * @param Debugger.Frame aFrame 1.1057 + * The frame we want to clear the stepping hooks from. 1.1058 + */ 1.1059 + _clearSteppingHooks: function (aFrame) { 1.1060 + while (aFrame) { 1.1061 + aFrame.onStep = undefined; 1.1062 + aFrame.onPop = undefined; 1.1063 + aFrame = aFrame.older; 1.1064 + } 1.1065 + }, 1.1066 + 1.1067 + /** 1.1068 + * Listen to the debuggee's DOM events if we received a request to do so. 1.1069 + * 1.1070 + * @param Object aRequest 1.1071 + * The resume request packet received over the RDP. 1.1072 + */ 1.1073 + _maybeListenToEvents: function (aRequest) { 1.1074 + // Break-on-DOMEvents is only supported in content debugging. 1.1075 + let events = aRequest.pauseOnDOMEvents; 1.1076 + if (this.global && events && 1.1077 + (events == "*" || 1.1078 + (Array.isArray(events) && events.length))) { 1.1079 + this._pauseOnDOMEvents = events; 1.1080 + let els = Cc["@mozilla.org/eventlistenerservice;1"] 1.1081 + .getService(Ci.nsIEventListenerService); 1.1082 + els.addListenerForAllEvents(this.global, this._allEventsListener, true); 1.1083 + } 1.1084 + }, 1.1085 + 1.1086 + /** 1.1087 + * Handle a protocol request to resume execution of the debuggee. 1.1088 + */ 1.1089 + onResume: function (aRequest) { 1.1090 + if (this._state !== "paused") { 1.1091 + return { 1.1092 + error: "wrongState", 1.1093 + message: "Can't resume when debuggee isn't paused. Current state is '" 1.1094 + + this._state + "'" 1.1095 + }; 1.1096 + } 1.1097 + 1.1098 + // In case of multiple nested event loops (due to multiple debuggers open in 1.1099 + // different tabs or multiple debugger clients connected to the same tab) 1.1100 + // only allow resumption in a LIFO order. 1.1101 + if (this._nestedEventLoops.size && this._nestedEventLoops.lastPausedUrl 1.1102 + && (this._nestedEventLoops.lastPausedUrl !== this._hooks.url 1.1103 + || this._nestedEventLoops.lastConnection !== this.conn)) { 1.1104 + return { 1.1105 + error: "wrongOrder", 1.1106 + message: "trying to resume in the wrong order.", 1.1107 + lastPausedUrl: this._nestedEventLoops.lastPausedUrl 1.1108 + }; 1.1109 + } 1.1110 + 1.1111 + if (aRequest && aRequest.forceCompletion) { 1.1112 + return this._forceCompletion(aRequest); 1.1113 + } 1.1114 + 1.1115 + let resumeLimitHandled; 1.1116 + if (aRequest && aRequest.resumeLimit) { 1.1117 + resumeLimitHandled = this._handleResumeLimit(aRequest) 1.1118 + } else { 1.1119 + this._clearSteppingHooks(this.youngestFrame); 1.1120 + resumeLimitHandled = resolve(true); 1.1121 + } 1.1122 + 1.1123 + return resumeLimitHandled.then(() => { 1.1124 + if (aRequest) { 1.1125 + this._options.pauseOnExceptions = aRequest.pauseOnExceptions; 1.1126 + this._options.ignoreCaughtExceptions = aRequest.ignoreCaughtExceptions; 1.1127 + this.maybePauseOnExceptions(); 1.1128 + this._maybeListenToEvents(aRequest); 1.1129 + } 1.1130 + 1.1131 + let packet = this._resumed(); 1.1132 + this._popThreadPause(); 1.1133 + return packet; 1.1134 + }, error => { 1.1135 + return error instanceof Error 1.1136 + ? { error: "unknownError", 1.1137 + message: DevToolsUtils.safeErrorString(error) } 1.1138 + // It is a known error, and the promise was rejected with an error 1.1139 + // packet. 1.1140 + : error; 1.1141 + }); 1.1142 + }, 1.1143 + 1.1144 + /** 1.1145 + * Spin up a nested event loop so we can synchronously resolve a promise. 1.1146 + * 1.1147 + * @param aPromise 1.1148 + * The promise we want to resolve. 1.1149 + * @returns The promise's resolution. 1.1150 + */ 1.1151 + synchronize: function(aPromise) { 1.1152 + let needNest = true; 1.1153 + let eventLoop; 1.1154 + let returnVal; 1.1155 + 1.1156 + aPromise 1.1157 + .then((aResolvedVal) => { 1.1158 + needNest = false; 1.1159 + returnVal = aResolvedVal; 1.1160 + }) 1.1161 + .then(null, (aError) => { 1.1162 + reportError(aError, "Error inside synchronize:"); 1.1163 + }) 1.1164 + .then(() => { 1.1165 + if (eventLoop) { 1.1166 + eventLoop.resolve(); 1.1167 + } 1.1168 + }); 1.1169 + 1.1170 + if (needNest) { 1.1171 + eventLoop = this._nestedEventLoops.push(); 1.1172 + eventLoop.enter(); 1.1173 + } 1.1174 + 1.1175 + return returnVal; 1.1176 + }, 1.1177 + 1.1178 + /** 1.1179 + * Set the debugging hook to pause on exceptions if configured to do so. 1.1180 + */ 1.1181 + maybePauseOnExceptions: function() { 1.1182 + if (this._options.pauseOnExceptions) { 1.1183 + this.dbg.onExceptionUnwind = this.onExceptionUnwind.bind(this); 1.1184 + } 1.1185 + }, 1.1186 + 1.1187 + /** 1.1188 + * A listener that gets called for every event fired on the page, when a list 1.1189 + * of interesting events was provided with the pauseOnDOMEvents property. It 1.1190 + * is used to set server-managed breakpoints on any existing event listeners 1.1191 + * for those events. 1.1192 + * 1.1193 + * @param Event event 1.1194 + * The event that was fired. 1.1195 + */ 1.1196 + _allEventsListener: function(event) { 1.1197 + if (this._pauseOnDOMEvents == "*" || 1.1198 + this._pauseOnDOMEvents.indexOf(event.type) != -1) { 1.1199 + for (let listener of this._getAllEventListeners(event.target)) { 1.1200 + if (event.type == listener.type || this._pauseOnDOMEvents == "*") { 1.1201 + this._breakOnEnter(listener.script); 1.1202 + } 1.1203 + } 1.1204 + } 1.1205 + }, 1.1206 + 1.1207 + /** 1.1208 + * Return an array containing all the event listeners attached to the 1.1209 + * specified event target and its ancestors in the event target chain. 1.1210 + * 1.1211 + * @param EventTarget eventTarget 1.1212 + * The target the event was dispatched on. 1.1213 + * @returns Array 1.1214 + */ 1.1215 + _getAllEventListeners: function(eventTarget) { 1.1216 + let els = Cc["@mozilla.org/eventlistenerservice;1"] 1.1217 + .getService(Ci.nsIEventListenerService); 1.1218 + 1.1219 + let targets = els.getEventTargetChainFor(eventTarget); 1.1220 + let listeners = []; 1.1221 + 1.1222 + for (let target of targets) { 1.1223 + let handlers = els.getListenerInfoFor(target); 1.1224 + for (let handler of handlers) { 1.1225 + // Null is returned for all-events handlers, and native event listeners 1.1226 + // don't provide any listenerObject, which makes them not that useful to 1.1227 + // a JS debugger. 1.1228 + if (!handler || !handler.listenerObject || !handler.type) 1.1229 + continue; 1.1230 + // Create a listener-like object suitable for our purposes. 1.1231 + let l = Object.create(null); 1.1232 + l.type = handler.type; 1.1233 + let listener = handler.listenerObject; 1.1234 + l.script = this.globalDebugObject.makeDebuggeeValue(listener).script; 1.1235 + // Chrome listeners won't be converted to debuggee values, since their 1.1236 + // compartment is not added as a debuggee. 1.1237 + if (!l.script) 1.1238 + continue; 1.1239 + listeners.push(l); 1.1240 + } 1.1241 + } 1.1242 + return listeners; 1.1243 + }, 1.1244 + 1.1245 + /** 1.1246 + * Set a breakpoint on the first bytecode offset in the provided script. 1.1247 + */ 1.1248 + _breakOnEnter: function(script) { 1.1249 + let offsets = script.getAllOffsets(); 1.1250 + for (let line = 0, n = offsets.length; line < n; line++) { 1.1251 + if (offsets[line]) { 1.1252 + let location = { url: script.url, line: line }; 1.1253 + let resp = this._createAndStoreBreakpoint(location); 1.1254 + dbg_assert(!resp.actualLocation, "No actualLocation should be returned"); 1.1255 + if (resp.error) { 1.1256 + reportError(new Error("Unable to set breakpoint on event listener")); 1.1257 + return; 1.1258 + } 1.1259 + let bp = this.breakpointStore.getBreakpoint(location); 1.1260 + let bpActor = bp.actor; 1.1261 + dbg_assert(bp, "Breakpoint must exist"); 1.1262 + dbg_assert(bpActor, "Breakpoint actor must be created"); 1.1263 + this._hiddenBreakpoints.set(bpActor.actorID, bpActor); 1.1264 + break; 1.1265 + } 1.1266 + } 1.1267 + }, 1.1268 + 1.1269 + /** 1.1270 + * Helper method that returns the next frame when stepping. 1.1271 + */ 1.1272 + _getNextStepFrame: function (aFrame) { 1.1273 + let stepFrame = aFrame.reportedPop ? aFrame.older : aFrame; 1.1274 + if (!stepFrame || !stepFrame.script) { 1.1275 + stepFrame = null; 1.1276 + } 1.1277 + return stepFrame; 1.1278 + }, 1.1279 + 1.1280 + onClientEvaluate: function (aRequest) { 1.1281 + if (this.state !== "paused") { 1.1282 + return { error: "wrongState", 1.1283 + message: "Debuggee must be paused to evaluate code." }; 1.1284 + } 1.1285 + 1.1286 + let frame = this._requestFrame(aRequest.frame); 1.1287 + if (!frame) { 1.1288 + return { error: "unknownFrame", 1.1289 + message: "Evaluation frame not found" }; 1.1290 + } 1.1291 + 1.1292 + if (!frame.environment) { 1.1293 + return { error: "notDebuggee", 1.1294 + message: "cannot access the environment of this frame." }; 1.1295 + } 1.1296 + 1.1297 + let youngest = this.youngestFrame; 1.1298 + 1.1299 + // Put ourselves back in the running state and inform the client. 1.1300 + let resumedPacket = this._resumed(); 1.1301 + this.conn.send(resumedPacket); 1.1302 + 1.1303 + // Run the expression. 1.1304 + // XXX: test syntax errors 1.1305 + let completion = frame.eval(aRequest.expression); 1.1306 + 1.1307 + // Put ourselves back in the pause state. 1.1308 + let packet = this._paused(youngest); 1.1309 + packet.why = { type: "clientEvaluated", 1.1310 + frameFinished: this.createProtocolCompletionValue(completion) }; 1.1311 + 1.1312 + // Return back to our previous pause's event loop. 1.1313 + return packet; 1.1314 + }, 1.1315 + 1.1316 + onFrames: function (aRequest) { 1.1317 + if (this.state !== "paused") { 1.1318 + return { error: "wrongState", 1.1319 + message: "Stack frames are only available while the debuggee is paused."}; 1.1320 + } 1.1321 + 1.1322 + let start = aRequest.start ? aRequest.start : 0; 1.1323 + let count = aRequest.count; 1.1324 + 1.1325 + // Find the starting frame... 1.1326 + let frame = this.youngestFrame; 1.1327 + let i = 0; 1.1328 + while (frame && (i < start)) { 1.1329 + frame = frame.older; 1.1330 + i++; 1.1331 + } 1.1332 + 1.1333 + // Return request.count frames, or all remaining 1.1334 + // frames if count is not defined. 1.1335 + let frames = []; 1.1336 + let promises = []; 1.1337 + for (; frame && (!count || i < (start + count)); i++, frame=frame.older) { 1.1338 + let form = this._createFrameActor(frame).form(); 1.1339 + form.depth = i; 1.1340 + frames.push(form); 1.1341 + 1.1342 + let promise = this.sources.getOriginalLocation(form.where) 1.1343 + .then((aOrigLocation) => { 1.1344 + form.where = aOrigLocation; 1.1345 + let source = this.sources.source({ url: form.where.url }); 1.1346 + if (source) { 1.1347 + form.source = source.form(); 1.1348 + } 1.1349 + }); 1.1350 + promises.push(promise); 1.1351 + } 1.1352 + 1.1353 + return all(promises).then(function () { 1.1354 + return { frames: frames }; 1.1355 + }); 1.1356 + }, 1.1357 + 1.1358 + onReleaseMany: function (aRequest) { 1.1359 + if (!aRequest.actors) { 1.1360 + return { error: "missingParameter", 1.1361 + message: "no actors were specified" }; 1.1362 + } 1.1363 + 1.1364 + let res; 1.1365 + for each (let actorID in aRequest.actors) { 1.1366 + let actor = this.threadLifetimePool.get(actorID); 1.1367 + if (!actor) { 1.1368 + if (!res) { 1.1369 + res = { error: "notReleasable", 1.1370 + message: "Only thread-lifetime actors can be released." }; 1.1371 + } 1.1372 + continue; 1.1373 + } 1.1374 + actor.onRelease(); 1.1375 + } 1.1376 + return res ? res : {}; 1.1377 + }, 1.1378 + 1.1379 + /** 1.1380 + * Handle a protocol request to set a breakpoint. 1.1381 + */ 1.1382 + onSetBreakpoint: function (aRequest) { 1.1383 + if (this.state !== "paused") { 1.1384 + return { error: "wrongState", 1.1385 + message: "Breakpoints can only be set while the debuggee is paused."}; 1.1386 + } 1.1387 + 1.1388 + let { url: originalSource, 1.1389 + line: originalLine, 1.1390 + column: originalColumn } = aRequest.location; 1.1391 + 1.1392 + let locationPromise = this.sources.getGeneratedLocation(aRequest.location); 1.1393 + return locationPromise.then(({url, line, column}) => { 1.1394 + if (line == null || 1.1395 + line < 0 || 1.1396 + this.dbg.findScripts({ url: url }).length == 0) { 1.1397 + return { 1.1398 + error: "noScript", 1.1399 + message: "Requested setting a breakpoint on " 1.1400 + + url + ":" + line 1.1401 + + (column != null ? ":" + column : "") 1.1402 + + " but there is no Debugger.Script at that location" 1.1403 + }; 1.1404 + } 1.1405 + 1.1406 + let response = this._createAndStoreBreakpoint({ 1.1407 + url: url, 1.1408 + line: line, 1.1409 + column: column, 1.1410 + condition: aRequest.condition 1.1411 + }); 1.1412 + // If the original location of our generated location is different from 1.1413 + // the original location we attempted to set the breakpoint on, we will 1.1414 + // need to know so that we can set actualLocation on the response. 1.1415 + let originalLocation = this.sources.getOriginalLocation({ 1.1416 + url: url, 1.1417 + line: line, 1.1418 + column: column 1.1419 + }); 1.1420 + 1.1421 + return all([response, originalLocation]) 1.1422 + .then(([aResponse, {url, line}]) => { 1.1423 + if (aResponse.actualLocation) { 1.1424 + let actualOrigLocation = this.sources.getOriginalLocation(aResponse.actualLocation); 1.1425 + return actualOrigLocation.then(({ url, line, column }) => { 1.1426 + if (url !== originalSource 1.1427 + || line !== originalLine 1.1428 + || column !== originalColumn) { 1.1429 + aResponse.actualLocation = { 1.1430 + url: url, 1.1431 + line: line, 1.1432 + column: column 1.1433 + }; 1.1434 + } 1.1435 + return aResponse; 1.1436 + }); 1.1437 + } 1.1438 + 1.1439 + if (url !== originalSource || line !== originalLine) { 1.1440 + aResponse.actualLocation = { url: url, line: line }; 1.1441 + } 1.1442 + 1.1443 + return aResponse; 1.1444 + }); 1.1445 + }); 1.1446 + }, 1.1447 + 1.1448 + /** 1.1449 + * Create a breakpoint at the specified location and store it in the 1.1450 + * cache. Takes ownership of `aLocation`. 1.1451 + * 1.1452 + * @param Object aLocation 1.1453 + * An object of the form { url, line[, column] } 1.1454 + */ 1.1455 + _createAndStoreBreakpoint: function (aLocation) { 1.1456 + // Add the breakpoint to the store for later reuse, in case it belongs to a 1.1457 + // script that hasn't appeared yet. 1.1458 + this.breakpointStore.addBreakpoint(aLocation); 1.1459 + return this._setBreakpoint(aLocation); 1.1460 + }, 1.1461 + 1.1462 + /** 1.1463 + * Set a breakpoint using the jsdbg2 API. If the line on which the breakpoint 1.1464 + * is being set contains no code, then the breakpoint will slide down to the 1.1465 + * next line that has runnable code. In this case the server breakpoint cache 1.1466 + * will be updated, so callers that iterate over the breakpoint cache should 1.1467 + * take that into account. 1.1468 + * 1.1469 + * @param object aLocation 1.1470 + * The location of the breakpoint (in the generated source, if source 1.1471 + * mapping). 1.1472 + */ 1.1473 + _setBreakpoint: function (aLocation) { 1.1474 + let actor; 1.1475 + let storedBp = this.breakpointStore.getBreakpoint(aLocation); 1.1476 + if (storedBp.actor) { 1.1477 + actor = storedBp.actor; 1.1478 + actor.condition = aLocation.condition; 1.1479 + } else { 1.1480 + storedBp.actor = actor = new BreakpointActor(this, { 1.1481 + url: aLocation.url, 1.1482 + line: aLocation.line, 1.1483 + column: aLocation.column, 1.1484 + condition: aLocation.condition 1.1485 + }); 1.1486 + this.threadLifetimePool.addActor(actor); 1.1487 + } 1.1488 + 1.1489 + // Find all scripts matching the given location 1.1490 + let scripts = this.dbg.findScripts(aLocation); 1.1491 + if (scripts.length == 0) { 1.1492 + return { 1.1493 + error: "noScript", 1.1494 + message: "Requested setting a breakpoint on " 1.1495 + + aLocation.url + ":" + aLocation.line 1.1496 + + (aLocation.column != null ? ":" + aLocation.column : "") 1.1497 + + " but there is no Debugger.Script at that location", 1.1498 + actor: actor.actorID 1.1499 + }; 1.1500 + } 1.1501 + 1.1502 + /** 1.1503 + * For each script, if the given line has at least one entry point, set a 1.1504 + * breakpoint on the bytecode offets for each of them. 1.1505 + */ 1.1506 + 1.1507 + // Debugger.Script -> array of offset mappings 1.1508 + let scriptsAndOffsetMappings = new Map(); 1.1509 + 1.1510 + for (let script of scripts) { 1.1511 + this._findClosestOffsetMappings(aLocation, 1.1512 + script, 1.1513 + scriptsAndOffsetMappings); 1.1514 + } 1.1515 + 1.1516 + if (scriptsAndOffsetMappings.size > 0) { 1.1517 + for (let [script, mappings] of scriptsAndOffsetMappings) { 1.1518 + for (let offsetMapping of mappings) { 1.1519 + script.setBreakpoint(offsetMapping.offset, actor); 1.1520 + } 1.1521 + actor.addScript(script, this); 1.1522 + } 1.1523 + 1.1524 + return { 1.1525 + actor: actor.actorID 1.1526 + }; 1.1527 + } 1.1528 + 1.1529 + /** 1.1530 + * If we get here, no breakpoint was set. This is because the given line 1.1531 + * has no entry points, for example because it is empty. As a fallback 1.1532 + * strategy, we try to set the breakpoint on the smallest line greater 1.1533 + * than or equal to the given line that as at least one entry point. 1.1534 + */ 1.1535 + 1.1536 + // Find all innermost scripts matching the given location 1.1537 + let scripts = this.dbg.findScripts({ 1.1538 + url: aLocation.url, 1.1539 + line: aLocation.line, 1.1540 + innermost: true 1.1541 + }); 1.1542 + 1.1543 + /** 1.1544 + * For each innermost script, look for the smallest line greater than or 1.1545 + * equal to the given line that has one or more entry points. If found, set 1.1546 + * a breakpoint on the bytecode offset for each of its entry points. 1.1547 + */ 1.1548 + let actualLocation; 1.1549 + let found = false; 1.1550 + for (let script of scripts) { 1.1551 + let offsets = script.getAllOffsets(); 1.1552 + for (let line = aLocation.line; line < offsets.length; ++line) { 1.1553 + if (offsets[line]) { 1.1554 + for (let offset of offsets[line]) { 1.1555 + script.setBreakpoint(offset, actor); 1.1556 + } 1.1557 + actor.addScript(script, this); 1.1558 + if (!actualLocation) { 1.1559 + actualLocation = { 1.1560 + url: aLocation.url, 1.1561 + line: line 1.1562 + }; 1.1563 + } 1.1564 + found = true; 1.1565 + break; 1.1566 + } 1.1567 + } 1.1568 + } 1.1569 + if (found) { 1.1570 + let existingBp = this.breakpointStore.hasBreakpoint(actualLocation); 1.1571 + 1.1572 + if (existingBp && existingBp.actor) { 1.1573 + /** 1.1574 + * We already have a breakpoint actor for the actual location, so 1.1575 + * actor we created earlier is now redundant. Delete it, update the 1.1576 + * breakpoint store, and return the actor for the actual location. 1.1577 + */ 1.1578 + actor.onDelete(); 1.1579 + this.breakpointStore.removeBreakpoint(aLocation); 1.1580 + return { 1.1581 + actor: existingBp.actor.actorID, 1.1582 + actualLocation: actualLocation 1.1583 + }; 1.1584 + } else { 1.1585 + /** 1.1586 + * We don't have a breakpoint actor for the actual location yet. 1.1587 + * Instead or creating a new actor, reuse the actor we created earlier, 1.1588 + * and update the breakpoint store. 1.1589 + */ 1.1590 + actor.location = actualLocation; 1.1591 + this.breakpointStore.addBreakpoint({ 1.1592 + actor: actor, 1.1593 + url: actualLocation.url, 1.1594 + line: actualLocation.line, 1.1595 + column: actualLocation.column 1.1596 + }); 1.1597 + this.breakpointStore.removeBreakpoint(aLocation); 1.1598 + return { 1.1599 + actor: actor.actorID, 1.1600 + actualLocation: actualLocation 1.1601 + }; 1.1602 + } 1.1603 + } 1.1604 + 1.1605 + /** 1.1606 + * If we get here, no line matching the given line was found, so just 1.1607 + * fail epically. 1.1608 + */ 1.1609 + return { 1.1610 + error: "noCodeAtLineColumn", 1.1611 + actor: actor.actorID 1.1612 + }; 1.1613 + }, 1.1614 + 1.1615 + /** 1.1616 + * Find all of the offset mappings associated with `aScript` that are closest 1.1617 + * to `aTargetLocation`. If new offset mappings are found that are closer to 1.1618 + * `aTargetOffset` than the existing offset mappings inside 1.1619 + * `aScriptsAndOffsetMappings`, we empty that map and only consider the 1.1620 + * closest offset mappings. If there is no column in `aTargetLocation`, we add 1.1621 + * all offset mappings that are on the given line. 1.1622 + * 1.1623 + * @param Object aTargetLocation 1.1624 + * An object of the form { url, line[, column] }. 1.1625 + * @param Debugger.Script aScript 1.1626 + * The script in which we are searching for offsets. 1.1627 + * @param Map aScriptsAndOffsetMappings 1.1628 + * A Map object which maps Debugger.Script instances to arrays of 1.1629 + * offset mappings. This is an out param. 1.1630 + */ 1.1631 + _findClosestOffsetMappings: function (aTargetLocation, 1.1632 + aScript, 1.1633 + aScriptsAndOffsetMappings) { 1.1634 + // If we are given a column, we will try and break only at that location, 1.1635 + // otherwise we will break anytime we get on that line. 1.1636 + 1.1637 + if (aTargetLocation.column == null) { 1.1638 + let offsetMappings = aScript.getLineOffsets(aTargetLocation.line) 1.1639 + .map(o => ({ 1.1640 + line: aTargetLocation.line, 1.1641 + offset: o 1.1642 + })); 1.1643 + if (offsetMappings.length) { 1.1644 + aScriptsAndOffsetMappings.set(aScript, offsetMappings); 1.1645 + } 1.1646 + return; 1.1647 + } 1.1648 + 1.1649 + let offsetMappings = aScript.getAllColumnOffsets() 1.1650 + .filter(({ lineNumber }) => lineNumber === aTargetLocation.line); 1.1651 + 1.1652 + // Attempt to find the current closest offset distance from the target 1.1653 + // location by grabbing any offset mapping in the map by doing one iteration 1.1654 + // and then breaking (they all have the same distance from the target 1.1655 + // location). 1.1656 + let closestDistance = Infinity; 1.1657 + if (aScriptsAndOffsetMappings.size) { 1.1658 + for (let mappings of aScriptsAndOffsetMappings.values()) { 1.1659 + closestDistance = Math.abs(aTargetLocation.column - mappings[0].columnNumber); 1.1660 + break; 1.1661 + } 1.1662 + } 1.1663 + 1.1664 + for (let mapping of offsetMappings) { 1.1665 + let currentDistance = Math.abs(aTargetLocation.column - mapping.columnNumber); 1.1666 + 1.1667 + if (currentDistance > closestDistance) { 1.1668 + continue; 1.1669 + } else if (currentDistance < closestDistance) { 1.1670 + closestDistance = currentDistance; 1.1671 + aScriptsAndOffsetMappings.clear(); 1.1672 + aScriptsAndOffsetMappings.set(aScript, [mapping]); 1.1673 + } else { 1.1674 + if (!aScriptsAndOffsetMappings.has(aScript)) { 1.1675 + aScriptsAndOffsetMappings.set(aScript, []); 1.1676 + } 1.1677 + aScriptsAndOffsetMappings.get(aScript).push(mapping); 1.1678 + } 1.1679 + } 1.1680 + }, 1.1681 + 1.1682 + /** 1.1683 + * Get the script and source lists from the debugger. 1.1684 + */ 1.1685 + _discoverSources: function () { 1.1686 + // Only get one script per url. 1.1687 + const sourcesToScripts = new Map(); 1.1688 + for (let s of this.dbg.findScripts()) { 1.1689 + if (s.source) { 1.1690 + sourcesToScripts.set(s.source, s); 1.1691 + } 1.1692 + } 1.1693 + 1.1694 + return all([this.sources.sourcesForScript(script) 1.1695 + for (script of sourcesToScripts.values())]); 1.1696 + }, 1.1697 + 1.1698 + onSources: function (aRequest) { 1.1699 + return this._discoverSources().then(() => { 1.1700 + return { 1.1701 + sources: [s.form() for (s of this.sources.iter())] 1.1702 + }; 1.1703 + }); 1.1704 + }, 1.1705 + 1.1706 + /** 1.1707 + * Disassociate all breakpoint actors from their scripts and clear the 1.1708 + * breakpoint handlers. This method can be used when the thread actor intends 1.1709 + * to keep the breakpoint store, but needs to clear any actual breakpoints, 1.1710 + * e.g. due to a page navigation. This way the breakpoint actors' script 1.1711 + * caches won't hold on to the Debugger.Script objects leaking memory. 1.1712 + */ 1.1713 + disableAllBreakpoints: function () { 1.1714 + for (let bp of this.breakpointStore.findBreakpoints()) { 1.1715 + if (bp.actor) { 1.1716 + bp.actor.removeScripts(); 1.1717 + } 1.1718 + } 1.1719 + }, 1.1720 + 1.1721 + /** 1.1722 + * Handle a protocol request to pause the debuggee. 1.1723 + */ 1.1724 + onInterrupt: function (aRequest) { 1.1725 + if (this.state == "exited") { 1.1726 + return { type: "exited" }; 1.1727 + } else if (this.state == "paused") { 1.1728 + // TODO: return the actual reason for the existing pause. 1.1729 + return { type: "paused", why: { type: "alreadyPaused" } }; 1.1730 + } else if (this.state != "running") { 1.1731 + return { error: "wrongState", 1.1732 + message: "Received interrupt request in " + this.state + 1.1733 + " state." }; 1.1734 + } 1.1735 + 1.1736 + try { 1.1737 + // Put ourselves in the paused state. 1.1738 + let packet = this._paused(); 1.1739 + if (!packet) { 1.1740 + return { error: "notInterrupted" }; 1.1741 + } 1.1742 + packet.why = { type: "interrupted" }; 1.1743 + 1.1744 + // Send the response to the interrupt request now (rather than 1.1745 + // returning it), because we're going to start a nested event loop 1.1746 + // here. 1.1747 + this.conn.send(packet); 1.1748 + 1.1749 + // Start a nested event loop. 1.1750 + this._pushThreadPause(); 1.1751 + 1.1752 + // We already sent a response to this request, don't send one 1.1753 + // now. 1.1754 + return null; 1.1755 + } catch (e) { 1.1756 + reportError(e); 1.1757 + return { error: "notInterrupted", message: e.toString() }; 1.1758 + } 1.1759 + }, 1.1760 + 1.1761 + /** 1.1762 + * Handle a protocol request to retrieve all the event listeners on the page. 1.1763 + */ 1.1764 + onEventListeners: function (aRequest) { 1.1765 + // This request is only supported in content debugging. 1.1766 + if (!this.global) { 1.1767 + return { 1.1768 + error: "notImplemented", 1.1769 + message: "eventListeners request is only supported in content debugging" 1.1770 + }; 1.1771 + } 1.1772 + 1.1773 + let els = Cc["@mozilla.org/eventlistenerservice;1"] 1.1774 + .getService(Ci.nsIEventListenerService); 1.1775 + 1.1776 + let nodes = this.global.document.getElementsByTagName("*"); 1.1777 + nodes = [this.global].concat([].slice.call(nodes)); 1.1778 + let listeners = []; 1.1779 + 1.1780 + for (let node of nodes) { 1.1781 + let handlers = els.getListenerInfoFor(node); 1.1782 + 1.1783 + for (let handler of handlers) { 1.1784 + // Create a form object for serializing the listener via the protocol. 1.1785 + let listenerForm = Object.create(null); 1.1786 + let listener = handler.listenerObject; 1.1787 + // Native event listeners don't provide any listenerObject or type and 1.1788 + // are not that useful to a JS debugger. 1.1789 + if (!listener || !handler.type) { 1.1790 + continue; 1.1791 + } 1.1792 + 1.1793 + // There will be no tagName if the event listener is set on the window. 1.1794 + let selector = node.tagName ? findCssSelector(node) : "window"; 1.1795 + let nodeDO = this.globalDebugObject.makeDebuggeeValue(node); 1.1796 + listenerForm.node = { 1.1797 + selector: selector, 1.1798 + object: this.createValueGrip(nodeDO) 1.1799 + }; 1.1800 + listenerForm.type = handler.type; 1.1801 + listenerForm.capturing = handler.capturing; 1.1802 + listenerForm.allowsUntrusted = handler.allowsUntrusted; 1.1803 + listenerForm.inSystemEventGroup = handler.inSystemEventGroup; 1.1804 + listenerForm.isEventHandler = !!node["on" + listenerForm.type]; 1.1805 + // Get the Debugger.Object for the listener object. 1.1806 + let listenerDO = this.globalDebugObject.makeDebuggeeValue(listener); 1.1807 + listenerForm.function = this.createValueGrip(listenerDO); 1.1808 + listeners.push(listenerForm); 1.1809 + } 1.1810 + } 1.1811 + return { listeners: listeners }; 1.1812 + }, 1.1813 + 1.1814 + /** 1.1815 + * Return the Debug.Frame for a frame mentioned by the protocol. 1.1816 + */ 1.1817 + _requestFrame: function (aFrameID) { 1.1818 + if (!aFrameID) { 1.1819 + return this.youngestFrame; 1.1820 + } 1.1821 + 1.1822 + if (this._framePool.has(aFrameID)) { 1.1823 + return this._framePool.get(aFrameID).frame; 1.1824 + } 1.1825 + 1.1826 + return undefined; 1.1827 + }, 1.1828 + 1.1829 + _paused: function (aFrame) { 1.1830 + // We don't handle nested pauses correctly. Don't try - if we're 1.1831 + // paused, just continue running whatever code triggered the pause. 1.1832 + // We don't want to actually have nested pauses (although we 1.1833 + // have nested event loops). If code runs in the debuggee during 1.1834 + // a pause, it should cause the actor to resume (dropping 1.1835 + // pause-lifetime actors etc) and then repause when complete. 1.1836 + 1.1837 + if (this.state === "paused") { 1.1838 + return undefined; 1.1839 + } 1.1840 + 1.1841 + // Clear stepping hooks. 1.1842 + this.dbg.onEnterFrame = undefined; 1.1843 + this.dbg.onExceptionUnwind = undefined; 1.1844 + if (aFrame) { 1.1845 + aFrame.onStep = undefined; 1.1846 + aFrame.onPop = undefined; 1.1847 + } 1.1848 + // Clear DOM event breakpoints. 1.1849 + // XPCShell tests don't use actual DOM windows for globals and cause 1.1850 + // removeListenerForAllEvents to throw. 1.1851 + if (this.global && !this.global.toString().contains("Sandbox")) { 1.1852 + let els = Cc["@mozilla.org/eventlistenerservice;1"] 1.1853 + .getService(Ci.nsIEventListenerService); 1.1854 + els.removeListenerForAllEvents(this.global, this._allEventsListener, true); 1.1855 + for (let [,bp] of this._hiddenBreakpoints) { 1.1856 + bp.onDelete(); 1.1857 + } 1.1858 + this._hiddenBreakpoints.clear(); 1.1859 + } 1.1860 + 1.1861 + this._state = "paused"; 1.1862 + 1.1863 + // Create the actor pool that will hold the pause actor and its 1.1864 + // children. 1.1865 + dbg_assert(!this._pausePool, "No pause pool should exist yet"); 1.1866 + this._pausePool = new ActorPool(this.conn); 1.1867 + this.conn.addActorPool(this._pausePool); 1.1868 + 1.1869 + // Give children of the pause pool a quick link back to the 1.1870 + // thread... 1.1871 + this._pausePool.threadActor = this; 1.1872 + 1.1873 + // Create the pause actor itself... 1.1874 + dbg_assert(!this._pauseActor, "No pause actor should exist yet"); 1.1875 + this._pauseActor = new PauseActor(this._pausePool); 1.1876 + this._pausePool.addActor(this._pauseActor); 1.1877 + 1.1878 + // Update the list of frames. 1.1879 + let poppedFrames = this._updateFrames(); 1.1880 + 1.1881 + // Send off the paused packet and spin an event loop. 1.1882 + let packet = { from: this.actorID, 1.1883 + type: "paused", 1.1884 + actor: this._pauseActor.actorID }; 1.1885 + if (aFrame) { 1.1886 + packet.frame = this._createFrameActor(aFrame).form(); 1.1887 + } 1.1888 + 1.1889 + if (poppedFrames) { 1.1890 + packet.poppedFrames = poppedFrames; 1.1891 + } 1.1892 + 1.1893 + return packet; 1.1894 + }, 1.1895 + 1.1896 + _resumed: function () { 1.1897 + this._state = "running"; 1.1898 + 1.1899 + // Drop the actors in the pause actor pool. 1.1900 + this.conn.removeActorPool(this._pausePool); 1.1901 + 1.1902 + this._pausePool = null; 1.1903 + this._pauseActor = null; 1.1904 + 1.1905 + return { from: this.actorID, type: "resumed" }; 1.1906 + }, 1.1907 + 1.1908 + /** 1.1909 + * Expire frame actors for frames that have been popped. 1.1910 + * 1.1911 + * @returns A list of actor IDs whose frames have been popped. 1.1912 + */ 1.1913 + _updateFrames: function () { 1.1914 + let popped = []; 1.1915 + 1.1916 + // Create the actor pool that will hold the still-living frames. 1.1917 + let framePool = new ActorPool(this.conn); 1.1918 + let frameList = []; 1.1919 + 1.1920 + for each (let frameActor in this._frameActors) { 1.1921 + if (frameActor.frame.live) { 1.1922 + framePool.addActor(frameActor); 1.1923 + frameList.push(frameActor); 1.1924 + } else { 1.1925 + popped.push(frameActor.actorID); 1.1926 + } 1.1927 + } 1.1928 + 1.1929 + // Remove the old frame actor pool, this will expire 1.1930 + // any actors that weren't added to the new pool. 1.1931 + if (this._framePool) { 1.1932 + this.conn.removeActorPool(this._framePool); 1.1933 + } 1.1934 + 1.1935 + this._frameActors = frameList; 1.1936 + this._framePool = framePool; 1.1937 + this.conn.addActorPool(framePool); 1.1938 + 1.1939 + return popped; 1.1940 + }, 1.1941 + 1.1942 + _createFrameActor: function (aFrame) { 1.1943 + if (aFrame.actor) { 1.1944 + return aFrame.actor; 1.1945 + } 1.1946 + 1.1947 + let actor = new FrameActor(aFrame, this); 1.1948 + this._frameActors.push(actor); 1.1949 + this._framePool.addActor(actor); 1.1950 + aFrame.actor = actor; 1.1951 + 1.1952 + return actor; 1.1953 + }, 1.1954 + 1.1955 + /** 1.1956 + * Create and return an environment actor that corresponds to the provided 1.1957 + * Debugger.Environment. 1.1958 + * @param Debugger.Environment aEnvironment 1.1959 + * The lexical environment we want to extract. 1.1960 + * @param object aPool 1.1961 + * The pool where the newly-created actor will be placed. 1.1962 + * @return The EnvironmentActor for aEnvironment or undefined for host 1.1963 + * functions or functions scoped to a non-debuggee global. 1.1964 + */ 1.1965 + createEnvironmentActor: function (aEnvironment, aPool) { 1.1966 + if (!aEnvironment) { 1.1967 + return undefined; 1.1968 + } 1.1969 + 1.1970 + if (aEnvironment.actor) { 1.1971 + return aEnvironment.actor; 1.1972 + } 1.1973 + 1.1974 + let actor = new EnvironmentActor(aEnvironment, this); 1.1975 + aPool.addActor(actor); 1.1976 + aEnvironment.actor = actor; 1.1977 + 1.1978 + return actor; 1.1979 + }, 1.1980 + 1.1981 + /** 1.1982 + * Create a grip for the given debuggee value. If the value is an 1.1983 + * object, will create an actor with the given lifetime. 1.1984 + */ 1.1985 + createValueGrip: function (aValue, aPool=false) { 1.1986 + if (!aPool) { 1.1987 + aPool = this._pausePool; 1.1988 + } 1.1989 + 1.1990 + switch (typeof aValue) { 1.1991 + case "boolean": 1.1992 + return aValue; 1.1993 + case "string": 1.1994 + if (this._stringIsLong(aValue)) { 1.1995 + return this.longStringGrip(aValue, aPool); 1.1996 + } 1.1997 + return aValue; 1.1998 + case "number": 1.1999 + if (aValue === Infinity) { 1.2000 + return { type: "Infinity" }; 1.2001 + } else if (aValue === -Infinity) { 1.2002 + return { type: "-Infinity" }; 1.2003 + } else if (Number.isNaN(aValue)) { 1.2004 + return { type: "NaN" }; 1.2005 + } else if (!aValue && 1 / aValue === -Infinity) { 1.2006 + return { type: "-0" }; 1.2007 + } 1.2008 + return aValue; 1.2009 + case "undefined": 1.2010 + return { type: "undefined" }; 1.2011 + case "object": 1.2012 + if (aValue === null) { 1.2013 + return { type: "null" }; 1.2014 + } 1.2015 + return this.objectGrip(aValue, aPool); 1.2016 + default: 1.2017 + dbg_assert(false, "Failed to provide a grip for: " + aValue); 1.2018 + return null; 1.2019 + } 1.2020 + }, 1.2021 + 1.2022 + /** 1.2023 + * Return a protocol completion value representing the given 1.2024 + * Debugger-provided completion value. 1.2025 + */ 1.2026 + createProtocolCompletionValue: function (aCompletion) { 1.2027 + let protoValue = {}; 1.2028 + if ("return" in aCompletion) { 1.2029 + protoValue.return = this.createValueGrip(aCompletion.return); 1.2030 + } else if ("yield" in aCompletion) { 1.2031 + protoValue.return = this.createValueGrip(aCompletion.yield); 1.2032 + } else if ("throw" in aCompletion) { 1.2033 + protoValue.throw = this.createValueGrip(aCompletion.throw); 1.2034 + } else { 1.2035 + protoValue.terminated = true; 1.2036 + } 1.2037 + return protoValue; 1.2038 + }, 1.2039 + 1.2040 + /** 1.2041 + * Create a grip for the given debuggee object. 1.2042 + * 1.2043 + * @param aValue Debugger.Object 1.2044 + * The debuggee object value. 1.2045 + * @param aPool ActorPool 1.2046 + * The actor pool where the new object actor will be added. 1.2047 + */ 1.2048 + objectGrip: function (aValue, aPool) { 1.2049 + if (!aPool.objectActors) { 1.2050 + aPool.objectActors = new WeakMap(); 1.2051 + } 1.2052 + 1.2053 + if (aPool.objectActors.has(aValue)) { 1.2054 + return aPool.objectActors.get(aValue).grip(); 1.2055 + } else if (this.threadLifetimePool.objectActors.has(aValue)) { 1.2056 + return this.threadLifetimePool.objectActors.get(aValue).grip(); 1.2057 + } 1.2058 + 1.2059 + let actor = new PauseScopedObjectActor(aValue, this); 1.2060 + aPool.addActor(actor); 1.2061 + aPool.objectActors.set(aValue, actor); 1.2062 + return actor.grip(); 1.2063 + }, 1.2064 + 1.2065 + /** 1.2066 + * Create a grip for the given debuggee object with a pause lifetime. 1.2067 + * 1.2068 + * @param aValue Debugger.Object 1.2069 + * The debuggee object value. 1.2070 + */ 1.2071 + pauseObjectGrip: function (aValue) { 1.2072 + if (!this._pausePool) { 1.2073 + throw "Object grip requested while not paused."; 1.2074 + } 1.2075 + 1.2076 + return this.objectGrip(aValue, this._pausePool); 1.2077 + }, 1.2078 + 1.2079 + /** 1.2080 + * Extend the lifetime of the provided object actor to thread lifetime. 1.2081 + * 1.2082 + * @param aActor object 1.2083 + * The object actor. 1.2084 + */ 1.2085 + threadObjectGrip: function (aActor) { 1.2086 + // We want to reuse the existing actor ID, so we just remove it from the 1.2087 + // current pool's weak map and then let pool.addActor do the rest. 1.2088 + aActor.registeredPool.objectActors.delete(aActor.obj); 1.2089 + this.threadLifetimePool.addActor(aActor); 1.2090 + this.threadLifetimePool.objectActors.set(aActor.obj, aActor); 1.2091 + }, 1.2092 + 1.2093 + /** 1.2094 + * Handle a protocol request to promote multiple pause-lifetime grips to 1.2095 + * thread-lifetime grips. 1.2096 + * 1.2097 + * @param aRequest object 1.2098 + * The protocol request object. 1.2099 + */ 1.2100 + onThreadGrips: function (aRequest) { 1.2101 + if (this.state != "paused") { 1.2102 + return { error: "wrongState" }; 1.2103 + } 1.2104 + 1.2105 + if (!aRequest.actors) { 1.2106 + return { error: "missingParameter", 1.2107 + message: "no actors were specified" }; 1.2108 + } 1.2109 + 1.2110 + for (let actorID of aRequest.actors) { 1.2111 + let actor = this._pausePool.get(actorID); 1.2112 + if (actor) { 1.2113 + this.threadObjectGrip(actor); 1.2114 + } 1.2115 + } 1.2116 + return {}; 1.2117 + }, 1.2118 + 1.2119 + /** 1.2120 + * Create a grip for the given string. 1.2121 + * 1.2122 + * @param aString String 1.2123 + * The string we are creating a grip for. 1.2124 + * @param aPool ActorPool 1.2125 + * The actor pool where the new actor will be added. 1.2126 + */ 1.2127 + longStringGrip: function (aString, aPool) { 1.2128 + if (!aPool.longStringActors) { 1.2129 + aPool.longStringActors = {}; 1.2130 + } 1.2131 + 1.2132 + if (aPool.longStringActors.hasOwnProperty(aString)) { 1.2133 + return aPool.longStringActors[aString].grip(); 1.2134 + } 1.2135 + 1.2136 + let actor = new LongStringActor(aString, this); 1.2137 + aPool.addActor(actor); 1.2138 + aPool.longStringActors[aString] = actor; 1.2139 + return actor.grip(); 1.2140 + }, 1.2141 + 1.2142 + /** 1.2143 + * Create a long string grip that is scoped to a pause. 1.2144 + * 1.2145 + * @param aString String 1.2146 + * The string we are creating a grip for. 1.2147 + */ 1.2148 + pauseLongStringGrip: function (aString) { 1.2149 + return this.longStringGrip(aString, this._pausePool); 1.2150 + }, 1.2151 + 1.2152 + /** 1.2153 + * Create a long string grip that is scoped to a thread. 1.2154 + * 1.2155 + * @param aString String 1.2156 + * The string we are creating a grip for. 1.2157 + */ 1.2158 + threadLongStringGrip: function (aString) { 1.2159 + return this.longStringGrip(aString, this._threadLifetimePool); 1.2160 + }, 1.2161 + 1.2162 + /** 1.2163 + * Returns true if the string is long enough to use a LongStringActor instead 1.2164 + * of passing the value directly over the protocol. 1.2165 + * 1.2166 + * @param aString String 1.2167 + * The string we are checking the length of. 1.2168 + */ 1.2169 + _stringIsLong: function (aString) { 1.2170 + return aString.length >= DebuggerServer.LONG_STRING_LENGTH; 1.2171 + }, 1.2172 + 1.2173 + // JS Debugger API hooks. 1.2174 + 1.2175 + /** 1.2176 + * A function that the engine calls when a call to a debug event hook, 1.2177 + * breakpoint handler, watchpoint handler, or similar function throws some 1.2178 + * exception. 1.2179 + * 1.2180 + * @param aException exception 1.2181 + * The exception that was thrown in the debugger code. 1.2182 + */ 1.2183 + uncaughtExceptionHook: function (aException) { 1.2184 + dumpn("Got an exception: " + aException.message + "\n" + aException.stack); 1.2185 + }, 1.2186 + 1.2187 + /** 1.2188 + * A function that the engine calls when a debugger statement has been 1.2189 + * executed in the specified frame. 1.2190 + * 1.2191 + * @param aFrame Debugger.Frame 1.2192 + * The stack frame that contained the debugger statement. 1.2193 + */ 1.2194 + onDebuggerStatement: function (aFrame) { 1.2195 + // Don't pause if we are currently stepping (in or over) or the frame is 1.2196 + // black-boxed. 1.2197 + const generatedLocation = getFrameLocation(aFrame); 1.2198 + const { url } = this.synchronize(this.sources.getOriginalLocation( 1.2199 + generatedLocation)); 1.2200 + 1.2201 + return this.sources.isBlackBoxed(url) || aFrame.onStep 1.2202 + ? undefined 1.2203 + : this._pauseAndRespond(aFrame, { type: "debuggerStatement" }); 1.2204 + }, 1.2205 + 1.2206 + /** 1.2207 + * A function that the engine calls when an exception has been thrown and has 1.2208 + * propagated to the specified frame. 1.2209 + * 1.2210 + * @param aFrame Debugger.Frame 1.2211 + * The youngest remaining stack frame. 1.2212 + * @param aValue object 1.2213 + * The exception that was thrown. 1.2214 + */ 1.2215 + onExceptionUnwind: function (aFrame, aValue) { 1.2216 + let willBeCaught = false; 1.2217 + for (let frame = aFrame; frame != null; frame = frame.older) { 1.2218 + if (frame.script.isInCatchScope(frame.offset)) { 1.2219 + willBeCaught = true; 1.2220 + break; 1.2221 + } 1.2222 + } 1.2223 + 1.2224 + if (willBeCaught && this._options.ignoreCaughtExceptions) { 1.2225 + return undefined; 1.2226 + } 1.2227 + 1.2228 + const generatedLocation = getFrameLocation(aFrame); 1.2229 + const { url } = this.synchronize(this.sources.getOriginalLocation( 1.2230 + generatedLocation)); 1.2231 + 1.2232 + if (this.sources.isBlackBoxed(url)) { 1.2233 + return undefined; 1.2234 + } 1.2235 + 1.2236 + try { 1.2237 + let packet = this._paused(aFrame); 1.2238 + if (!packet) { 1.2239 + return undefined; 1.2240 + } 1.2241 + 1.2242 + packet.why = { type: "exception", 1.2243 + exception: this.createValueGrip(aValue) }; 1.2244 + this.conn.send(packet); 1.2245 + 1.2246 + this._pushThreadPause(); 1.2247 + } catch(e) { 1.2248 + reportError(e, "Got an exception during TA_onExceptionUnwind: "); 1.2249 + } 1.2250 + 1.2251 + return undefined; 1.2252 + }, 1.2253 + 1.2254 + /** 1.2255 + * A function that the engine calls when a new script has been loaded into the 1.2256 + * scope of the specified debuggee global. 1.2257 + * 1.2258 + * @param aScript Debugger.Script 1.2259 + * The source script that has been loaded into a debuggee compartment. 1.2260 + * @param aGlobal Debugger.Object 1.2261 + * A Debugger.Object instance whose referent is the global object. 1.2262 + */ 1.2263 + onNewScript: function (aScript, aGlobal) { 1.2264 + this._addScript(aScript); 1.2265 + 1.2266 + // |onNewScript| is only fired for top level scripts (AKA staticLevel == 0), 1.2267 + // so we have to make sure to call |_addScript| on every child script as 1.2268 + // well to restore breakpoints in those scripts. 1.2269 + for (let s of aScript.getChildScripts()) { 1.2270 + this._addScript(s); 1.2271 + } 1.2272 + 1.2273 + this.sources.sourcesForScript(aScript); 1.2274 + }, 1.2275 + 1.2276 + onNewSource: function (aSource) { 1.2277 + this.conn.send({ 1.2278 + from: this.actorID, 1.2279 + type: "newSource", 1.2280 + source: aSource.form() 1.2281 + }); 1.2282 + }, 1.2283 + 1.2284 + /** 1.2285 + * Check if scripts from the provided source URL are allowed to be stored in 1.2286 + * the cache. 1.2287 + * 1.2288 + * @param aSourceUrl String 1.2289 + * The url of the script's source that will be stored. 1.2290 + * @returns true, if the script can be added, false otherwise. 1.2291 + */ 1.2292 + _allowSource: function (aSourceUrl) { 1.2293 + // Ignore anything we don't have a URL for (eval scripts, for example). 1.2294 + if (!aSourceUrl) 1.2295 + return false; 1.2296 + // Ignore XBL bindings for content debugging. 1.2297 + if (aSourceUrl.indexOf("chrome://") == 0) { 1.2298 + return false; 1.2299 + } 1.2300 + // Ignore about:* pages for content debugging. 1.2301 + if (aSourceUrl.indexOf("about:") == 0) { 1.2302 + return false; 1.2303 + } 1.2304 + return true; 1.2305 + }, 1.2306 + 1.2307 + /** 1.2308 + * Restore any pre-existing breakpoints to the scripts that we have access to. 1.2309 + */ 1.2310 + _restoreBreakpoints: function () { 1.2311 + if (this.breakpointStore.size === 0) { 1.2312 + return; 1.2313 + } 1.2314 + 1.2315 + for (let s of this.dbg.findScripts()) { 1.2316 + this._addScript(s); 1.2317 + } 1.2318 + }, 1.2319 + 1.2320 + /** 1.2321 + * Add the provided script to the server cache. 1.2322 + * 1.2323 + * @param aScript Debugger.Script 1.2324 + * The source script that will be stored. 1.2325 + * @returns true, if the script was added; false otherwise. 1.2326 + */ 1.2327 + _addScript: function (aScript) { 1.2328 + if (!this._allowSource(aScript.url)) { 1.2329 + return false; 1.2330 + } 1.2331 + 1.2332 + // Set any stored breakpoints. 1.2333 + 1.2334 + let endLine = aScript.startLine + aScript.lineCount - 1; 1.2335 + for (let bp of this.breakpointStore.findBreakpoints({ url: aScript.url })) { 1.2336 + // Only consider breakpoints that are not already associated with 1.2337 + // scripts, and limit search to the line numbers contained in the new 1.2338 + // script. 1.2339 + if (!bp.actor.scripts.length 1.2340 + && bp.line >= aScript.startLine 1.2341 + && bp.line <= endLine) { 1.2342 + this._setBreakpoint(bp); 1.2343 + } 1.2344 + } 1.2345 + 1.2346 + return true; 1.2347 + }, 1.2348 + 1.2349 + 1.2350 + /** 1.2351 + * Get prototypes and properties of multiple objects. 1.2352 + */ 1.2353 + onPrototypesAndProperties: function (aRequest) { 1.2354 + let result = {}; 1.2355 + for (let actorID of aRequest.actors) { 1.2356 + // This code assumes that there are no lazily loaded actors returned 1.2357 + // by this call. 1.2358 + let actor = this.conn.getActor(actorID); 1.2359 + if (!actor) { 1.2360 + return { from: this.actorID, 1.2361 + error: "noSuchActor" }; 1.2362 + } 1.2363 + let handler = actor.onPrototypeAndProperties; 1.2364 + if (!handler) { 1.2365 + return { from: this.actorID, 1.2366 + error: "unrecognizedPacketType", 1.2367 + message: ('Actor "' + actorID + 1.2368 + '" does not recognize the packet type ' + 1.2369 + '"prototypeAndProperties"') }; 1.2370 + } 1.2371 + result[actorID] = handler.call(actor, {}); 1.2372 + } 1.2373 + return { from: this.actorID, 1.2374 + actors: result }; 1.2375 + } 1.2376 + 1.2377 +}; 1.2378 + 1.2379 +ThreadActor.prototype.requestTypes = { 1.2380 + "attach": ThreadActor.prototype.onAttach, 1.2381 + "detach": ThreadActor.prototype.onDetach, 1.2382 + "reconfigure": ThreadActor.prototype.onReconfigure, 1.2383 + "resume": ThreadActor.prototype.onResume, 1.2384 + "clientEvaluate": ThreadActor.prototype.onClientEvaluate, 1.2385 + "frames": ThreadActor.prototype.onFrames, 1.2386 + "interrupt": ThreadActor.prototype.onInterrupt, 1.2387 + "eventListeners": ThreadActor.prototype.onEventListeners, 1.2388 + "releaseMany": ThreadActor.prototype.onReleaseMany, 1.2389 + "setBreakpoint": ThreadActor.prototype.onSetBreakpoint, 1.2390 + "sources": ThreadActor.prototype.onSources, 1.2391 + "threadGrips": ThreadActor.prototype.onThreadGrips, 1.2392 + "prototypesAndProperties": ThreadActor.prototype.onPrototypesAndProperties 1.2393 +}; 1.2394 + 1.2395 + 1.2396 +/** 1.2397 + * Creates a PauseActor. 1.2398 + * 1.2399 + * PauseActors exist for the lifetime of a given debuggee pause. Used to 1.2400 + * scope pause-lifetime grips. 1.2401 + * 1.2402 + * @param ActorPool aPool 1.2403 + * The actor pool created for this pause. 1.2404 + */ 1.2405 +function PauseActor(aPool) 1.2406 +{ 1.2407 + this.pool = aPool; 1.2408 +} 1.2409 + 1.2410 +PauseActor.prototype = { 1.2411 + actorPrefix: "pause" 1.2412 +}; 1.2413 + 1.2414 + 1.2415 +/** 1.2416 + * A base actor for any actors that should only respond receive messages in the 1.2417 + * paused state. Subclasses may expose a `threadActor` which is used to help 1.2418 + * determine when we are in a paused state. Subclasses should set their own 1.2419 + * "constructor" property if they want better error messages. You should never 1.2420 + * instantiate a PauseScopedActor directly, only through subclasses. 1.2421 + */ 1.2422 +function PauseScopedActor() 1.2423 +{ 1.2424 +} 1.2425 + 1.2426 +/** 1.2427 + * A function decorator for creating methods to handle protocol messages that 1.2428 + * should only be received while in the paused state. 1.2429 + * 1.2430 + * @param aMethod Function 1.2431 + * The function we are decorating. 1.2432 + */ 1.2433 +PauseScopedActor.withPaused = function (aMethod) { 1.2434 + return function () { 1.2435 + if (this.isPaused()) { 1.2436 + return aMethod.apply(this, arguments); 1.2437 + } else { 1.2438 + return this._wrongState(); 1.2439 + } 1.2440 + }; 1.2441 +}; 1.2442 + 1.2443 +PauseScopedActor.prototype = { 1.2444 + 1.2445 + /** 1.2446 + * Returns true if we are in the paused state. 1.2447 + */ 1.2448 + isPaused: function () { 1.2449 + // When there is not a ThreadActor available (like in the webconsole) we 1.2450 + // have to be optimistic and assume that we are paused so that we can 1.2451 + // respond to requests. 1.2452 + return this.threadActor ? this.threadActor.state === "paused" : true; 1.2453 + }, 1.2454 + 1.2455 + /** 1.2456 + * Returns the wrongState response packet for this actor. 1.2457 + */ 1.2458 + _wrongState: function () { 1.2459 + return { 1.2460 + error: "wrongState", 1.2461 + message: this.constructor.name + 1.2462 + " actors can only be accessed while the thread is paused." 1.2463 + }; 1.2464 + } 1.2465 +}; 1.2466 + 1.2467 +/** 1.2468 + * Resolve a URI back to physical file. 1.2469 + * 1.2470 + * Of course, this works only for URIs pointing to local resources. 1.2471 + * 1.2472 + * @param aURI 1.2473 + * URI to resolve 1.2474 + * @return 1.2475 + * resolved nsIURI 1.2476 + */ 1.2477 +function resolveURIToLocalPath(aURI) { 1.2478 + switch (aURI.scheme) { 1.2479 + case "jar": 1.2480 + case "file": 1.2481 + return aURI; 1.2482 + 1.2483 + case "chrome": 1.2484 + let resolved = Cc["@mozilla.org/chrome/chrome-registry;1"]. 1.2485 + getService(Ci.nsIChromeRegistry).convertChromeURL(aURI); 1.2486 + return resolveURIToLocalPath(resolved); 1.2487 + 1.2488 + case "resource": 1.2489 + resolved = Cc["@mozilla.org/network/protocol;1?name=resource"]. 1.2490 + getService(Ci.nsIResProtocolHandler).resolveURI(aURI); 1.2491 + aURI = Services.io.newURI(resolved, null, null); 1.2492 + return resolveURIToLocalPath(aURI); 1.2493 + 1.2494 + default: 1.2495 + return null; 1.2496 + } 1.2497 +} 1.2498 + 1.2499 +/** 1.2500 + * A SourceActor provides information about the source of a script. 1.2501 + * 1.2502 + * @param String url 1.2503 + * The url of the source we are representing. 1.2504 + * @param ThreadActor thread 1.2505 + * The current thread actor. 1.2506 + * @param SourceMapConsumer sourceMap 1.2507 + * Optional. The source map that introduced this source, if available. 1.2508 + * @param String generatedSource 1.2509 + * Optional, passed in when aSourceMap is also passed in. The generated 1.2510 + * source url that introduced this source. 1.2511 + * @param String text 1.2512 + * Optional. The content text of this source, if immediately available. 1.2513 + * @param String contentType 1.2514 + * Optional. The content type of this source, if immediately available. 1.2515 + */ 1.2516 +function SourceActor({ url, thread, sourceMap, generatedSource, text, 1.2517 + contentType }) { 1.2518 + this._threadActor = thread; 1.2519 + this._url = url; 1.2520 + this._sourceMap = sourceMap; 1.2521 + this._generatedSource = generatedSource; 1.2522 + this._text = text; 1.2523 + this._contentType = contentType; 1.2524 + 1.2525 + this.onSource = this.onSource.bind(this); 1.2526 + this._invertSourceMap = this._invertSourceMap.bind(this); 1.2527 + this._saveMap = this._saveMap.bind(this); 1.2528 + this._getSourceText = this._getSourceText.bind(this); 1.2529 + 1.2530 + this._mapSourceToAddon(); 1.2531 + 1.2532 + if (this.threadActor.sources.isPrettyPrinted(this.url)) { 1.2533 + this._init = this.onPrettyPrint({ 1.2534 + indent: this.threadActor.sources.prettyPrintIndent(this.url) 1.2535 + }).then(null, error => { 1.2536 + DevToolsUtils.reportException("SourceActor", error); 1.2537 + }); 1.2538 + } else { 1.2539 + this._init = null; 1.2540 + } 1.2541 +} 1.2542 + 1.2543 +SourceActor.prototype = { 1.2544 + constructor: SourceActor, 1.2545 + actorPrefix: "source", 1.2546 + 1.2547 + _oldSourceMap: null, 1.2548 + _init: null, 1.2549 + _addonID: null, 1.2550 + _addonPath: null, 1.2551 + 1.2552 + get threadActor() this._threadActor, 1.2553 + get url() this._url, 1.2554 + get addonID() this._addonID, 1.2555 + get addonPath() this._addonPath, 1.2556 + 1.2557 + get prettyPrintWorker() { 1.2558 + return this.threadActor.prettyPrintWorker; 1.2559 + }, 1.2560 + 1.2561 + form: function () { 1.2562 + return { 1.2563 + actor: this.actorID, 1.2564 + url: this._url, 1.2565 + addonID: this._addonID, 1.2566 + addonPath: this._addonPath, 1.2567 + isBlackBoxed: this.threadActor.sources.isBlackBoxed(this.url), 1.2568 + isPrettyPrinted: this.threadActor.sources.isPrettyPrinted(this.url) 1.2569 + // TODO bug 637572: introductionScript 1.2570 + }; 1.2571 + }, 1.2572 + 1.2573 + disconnect: function () { 1.2574 + if (this.registeredPool && this.registeredPool.sourceActors) { 1.2575 + delete this.registeredPool.sourceActors[this.actorID]; 1.2576 + } 1.2577 + }, 1.2578 + 1.2579 + _mapSourceToAddon: function() { 1.2580 + try { 1.2581 + var nsuri = Services.io.newURI(this._url.split(" -> ").pop(), null, null); 1.2582 + } 1.2583 + catch (e) { 1.2584 + // We can't do anything with an invalid URI 1.2585 + return; 1.2586 + } 1.2587 + 1.2588 + let localURI = resolveURIToLocalPath(nsuri); 1.2589 + 1.2590 + let id = {}; 1.2591 + if (localURI && mapURIToAddonID(localURI, id)) { 1.2592 + this._addonID = id.value; 1.2593 + 1.2594 + if (localURI instanceof Ci.nsIJARURI) { 1.2595 + // The path in the add-on is easy for jar: uris 1.2596 + this._addonPath = localURI.JAREntry; 1.2597 + } 1.2598 + else if (localURI instanceof Ci.nsIFileURL) { 1.2599 + // For file: uris walk up to find the last directory that is part of the 1.2600 + // add-on 1.2601 + let target = localURI.file; 1.2602 + let path = target.leafName; 1.2603 + 1.2604 + // We can assume that the directory containing the source file is part 1.2605 + // of the add-on 1.2606 + let root = target.parent; 1.2607 + let file = root.parent; 1.2608 + while (file && mapURIToAddonID(Services.io.newFileURI(file), {})) { 1.2609 + path = root.leafName + "/" + path; 1.2610 + root = file; 1.2611 + file = file.parent; 1.2612 + } 1.2613 + 1.2614 + if (!file) { 1.2615 + const error = new Error("Could not find the root of the add-on for " + this._url); 1.2616 + DevToolsUtils.reportException("SourceActor.prototype._mapSourceToAddon", error) 1.2617 + return; 1.2618 + } 1.2619 + 1.2620 + this._addonPath = path; 1.2621 + } 1.2622 + } 1.2623 + }, 1.2624 + 1.2625 + _getSourceText: function () { 1.2626 + const toResolvedContent = t => resolve({ 1.2627 + content: t, 1.2628 + contentType: this._contentType 1.2629 + }); 1.2630 + 1.2631 + let sc; 1.2632 + if (this._sourceMap && (sc = this._sourceMap.sourceContentFor(this._url))) { 1.2633 + return toResolvedContent(sc); 1.2634 + } 1.2635 + 1.2636 + if (this._text) { 1.2637 + return toResolvedContent(this._text); 1.2638 + } 1.2639 + 1.2640 + // XXX bug 865252: Don't load from the cache if this is a source mapped 1.2641 + // source because we can't guarantee that the cache has the most up to date 1.2642 + // content for this source like we can if it isn't source mapped. 1.2643 + let sourceFetched = fetch(this._url, { loadFromCache: !this._sourceMap }); 1.2644 + 1.2645 + // Record the contentType we just learned during fetching 1.2646 + sourceFetched.then(({ contentType }) => { 1.2647 + this._contentType = contentType; 1.2648 + }); 1.2649 + 1.2650 + return sourceFetched; 1.2651 + }, 1.2652 + 1.2653 + /** 1.2654 + * Handler for the "source" packet. 1.2655 + */ 1.2656 + onSource: function () { 1.2657 + return resolve(this._init) 1.2658 + .then(this._getSourceText) 1.2659 + .then(({ content, contentType }) => { 1.2660 + return { 1.2661 + from: this.actorID, 1.2662 + source: this.threadActor.createValueGrip( 1.2663 + content, this.threadActor.threadLifetimePool), 1.2664 + contentType: contentType 1.2665 + }; 1.2666 + }) 1.2667 + .then(null, aError => { 1.2668 + reportError(aError, "Got an exception during SA_onSource: "); 1.2669 + return { 1.2670 + "from": this.actorID, 1.2671 + "error": "loadSourceError", 1.2672 + "message": "Could not load the source for " + this._url + ".\n" 1.2673 + + DevToolsUtils.safeErrorString(aError) 1.2674 + }; 1.2675 + }); 1.2676 + }, 1.2677 + 1.2678 + /** 1.2679 + * Handler for the "prettyPrint" packet. 1.2680 + */ 1.2681 + onPrettyPrint: function ({ indent }) { 1.2682 + this.threadActor.sources.prettyPrint(this._url, indent); 1.2683 + return this._getSourceText() 1.2684 + .then(this._sendToPrettyPrintWorker(indent)) 1.2685 + .then(this._invertSourceMap) 1.2686 + .then(this._saveMap) 1.2687 + .then(() => { 1.2688 + // We need to reset `_init` now because we have already done the work of 1.2689 + // pretty printing, and don't want onSource to wait forever for 1.2690 + // initialization to complete. 1.2691 + this._init = null; 1.2692 + }) 1.2693 + .then(this.onSource) 1.2694 + .then(null, error => { 1.2695 + this.onDisablePrettyPrint(); 1.2696 + return { 1.2697 + from: this.actorID, 1.2698 + error: "prettyPrintError", 1.2699 + message: DevToolsUtils.safeErrorString(error) 1.2700 + }; 1.2701 + }); 1.2702 + }, 1.2703 + 1.2704 + /** 1.2705 + * Return a function that sends a request to the pretty print worker, waits on 1.2706 + * the worker's response, and then returns the pretty printed code. 1.2707 + * 1.2708 + * @param Number aIndent 1.2709 + * The number of spaces to indent by the code by, when we send the 1.2710 + * request to the pretty print worker. 1.2711 + * @returns Function 1.2712 + * Returns a function which takes an AST, and returns a promise that 1.2713 + * is resolved with `{ code, mappings }` where `code` is the pretty 1.2714 + * printed code, and `mappings` is an array of source mappings. 1.2715 + */ 1.2716 + _sendToPrettyPrintWorker: function (aIndent) { 1.2717 + return ({ content }) => { 1.2718 + const deferred = promise.defer(); 1.2719 + const id = Math.random(); 1.2720 + 1.2721 + const onReply = ({ data }) => { 1.2722 + if (data.id !== id) { 1.2723 + return; 1.2724 + } 1.2725 + this.prettyPrintWorker.removeEventListener("message", onReply, false); 1.2726 + 1.2727 + if (data.error) { 1.2728 + deferred.reject(new Error(data.error)); 1.2729 + } else { 1.2730 + deferred.resolve(data); 1.2731 + } 1.2732 + }; 1.2733 + 1.2734 + this.prettyPrintWorker.addEventListener("message", onReply, false); 1.2735 + this.prettyPrintWorker.postMessage({ 1.2736 + id: id, 1.2737 + url: this._url, 1.2738 + indent: aIndent, 1.2739 + source: content 1.2740 + }); 1.2741 + 1.2742 + return deferred.promise; 1.2743 + }; 1.2744 + }, 1.2745 + 1.2746 + /** 1.2747 + * Invert a source map. So if a source map maps from a to b, return a new 1.2748 + * source map from b to a. We need to do this because the source map we get 1.2749 + * from _generatePrettyCodeAndMap goes the opposite way we want it to for 1.2750 + * debugging. 1.2751 + * 1.2752 + * Note that the source map is modified in place. 1.2753 + */ 1.2754 + _invertSourceMap: function ({ code, mappings }) { 1.2755 + const generator = new SourceMapGenerator({ file: this._url }); 1.2756 + return DevToolsUtils.yieldingEach(mappings, m => { 1.2757 + let mapping = { 1.2758 + generated: { 1.2759 + line: m.generatedLine, 1.2760 + column: m.generatedColumn 1.2761 + } 1.2762 + }; 1.2763 + if (m.source) { 1.2764 + mapping.source = m.source; 1.2765 + mapping.original = { 1.2766 + line: m.originalLine, 1.2767 + column: m.originalColumn 1.2768 + }; 1.2769 + mapping.name = m.name; 1.2770 + } 1.2771 + generator.addMapping(mapping); 1.2772 + }).then(() => { 1.2773 + generator.setSourceContent(this._url, code); 1.2774 + const consumer = SourceMapConsumer.fromSourceMap(generator); 1.2775 + 1.2776 + // XXX bug 918802: Monkey punch the source map consumer, because iterating 1.2777 + // over all mappings and inverting each of them, and then creating a new 1.2778 + // SourceMapConsumer is slow. 1.2779 + 1.2780 + const getOrigPos = consumer.originalPositionFor.bind(consumer); 1.2781 + const getGenPos = consumer.generatedPositionFor.bind(consumer); 1.2782 + 1.2783 + consumer.originalPositionFor = ({ line, column }) => { 1.2784 + const location = getGenPos({ 1.2785 + line: line, 1.2786 + column: column, 1.2787 + source: this._url 1.2788 + }); 1.2789 + location.source = this._url; 1.2790 + return location; 1.2791 + }; 1.2792 + 1.2793 + consumer.generatedPositionFor = ({ line, column }) => getOrigPos({ 1.2794 + line: line, 1.2795 + column: column 1.2796 + }); 1.2797 + 1.2798 + return { 1.2799 + code: code, 1.2800 + map: consumer 1.2801 + }; 1.2802 + }); 1.2803 + }, 1.2804 + 1.2805 + /** 1.2806 + * Save the source map back to our thread's ThreadSources object so that 1.2807 + * stepping, breakpoints, debugger statements, etc can use it. If we are 1.2808 + * pretty printing a source mapped source, we need to compose the existing 1.2809 + * source map with our new one. 1.2810 + */ 1.2811 + _saveMap: function ({ map }) { 1.2812 + if (this._sourceMap) { 1.2813 + // Compose the source maps 1.2814 + this._oldSourceMap = this._sourceMap; 1.2815 + this._sourceMap = SourceMapGenerator.fromSourceMap(this._sourceMap); 1.2816 + this._sourceMap.applySourceMap(map, this._url); 1.2817 + this._sourceMap = SourceMapConsumer.fromSourceMap(this._sourceMap); 1.2818 + this._threadActor.sources.saveSourceMap(this._sourceMap, 1.2819 + this._generatedSource); 1.2820 + } else { 1.2821 + this._sourceMap = map; 1.2822 + this._threadActor.sources.saveSourceMap(this._sourceMap, this._url); 1.2823 + } 1.2824 + }, 1.2825 + 1.2826 + /** 1.2827 + * Handler for the "disablePrettyPrint" packet. 1.2828 + */ 1.2829 + onDisablePrettyPrint: function () { 1.2830 + this._sourceMap = this._oldSourceMap; 1.2831 + this.threadActor.sources.saveSourceMap(this._sourceMap, 1.2832 + this._generatedSource || this._url); 1.2833 + this.threadActor.sources.disablePrettyPrint(this._url); 1.2834 + return this.onSource(); 1.2835 + }, 1.2836 + 1.2837 + /** 1.2838 + * Handler for the "blackbox" packet. 1.2839 + */ 1.2840 + onBlackBox: function (aRequest) { 1.2841 + this.threadActor.sources.blackBox(this.url); 1.2842 + let packet = { 1.2843 + from: this.actorID 1.2844 + }; 1.2845 + if (this.threadActor.state == "paused" 1.2846 + && this.threadActor.youngestFrame 1.2847 + && this.threadActor.youngestFrame.script.url == this.url) { 1.2848 + packet.pausedInSource = true; 1.2849 + } 1.2850 + return packet; 1.2851 + }, 1.2852 + 1.2853 + /** 1.2854 + * Handler for the "unblackbox" packet. 1.2855 + */ 1.2856 + onUnblackBox: function (aRequest) { 1.2857 + this.threadActor.sources.unblackBox(this.url); 1.2858 + return { 1.2859 + from: this.actorID 1.2860 + }; 1.2861 + } 1.2862 +}; 1.2863 + 1.2864 +SourceActor.prototype.requestTypes = { 1.2865 + "source": SourceActor.prototype.onSource, 1.2866 + "blackbox": SourceActor.prototype.onBlackBox, 1.2867 + "unblackbox": SourceActor.prototype.onUnblackBox, 1.2868 + "prettyPrint": SourceActor.prototype.onPrettyPrint, 1.2869 + "disablePrettyPrint": SourceActor.prototype.onDisablePrettyPrint 1.2870 +}; 1.2871 + 1.2872 + 1.2873 +/** 1.2874 + * Determine if a given value is non-primitive. 1.2875 + * 1.2876 + * @param Any aValue 1.2877 + * The value to test. 1.2878 + * @return Boolean 1.2879 + * Whether the value is non-primitive. 1.2880 + */ 1.2881 +function isObject(aValue) { 1.2882 + const type = typeof aValue; 1.2883 + return type == "object" ? aValue !== null : type == "function"; 1.2884 +} 1.2885 + 1.2886 +/** 1.2887 + * Create a function that can safely stringify Debugger.Objects of a given 1.2888 + * builtin type. 1.2889 + * 1.2890 + * @param Function aCtor 1.2891 + * The builtin class constructor. 1.2892 + * @return Function 1.2893 + * The stringifier for the class. 1.2894 + */ 1.2895 +function createBuiltinStringifier(aCtor) { 1.2896 + return aObj => aCtor.prototype.toString.call(aObj.unsafeDereference()); 1.2897 +} 1.2898 + 1.2899 +/** 1.2900 + * Stringify a Debugger.Object-wrapped Error instance. 1.2901 + * 1.2902 + * @param Debugger.Object aObj 1.2903 + * The object to stringify. 1.2904 + * @return String 1.2905 + * The stringification of the object. 1.2906 + */ 1.2907 +function errorStringify(aObj) { 1.2908 + let name = DevToolsUtils.getProperty(aObj, "name"); 1.2909 + if (name === "" || name === undefined) { 1.2910 + name = aObj.class; 1.2911 + } else if (isObject(name)) { 1.2912 + name = stringify(name); 1.2913 + } 1.2914 + 1.2915 + let message = DevToolsUtils.getProperty(aObj, "message"); 1.2916 + if (isObject(message)) { 1.2917 + message = stringify(message); 1.2918 + } 1.2919 + 1.2920 + if (message === "" || message === undefined) { 1.2921 + return name; 1.2922 + } 1.2923 + return name + ": " + message; 1.2924 +} 1.2925 + 1.2926 +/** 1.2927 + * Stringify a Debugger.Object based on its class. 1.2928 + * 1.2929 + * @param Debugger.Object aObj 1.2930 + * The object to stringify. 1.2931 + * @return String 1.2932 + * The stringification for the object. 1.2933 + */ 1.2934 +function stringify(aObj) { 1.2935 + if (aObj.class == "DeadObject") { 1.2936 + const error = new Error("Dead object encountered."); 1.2937 + DevToolsUtils.reportException("stringify", error); 1.2938 + return "<dead object>"; 1.2939 + } 1.2940 + const stringifier = stringifiers[aObj.class] || stringifiers.Object; 1.2941 + return stringifier(aObj); 1.2942 +} 1.2943 + 1.2944 +// Used to prevent infinite recursion when an array is found inside itself. 1.2945 +let seen = null; 1.2946 + 1.2947 +let stringifiers = { 1.2948 + Error: errorStringify, 1.2949 + EvalError: errorStringify, 1.2950 + RangeError: errorStringify, 1.2951 + ReferenceError: errorStringify, 1.2952 + SyntaxError: errorStringify, 1.2953 + TypeError: errorStringify, 1.2954 + URIError: errorStringify, 1.2955 + Boolean: createBuiltinStringifier(Boolean), 1.2956 + Function: createBuiltinStringifier(Function), 1.2957 + Number: createBuiltinStringifier(Number), 1.2958 + RegExp: createBuiltinStringifier(RegExp), 1.2959 + String: createBuiltinStringifier(String), 1.2960 + Object: obj => "[object " + obj.class + "]", 1.2961 + Array: obj => { 1.2962 + // If we're at the top level then we need to create the Set for tracking 1.2963 + // previously stringified arrays. 1.2964 + const topLevel = !seen; 1.2965 + if (topLevel) { 1.2966 + seen = new Set(); 1.2967 + } else if (seen.has(obj)) { 1.2968 + return ""; 1.2969 + } 1.2970 + 1.2971 + seen.add(obj); 1.2972 + 1.2973 + const len = DevToolsUtils.getProperty(obj, "length"); 1.2974 + let string = ""; 1.2975 + 1.2976 + // The following check is only required because the debuggee could possibly 1.2977 + // be a Proxy and return any value. For normal objects, array.length is 1.2978 + // always a non-negative integer. 1.2979 + if (typeof len == "number" && len > 0) { 1.2980 + for (let i = 0; i < len; i++) { 1.2981 + const desc = obj.getOwnPropertyDescriptor(i); 1.2982 + if (desc) { 1.2983 + const { value } = desc; 1.2984 + if (value != null) { 1.2985 + string += isObject(value) ? stringify(value) : value; 1.2986 + } 1.2987 + } 1.2988 + 1.2989 + if (i < len - 1) { 1.2990 + string += ","; 1.2991 + } 1.2992 + } 1.2993 + } 1.2994 + 1.2995 + if (topLevel) { 1.2996 + seen = null; 1.2997 + } 1.2998 + 1.2999 + return string; 1.3000 + }, 1.3001 + DOMException: obj => { 1.3002 + const message = DevToolsUtils.getProperty(obj, "message") || "<no message>"; 1.3003 + const result = (+DevToolsUtils.getProperty(obj, "result")).toString(16); 1.3004 + const code = DevToolsUtils.getProperty(obj, "code"); 1.3005 + const name = DevToolsUtils.getProperty(obj, "name") || "<unknown>"; 1.3006 + 1.3007 + return '[Exception... "' + message + '" ' + 1.3008 + 'code: "' + code +'" ' + 1.3009 + 'nsresult: "0x' + result + ' (' + name + ')"]'; 1.3010 + } 1.3011 +}; 1.3012 + 1.3013 +/** 1.3014 + * Creates an actor for the specified object. 1.3015 + * 1.3016 + * @param aObj Debugger.Object 1.3017 + * The debuggee object. 1.3018 + * @param aThreadActor ThreadActor 1.3019 + * The parent thread actor for this object. 1.3020 + */ 1.3021 +function ObjectActor(aObj, aThreadActor) 1.3022 +{ 1.3023 + dbg_assert(!aObj.optimizedOut, "Should not create object actors for optimized out values!"); 1.3024 + this.obj = aObj; 1.3025 + this.threadActor = aThreadActor; 1.3026 +} 1.3027 + 1.3028 +ObjectActor.prototype = { 1.3029 + actorPrefix: "obj", 1.3030 + 1.3031 + /** 1.3032 + * Returns a grip for this actor for returning in a protocol message. 1.3033 + */ 1.3034 + grip: function () { 1.3035 + this.threadActor._gripDepth++; 1.3036 + 1.3037 + let g = { 1.3038 + "type": "object", 1.3039 + "class": this.obj.class, 1.3040 + "actor": this.actorID, 1.3041 + "extensible": this.obj.isExtensible(), 1.3042 + "frozen": this.obj.isFrozen(), 1.3043 + "sealed": this.obj.isSealed() 1.3044 + }; 1.3045 + 1.3046 + if (this.obj.class != "DeadObject") { 1.3047 + let raw = Cu.unwaiveXrays(this.obj.unsafeDereference()); 1.3048 + if (!DevToolsUtils.isSafeJSObject(raw)) { 1.3049 + raw = null; 1.3050 + } 1.3051 + 1.3052 + let previewers = DebuggerServer.ObjectActorPreviewers[this.obj.class] || 1.3053 + DebuggerServer.ObjectActorPreviewers.Object; 1.3054 + for (let fn of previewers) { 1.3055 + try { 1.3056 + if (fn(this, g, raw)) { 1.3057 + break; 1.3058 + } 1.3059 + } catch (e) { 1.3060 + DevToolsUtils.reportException("ObjectActor.prototype.grip previewer function", e); 1.3061 + } 1.3062 + } 1.3063 + } 1.3064 + 1.3065 + this.threadActor._gripDepth--; 1.3066 + return g; 1.3067 + }, 1.3068 + 1.3069 + /** 1.3070 + * Releases this actor from the pool. 1.3071 + */ 1.3072 + release: function () { 1.3073 + if (this.registeredPool.objectActors) { 1.3074 + this.registeredPool.objectActors.delete(this.obj); 1.3075 + } 1.3076 + this.registeredPool.removeActor(this); 1.3077 + }, 1.3078 + 1.3079 + /** 1.3080 + * Handle a protocol request to provide the definition site of this function 1.3081 + * object. 1.3082 + * 1.3083 + * @param aRequest object 1.3084 + * The protocol request object. 1.3085 + */ 1.3086 + onDefinitionSite: function OA_onDefinitionSite(aRequest) { 1.3087 + if (this.obj.class != "Function") { 1.3088 + return { 1.3089 + from: this.actorID, 1.3090 + error: "objectNotFunction", 1.3091 + message: this.actorID + " is not a function." 1.3092 + }; 1.3093 + } 1.3094 + 1.3095 + if (!this.obj.script) { 1.3096 + return { 1.3097 + from: this.actorID, 1.3098 + error: "noScript", 1.3099 + message: this.actorID + " has no Debugger.Script" 1.3100 + }; 1.3101 + } 1.3102 + 1.3103 + const generatedLocation = { 1.3104 + url: this.obj.script.url, 1.3105 + line: this.obj.script.startLine, 1.3106 + // TODO bug 901138: use Debugger.Script.prototype.startColumn. 1.3107 + column: 0 1.3108 + }; 1.3109 + 1.3110 + return this.threadActor.sources.getOriginalLocation(generatedLocation) 1.3111 + .then(({ url, line, column }) => { 1.3112 + return { 1.3113 + from: this.actorID, 1.3114 + url: url, 1.3115 + line: line, 1.3116 + column: column 1.3117 + }; 1.3118 + }); 1.3119 + }, 1.3120 + 1.3121 + /** 1.3122 + * Handle a protocol request to provide the names of the properties defined on 1.3123 + * the object and not its prototype. 1.3124 + * 1.3125 + * @param aRequest object 1.3126 + * The protocol request object. 1.3127 + */ 1.3128 + onOwnPropertyNames: function (aRequest) { 1.3129 + return { from: this.actorID, 1.3130 + ownPropertyNames: this.obj.getOwnPropertyNames() }; 1.3131 + }, 1.3132 + 1.3133 + /** 1.3134 + * Handle a protocol request to provide the prototype and own properties of 1.3135 + * the object. 1.3136 + * 1.3137 + * @param aRequest object 1.3138 + * The protocol request object. 1.3139 + */ 1.3140 + onPrototypeAndProperties: function (aRequest) { 1.3141 + let ownProperties = Object.create(null); 1.3142 + let names; 1.3143 + try { 1.3144 + names = this.obj.getOwnPropertyNames(); 1.3145 + } catch (ex) { 1.3146 + // The above can throw if this.obj points to a dead object. 1.3147 + // TODO: we should use Cu.isDeadWrapper() - see bug 885800. 1.3148 + return { from: this.actorID, 1.3149 + prototype: this.threadActor.createValueGrip(null), 1.3150 + ownProperties: ownProperties, 1.3151 + safeGetterValues: Object.create(null) }; 1.3152 + } 1.3153 + for (let name of names) { 1.3154 + ownProperties[name] = this._propertyDescriptor(name); 1.3155 + } 1.3156 + return { from: this.actorID, 1.3157 + prototype: this.threadActor.createValueGrip(this.obj.proto), 1.3158 + ownProperties: ownProperties, 1.3159 + safeGetterValues: this._findSafeGetterValues(ownProperties) }; 1.3160 + }, 1.3161 + 1.3162 + /** 1.3163 + * Find the safe getter values for the current Debugger.Object, |this.obj|. 1.3164 + * 1.3165 + * @private 1.3166 + * @param object aOwnProperties 1.3167 + * The object that holds the list of known ownProperties for 1.3168 + * |this.obj|. 1.3169 + * @param number [aLimit=0] 1.3170 + * Optional limit of getter values to find. 1.3171 + * @return object 1.3172 + * An object that maps property names to safe getter descriptors as 1.3173 + * defined by the remote debugging protocol. 1.3174 + */ 1.3175 + _findSafeGetterValues: function (aOwnProperties, aLimit = 0) 1.3176 + { 1.3177 + let safeGetterValues = Object.create(null); 1.3178 + let obj = this.obj; 1.3179 + let level = 0, i = 0; 1.3180 + 1.3181 + while (obj) { 1.3182 + let getters = this._findSafeGetters(obj); 1.3183 + for (let name of getters) { 1.3184 + // Avoid overwriting properties from prototypes closer to this.obj. Also 1.3185 + // avoid providing safeGetterValues from prototypes if property |name| 1.3186 + // is already defined as an own property. 1.3187 + if (name in safeGetterValues || 1.3188 + (obj != this.obj && name in aOwnProperties)) { 1.3189 + continue; 1.3190 + } 1.3191 + 1.3192 + let desc = null, getter = null; 1.3193 + try { 1.3194 + desc = obj.getOwnPropertyDescriptor(name); 1.3195 + getter = desc.get; 1.3196 + } catch (ex) { 1.3197 + // The above can throw if the cache becomes stale. 1.3198 + } 1.3199 + if (!getter) { 1.3200 + obj._safeGetters = null; 1.3201 + continue; 1.3202 + } 1.3203 + 1.3204 + let result = getter.call(this.obj); 1.3205 + if (result && !("throw" in result)) { 1.3206 + let getterValue = undefined; 1.3207 + if ("return" in result) { 1.3208 + getterValue = result.return; 1.3209 + } else if ("yield" in result) { 1.3210 + getterValue = result.yield; 1.3211 + } 1.3212 + // WebIDL attributes specified with the LenientThis extended attribute 1.3213 + // return undefined and should be ignored. 1.3214 + if (getterValue !== undefined) { 1.3215 + safeGetterValues[name] = { 1.3216 + getterValue: this.threadActor.createValueGrip(getterValue), 1.3217 + getterPrototypeLevel: level, 1.3218 + enumerable: desc.enumerable, 1.3219 + writable: level == 0 ? desc.writable : true, 1.3220 + }; 1.3221 + if (aLimit && ++i == aLimit) { 1.3222 + break; 1.3223 + } 1.3224 + } 1.3225 + } 1.3226 + } 1.3227 + if (aLimit && i == aLimit) { 1.3228 + break; 1.3229 + } 1.3230 + 1.3231 + obj = obj.proto; 1.3232 + level++; 1.3233 + } 1.3234 + 1.3235 + return safeGetterValues; 1.3236 + }, 1.3237 + 1.3238 + /** 1.3239 + * Find the safe getters for a given Debugger.Object. Safe getters are native 1.3240 + * getters which are safe to execute. 1.3241 + * 1.3242 + * @private 1.3243 + * @param Debugger.Object aObject 1.3244 + * The Debugger.Object where you want to find safe getters. 1.3245 + * @return Set 1.3246 + * A Set of names of safe getters. This result is cached for each 1.3247 + * Debugger.Object. 1.3248 + */ 1.3249 + _findSafeGetters: function (aObject) 1.3250 + { 1.3251 + if (aObject._safeGetters) { 1.3252 + return aObject._safeGetters; 1.3253 + } 1.3254 + 1.3255 + let getters = new Set(); 1.3256 + let names = []; 1.3257 + try { 1.3258 + names = aObject.getOwnPropertyNames() 1.3259 + } catch (ex) { 1.3260 + // Calling getOwnPropertyNames() on some wrapped native prototypes is not 1.3261 + // allowed: "cannot modify properties of a WrappedNative". See bug 952093. 1.3262 + } 1.3263 + 1.3264 + for (let name of names) { 1.3265 + let desc = null; 1.3266 + try { 1.3267 + desc = aObject.getOwnPropertyDescriptor(name); 1.3268 + } catch (e) { 1.3269 + // Calling getOwnPropertyDescriptor on wrapped native prototypes is not 1.3270 + // allowed (bug 560072). 1.3271 + } 1.3272 + if (!desc || desc.value !== undefined || !("get" in desc)) { 1.3273 + continue; 1.3274 + } 1.3275 + 1.3276 + if (DevToolsUtils.hasSafeGetter(desc)) { 1.3277 + getters.add(name); 1.3278 + } 1.3279 + } 1.3280 + 1.3281 + aObject._safeGetters = getters; 1.3282 + return getters; 1.3283 + }, 1.3284 + 1.3285 + /** 1.3286 + * Handle a protocol request to provide the prototype of the object. 1.3287 + * 1.3288 + * @param aRequest object 1.3289 + * The protocol request object. 1.3290 + */ 1.3291 + onPrototype: function (aRequest) { 1.3292 + return { from: this.actorID, 1.3293 + prototype: this.threadActor.createValueGrip(this.obj.proto) }; 1.3294 + }, 1.3295 + 1.3296 + /** 1.3297 + * Handle a protocol request to provide the property descriptor of the 1.3298 + * object's specified property. 1.3299 + * 1.3300 + * @param aRequest object 1.3301 + * The protocol request object. 1.3302 + */ 1.3303 + onProperty: function (aRequest) { 1.3304 + if (!aRequest.name) { 1.3305 + return { error: "missingParameter", 1.3306 + message: "no property name was specified" }; 1.3307 + } 1.3308 + 1.3309 + return { from: this.actorID, 1.3310 + descriptor: this._propertyDescriptor(aRequest.name) }; 1.3311 + }, 1.3312 + 1.3313 + /** 1.3314 + * Handle a protocol request to provide the display string for the object. 1.3315 + * 1.3316 + * @param aRequest object 1.3317 + * The protocol request object. 1.3318 + */ 1.3319 + onDisplayString: function (aRequest) { 1.3320 + const string = stringify(this.obj); 1.3321 + return { from: this.actorID, 1.3322 + displayString: this.threadActor.createValueGrip(string) }; 1.3323 + }, 1.3324 + 1.3325 + /** 1.3326 + * A helper method that creates a property descriptor for the provided object, 1.3327 + * properly formatted for sending in a protocol response. 1.3328 + * 1.3329 + * @private 1.3330 + * @param string aName 1.3331 + * The property that the descriptor is generated for. 1.3332 + * @param boolean [aOnlyEnumerable] 1.3333 + * Optional: true if you want a descriptor only for an enumerable 1.3334 + * property, false otherwise. 1.3335 + * @return object|undefined 1.3336 + * The property descriptor, or undefined if this is not an enumerable 1.3337 + * property and aOnlyEnumerable=true. 1.3338 + */ 1.3339 + _propertyDescriptor: function (aName, aOnlyEnumerable) { 1.3340 + let desc; 1.3341 + try { 1.3342 + desc = this.obj.getOwnPropertyDescriptor(aName); 1.3343 + } catch (e) { 1.3344 + // Calling getOwnPropertyDescriptor on wrapped native prototypes is not 1.3345 + // allowed (bug 560072). Inform the user with a bogus, but hopefully 1.3346 + // explanatory, descriptor. 1.3347 + return { 1.3348 + configurable: false, 1.3349 + writable: false, 1.3350 + enumerable: false, 1.3351 + value: e.name 1.3352 + }; 1.3353 + } 1.3354 + 1.3355 + if (!desc || aOnlyEnumerable && !desc.enumerable) { 1.3356 + return undefined; 1.3357 + } 1.3358 + 1.3359 + let retval = { 1.3360 + configurable: desc.configurable, 1.3361 + enumerable: desc.enumerable 1.3362 + }; 1.3363 + 1.3364 + if ("value" in desc) { 1.3365 + retval.writable = desc.writable; 1.3366 + retval.value = this.threadActor.createValueGrip(desc.value); 1.3367 + } else { 1.3368 + if ("get" in desc) { 1.3369 + retval.get = this.threadActor.createValueGrip(desc.get); 1.3370 + } 1.3371 + if ("set" in desc) { 1.3372 + retval.set = this.threadActor.createValueGrip(desc.set); 1.3373 + } 1.3374 + } 1.3375 + return retval; 1.3376 + }, 1.3377 + 1.3378 + /** 1.3379 + * Handle a protocol request to provide the source code of a function. 1.3380 + * 1.3381 + * @param aRequest object 1.3382 + * The protocol request object. 1.3383 + */ 1.3384 + onDecompile: function (aRequest) { 1.3385 + if (this.obj.class !== "Function") { 1.3386 + return { error: "objectNotFunction", 1.3387 + message: "decompile request is only valid for object grips " + 1.3388 + "with a 'Function' class." }; 1.3389 + } 1.3390 + 1.3391 + return { from: this.actorID, 1.3392 + decompiledCode: this.obj.decompile(!!aRequest.pretty) }; 1.3393 + }, 1.3394 + 1.3395 + /** 1.3396 + * Handle a protocol request to provide the parameters of a function. 1.3397 + * 1.3398 + * @param aRequest object 1.3399 + * The protocol request object. 1.3400 + */ 1.3401 + onParameterNames: function (aRequest) { 1.3402 + if (this.obj.class !== "Function") { 1.3403 + return { error: "objectNotFunction", 1.3404 + message: "'parameterNames' request is only valid for object " + 1.3405 + "grips with a 'Function' class." }; 1.3406 + } 1.3407 + 1.3408 + return { parameterNames: this.obj.parameterNames }; 1.3409 + }, 1.3410 + 1.3411 + /** 1.3412 + * Handle a protocol request to release a thread-lifetime grip. 1.3413 + * 1.3414 + * @param aRequest object 1.3415 + * The protocol request object. 1.3416 + */ 1.3417 + onRelease: function (aRequest) { 1.3418 + this.release(); 1.3419 + return {}; 1.3420 + }, 1.3421 + 1.3422 + /** 1.3423 + * Handle a protocol request to provide the lexical scope of a function. 1.3424 + * 1.3425 + * @param aRequest object 1.3426 + * The protocol request object. 1.3427 + */ 1.3428 + onScope: function (aRequest) { 1.3429 + if (this.obj.class !== "Function") { 1.3430 + return { error: "objectNotFunction", 1.3431 + message: "scope request is only valid for object grips with a" + 1.3432 + " 'Function' class." }; 1.3433 + } 1.3434 + 1.3435 + let envActor = this.threadActor.createEnvironmentActor(this.obj.environment, 1.3436 + this.registeredPool); 1.3437 + if (!envActor) { 1.3438 + return { error: "notDebuggee", 1.3439 + message: "cannot access the environment of this function." }; 1.3440 + } 1.3441 + 1.3442 + return { from: this.actorID, scope: envActor.form() }; 1.3443 + } 1.3444 +}; 1.3445 + 1.3446 +ObjectActor.prototype.requestTypes = { 1.3447 + "definitionSite": ObjectActor.prototype.onDefinitionSite, 1.3448 + "parameterNames": ObjectActor.prototype.onParameterNames, 1.3449 + "prototypeAndProperties": ObjectActor.prototype.onPrototypeAndProperties, 1.3450 + "prototype": ObjectActor.prototype.onPrototype, 1.3451 + "property": ObjectActor.prototype.onProperty, 1.3452 + "displayString": ObjectActor.prototype.onDisplayString, 1.3453 + "ownPropertyNames": ObjectActor.prototype.onOwnPropertyNames, 1.3454 + "decompile": ObjectActor.prototype.onDecompile, 1.3455 + "release": ObjectActor.prototype.onRelease, 1.3456 + "scope": ObjectActor.prototype.onScope, 1.3457 +}; 1.3458 + 1.3459 + 1.3460 +/** 1.3461 + * Functions for adding information to ObjectActor grips for the purpose of 1.3462 + * having customized output. This object holds arrays mapped by 1.3463 + * Debugger.Object.prototype.class. 1.3464 + * 1.3465 + * In each array you can add functions that take two 1.3466 + * arguments: 1.3467 + * - the ObjectActor instance to make a preview for, 1.3468 + * - the grip object being prepared for the client, 1.3469 + * - the raw JS object after calling Debugger.Object.unsafeDereference(). This 1.3470 + * argument is only provided if the object is safe for reading properties and 1.3471 + * executing methods. See DevToolsUtils.isSafeJSObject(). 1.3472 + * 1.3473 + * Functions must return false if they cannot provide preview 1.3474 + * information for the debugger object, or true otherwise. 1.3475 + */ 1.3476 +DebuggerServer.ObjectActorPreviewers = { 1.3477 + String: [function({obj, threadActor}, aGrip) { 1.3478 + let result = genericObjectPreviewer("String", String, obj, threadActor); 1.3479 + if (result) { 1.3480 + let length = DevToolsUtils.getProperty(obj, "length"); 1.3481 + if (typeof length != "number") { 1.3482 + return false; 1.3483 + } 1.3484 + 1.3485 + aGrip.displayString = result.value; 1.3486 + return true; 1.3487 + } 1.3488 + 1.3489 + return true; 1.3490 + }], 1.3491 + 1.3492 + Boolean: [function({obj, threadActor}, aGrip) { 1.3493 + let result = genericObjectPreviewer("Boolean", Boolean, obj, threadActor); 1.3494 + if (result) { 1.3495 + aGrip.preview = result; 1.3496 + return true; 1.3497 + } 1.3498 + 1.3499 + return false; 1.3500 + }], 1.3501 + 1.3502 + Number: [function({obj, threadActor}, aGrip) { 1.3503 + let result = genericObjectPreviewer("Number", Number, obj, threadActor); 1.3504 + if (result) { 1.3505 + aGrip.preview = result; 1.3506 + return true; 1.3507 + } 1.3508 + 1.3509 + return false; 1.3510 + }], 1.3511 + 1.3512 + Function: [function({obj, threadActor}, aGrip) { 1.3513 + if (obj.name) { 1.3514 + aGrip.name = obj.name; 1.3515 + } 1.3516 + 1.3517 + if (obj.displayName) { 1.3518 + aGrip.displayName = obj.displayName.substr(0, 500); 1.3519 + } 1.3520 + 1.3521 + if (obj.parameterNames) { 1.3522 + aGrip.parameterNames = obj.parameterNames; 1.3523 + } 1.3524 + 1.3525 + // Check if the developer has added a de-facto standard displayName 1.3526 + // property for us to use. 1.3527 + let userDisplayName; 1.3528 + try { 1.3529 + userDisplayName = obj.getOwnPropertyDescriptor("displayName"); 1.3530 + } catch (e) { 1.3531 + // Calling getOwnPropertyDescriptor with displayName might throw 1.3532 + // with "permission denied" errors for some functions. 1.3533 + dumpn(e); 1.3534 + } 1.3535 + 1.3536 + if (userDisplayName && typeof userDisplayName.value == "string" && 1.3537 + userDisplayName.value) { 1.3538 + aGrip.userDisplayName = threadActor.createValueGrip(userDisplayName.value); 1.3539 + } 1.3540 + 1.3541 + return true; 1.3542 + }], 1.3543 + 1.3544 + RegExp: [function({obj, threadActor}, aGrip) { 1.3545 + // Avoid having any special preview for the RegExp.prototype itself. 1.3546 + if (!obj.proto || obj.proto.class != "RegExp") { 1.3547 + return false; 1.3548 + } 1.3549 + 1.3550 + let str = RegExp.prototype.toString.call(obj.unsafeDereference()); 1.3551 + aGrip.displayString = threadActor.createValueGrip(str); 1.3552 + return true; 1.3553 + }], 1.3554 + 1.3555 + Date: [function({obj, threadActor}, aGrip) { 1.3556 + if (!obj.proto || obj.proto.class != "Date") { 1.3557 + return false; 1.3558 + } 1.3559 + 1.3560 + let time = Date.prototype.getTime.call(obj.unsafeDereference()); 1.3561 + 1.3562 + aGrip.preview = { 1.3563 + timestamp: threadActor.createValueGrip(time), 1.3564 + }; 1.3565 + return true; 1.3566 + }], 1.3567 + 1.3568 + Array: [function({obj, threadActor}, aGrip) { 1.3569 + let length = DevToolsUtils.getProperty(obj, "length"); 1.3570 + if (typeof length != "number") { 1.3571 + return false; 1.3572 + } 1.3573 + 1.3574 + aGrip.preview = { 1.3575 + kind: "ArrayLike", 1.3576 + length: length, 1.3577 + }; 1.3578 + 1.3579 + if (threadActor._gripDepth > 1) { 1.3580 + return true; 1.3581 + } 1.3582 + 1.3583 + let raw = obj.unsafeDereference(); 1.3584 + let items = aGrip.preview.items = []; 1.3585 + 1.3586 + for (let [i, value] of Array.prototype.entries.call(raw)) { 1.3587 + if (Object.hasOwnProperty.call(raw, i)) { 1.3588 + value = makeDebuggeeValueIfNeeded(obj, value); 1.3589 + items.push(threadActor.createValueGrip(value)); 1.3590 + } else { 1.3591 + items.push(null); 1.3592 + } 1.3593 + 1.3594 + if (items.length == OBJECT_PREVIEW_MAX_ITEMS) { 1.3595 + break; 1.3596 + } 1.3597 + } 1.3598 + 1.3599 + return true; 1.3600 + }], // Array 1.3601 + 1.3602 + Set: [function({obj, threadActor}, aGrip) { 1.3603 + let size = DevToolsUtils.getProperty(obj, "size"); 1.3604 + if (typeof size != "number") { 1.3605 + return false; 1.3606 + } 1.3607 + 1.3608 + aGrip.preview = { 1.3609 + kind: "ArrayLike", 1.3610 + length: size, 1.3611 + }; 1.3612 + 1.3613 + // Avoid recursive object grips. 1.3614 + if (threadActor._gripDepth > 1) { 1.3615 + return true; 1.3616 + } 1.3617 + 1.3618 + let raw = obj.unsafeDereference(); 1.3619 + let items = aGrip.preview.items = []; 1.3620 + for (let item of Set.prototype.values.call(raw)) { 1.3621 + item = makeDebuggeeValueIfNeeded(obj, item); 1.3622 + items.push(threadActor.createValueGrip(item)); 1.3623 + if (items.length == OBJECT_PREVIEW_MAX_ITEMS) { 1.3624 + break; 1.3625 + } 1.3626 + } 1.3627 + 1.3628 + return true; 1.3629 + }], // Set 1.3630 + 1.3631 + Map: [function({obj, threadActor}, aGrip) { 1.3632 + let size = DevToolsUtils.getProperty(obj, "size"); 1.3633 + if (typeof size != "number") { 1.3634 + return false; 1.3635 + } 1.3636 + 1.3637 + aGrip.preview = { 1.3638 + kind: "MapLike", 1.3639 + size: size, 1.3640 + }; 1.3641 + 1.3642 + if (threadActor._gripDepth > 1) { 1.3643 + return true; 1.3644 + } 1.3645 + 1.3646 + let raw = obj.unsafeDereference(); 1.3647 + let entries = aGrip.preview.entries = []; 1.3648 + for (let [key, value] of Map.prototype.entries.call(raw)) { 1.3649 + key = makeDebuggeeValueIfNeeded(obj, key); 1.3650 + value = makeDebuggeeValueIfNeeded(obj, value); 1.3651 + entries.push([threadActor.createValueGrip(key), 1.3652 + threadActor.createValueGrip(value)]); 1.3653 + if (entries.length == OBJECT_PREVIEW_MAX_ITEMS) { 1.3654 + break; 1.3655 + } 1.3656 + } 1.3657 + 1.3658 + return true; 1.3659 + }], // Map 1.3660 + 1.3661 + DOMStringMap: [function({obj, threadActor}, aGrip, aRawObj) { 1.3662 + if (!aRawObj) { 1.3663 + return false; 1.3664 + } 1.3665 + 1.3666 + let keys = obj.getOwnPropertyNames(); 1.3667 + aGrip.preview = { 1.3668 + kind: "MapLike", 1.3669 + size: keys.length, 1.3670 + }; 1.3671 + 1.3672 + if (threadActor._gripDepth > 1) { 1.3673 + return true; 1.3674 + } 1.3675 + 1.3676 + let entries = aGrip.preview.entries = []; 1.3677 + for (let key of keys) { 1.3678 + let value = makeDebuggeeValueIfNeeded(obj, aRawObj[key]); 1.3679 + entries.push([key, threadActor.createValueGrip(value)]); 1.3680 + if (entries.length == OBJECT_PREVIEW_MAX_ITEMS) { 1.3681 + break; 1.3682 + } 1.3683 + } 1.3684 + 1.3685 + return true; 1.3686 + }], // DOMStringMap 1.3687 +}; // DebuggerServer.ObjectActorPreviewers 1.3688 + 1.3689 +/** 1.3690 + * Generic previewer for "simple" classes like String, Number and Boolean. 1.3691 + * 1.3692 + * @param string aClassName 1.3693 + * Class name to expect. 1.3694 + * @param object aClass 1.3695 + * The class to expect, eg. String. The valueOf() method of the class is 1.3696 + * invoked on the given object. 1.3697 + * @param Debugger.Object aObj 1.3698 + * The debugger object we need to preview. 1.3699 + * @param object aThreadActor 1.3700 + * The thread actor to use to create a value grip. 1.3701 + * @return object|null 1.3702 + * An object with one property, "value", which holds the value grip that 1.3703 + * represents the given object. Null is returned if we cant preview the 1.3704 + * object. 1.3705 + */ 1.3706 +function genericObjectPreviewer(aClassName, aClass, aObj, aThreadActor) { 1.3707 + if (!aObj.proto || aObj.proto.class != aClassName) { 1.3708 + return null; 1.3709 + } 1.3710 + 1.3711 + let raw = aObj.unsafeDereference(); 1.3712 + let v = null; 1.3713 + try { 1.3714 + v = aClass.prototype.valueOf.call(raw); 1.3715 + } catch (ex) { 1.3716 + // valueOf() can throw if the raw JS object is "misbehaved". 1.3717 + return null; 1.3718 + } 1.3719 + 1.3720 + if (v !== null) { 1.3721 + v = aThreadActor.createValueGrip(makeDebuggeeValueIfNeeded(aObj, v)); 1.3722 + return { value: v }; 1.3723 + } 1.3724 + 1.3725 + return null; 1.3726 +} 1.3727 + 1.3728 +// Preview functions that do not rely on the object class. 1.3729 +DebuggerServer.ObjectActorPreviewers.Object = [ 1.3730 + function TypedArray({obj, threadActor}, aGrip) { 1.3731 + if (TYPED_ARRAY_CLASSES.indexOf(obj.class) == -1) { 1.3732 + return false; 1.3733 + } 1.3734 + 1.3735 + let length = DevToolsUtils.getProperty(obj, "length"); 1.3736 + if (typeof length != "number") { 1.3737 + return false; 1.3738 + } 1.3739 + 1.3740 + aGrip.preview = { 1.3741 + kind: "ArrayLike", 1.3742 + length: length, 1.3743 + }; 1.3744 + 1.3745 + if (threadActor._gripDepth > 1) { 1.3746 + return true; 1.3747 + } 1.3748 + 1.3749 + let raw = obj.unsafeDereference(); 1.3750 + let global = Cu.getGlobalForObject(DebuggerServer); 1.3751 + let classProto = global[obj.class].prototype; 1.3752 + let safeView = classProto.subarray.call(raw, 0, OBJECT_PREVIEW_MAX_ITEMS); 1.3753 + let items = aGrip.preview.items = []; 1.3754 + for (let i = 0; i < safeView.length; i++) { 1.3755 + items.push(safeView[i]); 1.3756 + } 1.3757 + 1.3758 + return true; 1.3759 + }, 1.3760 + 1.3761 + function Error({obj, threadActor}, aGrip) { 1.3762 + switch (obj.class) { 1.3763 + case "Error": 1.3764 + case "EvalError": 1.3765 + case "RangeError": 1.3766 + case "ReferenceError": 1.3767 + case "SyntaxError": 1.3768 + case "TypeError": 1.3769 + case "URIError": 1.3770 + let name = DevToolsUtils.getProperty(obj, "name"); 1.3771 + let msg = DevToolsUtils.getProperty(obj, "message"); 1.3772 + let stack = DevToolsUtils.getProperty(obj, "stack"); 1.3773 + let fileName = DevToolsUtils.getProperty(obj, "fileName"); 1.3774 + let lineNumber = DevToolsUtils.getProperty(obj, "lineNumber"); 1.3775 + let columnNumber = DevToolsUtils.getProperty(obj, "columnNumber"); 1.3776 + aGrip.preview = { 1.3777 + kind: "Error", 1.3778 + name: threadActor.createValueGrip(name), 1.3779 + message: threadActor.createValueGrip(msg), 1.3780 + stack: threadActor.createValueGrip(stack), 1.3781 + fileName: threadActor.createValueGrip(fileName), 1.3782 + lineNumber: threadActor.createValueGrip(lineNumber), 1.3783 + columnNumber: threadActor.createValueGrip(columnNumber), 1.3784 + }; 1.3785 + return true; 1.3786 + default: 1.3787 + return false; 1.3788 + } 1.3789 + }, 1.3790 + 1.3791 + function CSSMediaRule({obj, threadActor}, aGrip, aRawObj) { 1.3792 + if (!aRawObj || !(aRawObj instanceof Ci.nsIDOMCSSMediaRule)) { 1.3793 + return false; 1.3794 + } 1.3795 + aGrip.preview = { 1.3796 + kind: "ObjectWithText", 1.3797 + text: threadActor.createValueGrip(aRawObj.conditionText), 1.3798 + }; 1.3799 + return true; 1.3800 + }, 1.3801 + 1.3802 + function CSSStyleRule({obj, threadActor}, aGrip, aRawObj) { 1.3803 + if (!aRawObj || !(aRawObj instanceof Ci.nsIDOMCSSStyleRule)) { 1.3804 + return false; 1.3805 + } 1.3806 + aGrip.preview = { 1.3807 + kind: "ObjectWithText", 1.3808 + text: threadActor.createValueGrip(aRawObj.selectorText), 1.3809 + }; 1.3810 + return true; 1.3811 + }, 1.3812 + 1.3813 + function ObjectWithURL({obj, threadActor}, aGrip, aRawObj) { 1.3814 + if (!aRawObj || 1.3815 + !(aRawObj instanceof Ci.nsIDOMCSSImportRule || 1.3816 + aRawObj instanceof Ci.nsIDOMCSSStyleSheet || 1.3817 + aRawObj instanceof Ci.nsIDOMLocation || 1.3818 + aRawObj instanceof Ci.nsIDOMWindow)) { 1.3819 + return false; 1.3820 + } 1.3821 + 1.3822 + let url; 1.3823 + if (aRawObj instanceof Ci.nsIDOMWindow && aRawObj.location) { 1.3824 + url = aRawObj.location.href; 1.3825 + } else if (aRawObj.href) { 1.3826 + url = aRawObj.href; 1.3827 + } else { 1.3828 + return false; 1.3829 + } 1.3830 + 1.3831 + aGrip.preview = { 1.3832 + kind: "ObjectWithURL", 1.3833 + url: threadActor.createValueGrip(url), 1.3834 + }; 1.3835 + 1.3836 + return true; 1.3837 + }, 1.3838 + 1.3839 + function ArrayLike({obj, threadActor}, aGrip, aRawObj) { 1.3840 + if (!aRawObj || 1.3841 + obj.class != "DOMStringList" && 1.3842 + obj.class != "DOMTokenList" && 1.3843 + !(aRawObj instanceof Ci.nsIDOMMozNamedAttrMap || 1.3844 + aRawObj instanceof Ci.nsIDOMCSSRuleList || 1.3845 + aRawObj instanceof Ci.nsIDOMCSSValueList || 1.3846 + aRawObj instanceof Ci.nsIDOMFileList || 1.3847 + aRawObj instanceof Ci.nsIDOMFontFaceList || 1.3848 + aRawObj instanceof Ci.nsIDOMMediaList || 1.3849 + aRawObj instanceof Ci.nsIDOMNodeList || 1.3850 + aRawObj instanceof Ci.nsIDOMStyleSheetList)) { 1.3851 + return false; 1.3852 + } 1.3853 + 1.3854 + if (typeof aRawObj.length != "number") { 1.3855 + return false; 1.3856 + } 1.3857 + 1.3858 + aGrip.preview = { 1.3859 + kind: "ArrayLike", 1.3860 + length: aRawObj.length, 1.3861 + }; 1.3862 + 1.3863 + if (threadActor._gripDepth > 1) { 1.3864 + return true; 1.3865 + } 1.3866 + 1.3867 + let items = aGrip.preview.items = []; 1.3868 + 1.3869 + for (let i = 0; i < aRawObj.length && 1.3870 + items.length < OBJECT_PREVIEW_MAX_ITEMS; i++) { 1.3871 + let value = makeDebuggeeValueIfNeeded(obj, aRawObj[i]); 1.3872 + items.push(threadActor.createValueGrip(value)); 1.3873 + } 1.3874 + 1.3875 + return true; 1.3876 + }, // ArrayLike 1.3877 + 1.3878 + function CSSStyleDeclaration({obj, threadActor}, aGrip, aRawObj) { 1.3879 + if (!aRawObj || !(aRawObj instanceof Ci.nsIDOMCSSStyleDeclaration)) { 1.3880 + return false; 1.3881 + } 1.3882 + 1.3883 + aGrip.preview = { 1.3884 + kind: "MapLike", 1.3885 + size: aRawObj.length, 1.3886 + }; 1.3887 + 1.3888 + let entries = aGrip.preview.entries = []; 1.3889 + 1.3890 + for (let i = 0; i < OBJECT_PREVIEW_MAX_ITEMS && 1.3891 + i < aRawObj.length; i++) { 1.3892 + let prop = aRawObj[i]; 1.3893 + let value = aRawObj.getPropertyValue(prop); 1.3894 + entries.push([prop, threadActor.createValueGrip(value)]); 1.3895 + } 1.3896 + 1.3897 + return true; 1.3898 + }, 1.3899 + 1.3900 + function DOMNode({obj, threadActor}, aGrip, aRawObj) { 1.3901 + if (obj.class == "Object" || !aRawObj || !(aRawObj instanceof Ci.nsIDOMNode)) { 1.3902 + return false; 1.3903 + } 1.3904 + 1.3905 + let preview = aGrip.preview = { 1.3906 + kind: "DOMNode", 1.3907 + nodeType: aRawObj.nodeType, 1.3908 + nodeName: aRawObj.nodeName, 1.3909 + }; 1.3910 + 1.3911 + if (aRawObj instanceof Ci.nsIDOMDocument && aRawObj.location) { 1.3912 + preview.location = threadActor.createValueGrip(aRawObj.location.href); 1.3913 + } else if (aRawObj instanceof Ci.nsIDOMDocumentFragment) { 1.3914 + preview.childNodesLength = aRawObj.childNodes.length; 1.3915 + 1.3916 + if (threadActor._gripDepth < 2) { 1.3917 + preview.childNodes = []; 1.3918 + for (let node of aRawObj.childNodes) { 1.3919 + let actor = threadActor.createValueGrip(obj.makeDebuggeeValue(node)); 1.3920 + preview.childNodes.push(actor); 1.3921 + if (preview.childNodes.length == OBJECT_PREVIEW_MAX_ITEMS) { 1.3922 + break; 1.3923 + } 1.3924 + } 1.3925 + } 1.3926 + } else if (aRawObj instanceof Ci.nsIDOMElement) { 1.3927 + // Add preview for DOM element attributes. 1.3928 + if (aRawObj instanceof Ci.nsIDOMHTMLElement) { 1.3929 + preview.nodeName = preview.nodeName.toLowerCase(); 1.3930 + } 1.3931 + 1.3932 + let i = 0; 1.3933 + preview.attributes = {}; 1.3934 + preview.attributesLength = aRawObj.attributes.length; 1.3935 + for (let attr of aRawObj.attributes) { 1.3936 + preview.attributes[attr.nodeName] = threadActor.createValueGrip(attr.value); 1.3937 + if (++i == OBJECT_PREVIEW_MAX_ITEMS) { 1.3938 + break; 1.3939 + } 1.3940 + } 1.3941 + } else if (aRawObj instanceof Ci.nsIDOMAttr) { 1.3942 + preview.value = threadActor.createValueGrip(aRawObj.value); 1.3943 + } else if (aRawObj instanceof Ci.nsIDOMText || 1.3944 + aRawObj instanceof Ci.nsIDOMComment) { 1.3945 + preview.textContent = threadActor.createValueGrip(aRawObj.textContent); 1.3946 + } 1.3947 + 1.3948 + return true; 1.3949 + }, // DOMNode 1.3950 + 1.3951 + function DOMEvent({obj, threadActor}, aGrip, aRawObj) { 1.3952 + if (!aRawObj || !(aRawObj instanceof Ci.nsIDOMEvent)) { 1.3953 + return false; 1.3954 + } 1.3955 + 1.3956 + let preview = aGrip.preview = { 1.3957 + kind: "DOMEvent", 1.3958 + type: aRawObj.type, 1.3959 + properties: Object.create(null), 1.3960 + }; 1.3961 + 1.3962 + if (threadActor._gripDepth < 2) { 1.3963 + let target = obj.makeDebuggeeValue(aRawObj.target); 1.3964 + preview.target = threadActor.createValueGrip(target); 1.3965 + } 1.3966 + 1.3967 + let props = []; 1.3968 + if (aRawObj instanceof Ci.nsIDOMMouseEvent) { 1.3969 + props.push("buttons", "clientX", "clientY", "layerX", "layerY"); 1.3970 + } else if (aRawObj instanceof Ci.nsIDOMKeyEvent) { 1.3971 + let modifiers = []; 1.3972 + if (aRawObj.altKey) { 1.3973 + modifiers.push("Alt"); 1.3974 + } 1.3975 + if (aRawObj.ctrlKey) { 1.3976 + modifiers.push("Control"); 1.3977 + } 1.3978 + if (aRawObj.metaKey) { 1.3979 + modifiers.push("Meta"); 1.3980 + } 1.3981 + if (aRawObj.shiftKey) { 1.3982 + modifiers.push("Shift"); 1.3983 + } 1.3984 + preview.eventKind = "key"; 1.3985 + preview.modifiers = modifiers; 1.3986 + 1.3987 + props.push("key", "charCode", "keyCode"); 1.3988 + } else if (aRawObj instanceof Ci.nsIDOMTransitionEvent || 1.3989 + aRawObj instanceof Ci.nsIDOMAnimationEvent) { 1.3990 + props.push("animationName", "pseudoElement"); 1.3991 + } else if (aRawObj instanceof Ci.nsIDOMClipboardEvent) { 1.3992 + props.push("clipboardData"); 1.3993 + } 1.3994 + 1.3995 + // Add event-specific properties. 1.3996 + for (let prop of props) { 1.3997 + let value = aRawObj[prop]; 1.3998 + if (value && (typeof value == "object" || typeof value == "function")) { 1.3999 + // Skip properties pointing to objects. 1.4000 + if (threadActor._gripDepth > 1) { 1.4001 + continue; 1.4002 + } 1.4003 + value = obj.makeDebuggeeValue(value); 1.4004 + } 1.4005 + preview.properties[prop] = threadActor.createValueGrip(value); 1.4006 + } 1.4007 + 1.4008 + // Add any properties we find on the event object. 1.4009 + if (!props.length) { 1.4010 + let i = 0; 1.4011 + for (let prop in aRawObj) { 1.4012 + let value = aRawObj[prop]; 1.4013 + if (prop == "target" || prop == "type" || value === null || 1.4014 + typeof value == "function") { 1.4015 + continue; 1.4016 + } 1.4017 + if (value && typeof value == "object") { 1.4018 + if (threadActor._gripDepth > 1) { 1.4019 + continue; 1.4020 + } 1.4021 + value = obj.makeDebuggeeValue(value); 1.4022 + } 1.4023 + preview.properties[prop] = threadActor.createValueGrip(value); 1.4024 + if (++i == OBJECT_PREVIEW_MAX_ITEMS) { 1.4025 + break; 1.4026 + } 1.4027 + } 1.4028 + } 1.4029 + 1.4030 + return true; 1.4031 + }, // DOMEvent 1.4032 + 1.4033 + function DOMException({obj, threadActor}, aGrip, aRawObj) { 1.4034 + if (!aRawObj || !(aRawObj instanceof Ci.nsIDOMDOMException)) { 1.4035 + return false; 1.4036 + } 1.4037 + 1.4038 + aGrip.preview = { 1.4039 + kind: "DOMException", 1.4040 + name: threadActor.createValueGrip(aRawObj.name), 1.4041 + message: threadActor.createValueGrip(aRawObj.message), 1.4042 + code: threadActor.createValueGrip(aRawObj.code), 1.4043 + result: threadActor.createValueGrip(aRawObj.result), 1.4044 + filename: threadActor.createValueGrip(aRawObj.filename), 1.4045 + lineNumber: threadActor.createValueGrip(aRawObj.lineNumber), 1.4046 + columnNumber: threadActor.createValueGrip(aRawObj.columnNumber), 1.4047 + }; 1.4048 + 1.4049 + return true; 1.4050 + }, 1.4051 + 1.4052 + function GenericObject(aObjectActor, aGrip) { 1.4053 + let {obj, threadActor} = aObjectActor; 1.4054 + if (aGrip.preview || aGrip.displayString || threadActor._gripDepth > 1) { 1.4055 + return false; 1.4056 + } 1.4057 + 1.4058 + let i = 0, names = []; 1.4059 + let preview = aGrip.preview = { 1.4060 + kind: "Object", 1.4061 + ownProperties: Object.create(null), 1.4062 + }; 1.4063 + 1.4064 + try { 1.4065 + names = obj.getOwnPropertyNames(); 1.4066 + } catch (ex) { 1.4067 + // Calling getOwnPropertyNames() on some wrapped native prototypes is not 1.4068 + // allowed: "cannot modify properties of a WrappedNative". See bug 952093. 1.4069 + } 1.4070 + 1.4071 + preview.ownPropertiesLength = names.length; 1.4072 + 1.4073 + for (let name of names) { 1.4074 + let desc = aObjectActor._propertyDescriptor(name, true); 1.4075 + if (!desc) { 1.4076 + continue; 1.4077 + } 1.4078 + 1.4079 + preview.ownProperties[name] = desc; 1.4080 + if (++i == OBJECT_PREVIEW_MAX_ITEMS) { 1.4081 + break; 1.4082 + } 1.4083 + } 1.4084 + 1.4085 + if (i < OBJECT_PREVIEW_MAX_ITEMS) { 1.4086 + preview.safeGetterValues = aObjectActor. 1.4087 + _findSafeGetterValues(preview.ownProperties, 1.4088 + OBJECT_PREVIEW_MAX_ITEMS - i); 1.4089 + } 1.4090 + 1.4091 + return true; 1.4092 + }, // GenericObject 1.4093 +]; // DebuggerServer.ObjectActorPreviewers.Object 1.4094 + 1.4095 +/** 1.4096 + * Creates a pause-scoped actor for the specified object. 1.4097 + * @see ObjectActor 1.4098 + */ 1.4099 +function PauseScopedObjectActor() 1.4100 +{ 1.4101 + ObjectActor.apply(this, arguments); 1.4102 +} 1.4103 + 1.4104 +PauseScopedObjectActor.prototype = Object.create(PauseScopedActor.prototype); 1.4105 + 1.4106 +update(PauseScopedObjectActor.prototype, ObjectActor.prototype); 1.4107 + 1.4108 +update(PauseScopedObjectActor.prototype, { 1.4109 + constructor: PauseScopedObjectActor, 1.4110 + actorPrefix: "pausedobj", 1.4111 + 1.4112 + onOwnPropertyNames: 1.4113 + PauseScopedActor.withPaused(ObjectActor.prototype.onOwnPropertyNames), 1.4114 + 1.4115 + onPrototypeAndProperties: 1.4116 + PauseScopedActor.withPaused(ObjectActor.prototype.onPrototypeAndProperties), 1.4117 + 1.4118 + onPrototype: PauseScopedActor.withPaused(ObjectActor.prototype.onPrototype), 1.4119 + onProperty: PauseScopedActor.withPaused(ObjectActor.prototype.onProperty), 1.4120 + onDecompile: PauseScopedActor.withPaused(ObjectActor.prototype.onDecompile), 1.4121 + 1.4122 + onDisplayString: 1.4123 + PauseScopedActor.withPaused(ObjectActor.prototype.onDisplayString), 1.4124 + 1.4125 + onParameterNames: 1.4126 + PauseScopedActor.withPaused(ObjectActor.prototype.onParameterNames), 1.4127 + 1.4128 + /** 1.4129 + * Handle a protocol request to promote a pause-lifetime grip to a 1.4130 + * thread-lifetime grip. 1.4131 + * 1.4132 + * @param aRequest object 1.4133 + * The protocol request object. 1.4134 + */ 1.4135 + onThreadGrip: PauseScopedActor.withPaused(function (aRequest) { 1.4136 + this.threadActor.threadObjectGrip(this); 1.4137 + return {}; 1.4138 + }), 1.4139 + 1.4140 + /** 1.4141 + * Handle a protocol request to release a thread-lifetime grip. 1.4142 + * 1.4143 + * @param aRequest object 1.4144 + * The protocol request object. 1.4145 + */ 1.4146 + onRelease: PauseScopedActor.withPaused(function (aRequest) { 1.4147 + if (this.registeredPool !== this.threadActor.threadLifetimePool) { 1.4148 + return { error: "notReleasable", 1.4149 + message: "Only thread-lifetime actors can be released." }; 1.4150 + } 1.4151 + 1.4152 + this.release(); 1.4153 + return {}; 1.4154 + }), 1.4155 +}); 1.4156 + 1.4157 +update(PauseScopedObjectActor.prototype.requestTypes, { 1.4158 + "threadGrip": PauseScopedObjectActor.prototype.onThreadGrip, 1.4159 +}); 1.4160 + 1.4161 + 1.4162 +/** 1.4163 + * Creates an actor for the specied "very long" string. "Very long" is specified 1.4164 + * at the server's discretion. 1.4165 + * 1.4166 + * @param aString String 1.4167 + * The string. 1.4168 + */ 1.4169 +function LongStringActor(aString) 1.4170 +{ 1.4171 + this.string = aString; 1.4172 + this.stringLength = aString.length; 1.4173 +} 1.4174 + 1.4175 +LongStringActor.prototype = { 1.4176 + 1.4177 + actorPrefix: "longString", 1.4178 + 1.4179 + disconnect: function () { 1.4180 + // Because longStringActors is not a weak map, we won't automatically leave 1.4181 + // it so we need to manually leave on disconnect so that we don't leak 1.4182 + // memory. 1.4183 + if (this.registeredPool && this.registeredPool.longStringActors) { 1.4184 + delete this.registeredPool.longStringActors[this.actorID]; 1.4185 + } 1.4186 + }, 1.4187 + 1.4188 + /** 1.4189 + * Returns a grip for this actor for returning in a protocol message. 1.4190 + */ 1.4191 + grip: function () { 1.4192 + return { 1.4193 + "type": "longString", 1.4194 + "initial": this.string.substring( 1.4195 + 0, DebuggerServer.LONG_STRING_INITIAL_LENGTH), 1.4196 + "length": this.stringLength, 1.4197 + "actor": this.actorID 1.4198 + }; 1.4199 + }, 1.4200 + 1.4201 + /** 1.4202 + * Handle a request to extract part of this actor's string. 1.4203 + * 1.4204 + * @param aRequest object 1.4205 + * The protocol request object. 1.4206 + */ 1.4207 + onSubstring: function (aRequest) { 1.4208 + return { 1.4209 + "from": this.actorID, 1.4210 + "substring": this.string.substring(aRequest.start, aRequest.end) 1.4211 + }; 1.4212 + }, 1.4213 + 1.4214 + /** 1.4215 + * Handle a request to release this LongStringActor instance. 1.4216 + */ 1.4217 + onRelease: function () { 1.4218 + // TODO: also check if registeredPool === threadActor.threadLifetimePool 1.4219 + // when the web console moves aray from manually releasing pause-scoped 1.4220 + // actors. 1.4221 + if (this.registeredPool.longStringActors) { 1.4222 + delete this.registeredPool.longStringActors[this.actorID]; 1.4223 + } 1.4224 + this.registeredPool.removeActor(this); 1.4225 + return {}; 1.4226 + }, 1.4227 +}; 1.4228 + 1.4229 +LongStringActor.prototype.requestTypes = { 1.4230 + "substring": LongStringActor.prototype.onSubstring, 1.4231 + "release": LongStringActor.prototype.onRelease 1.4232 +}; 1.4233 + 1.4234 + 1.4235 +/** 1.4236 + * Creates an actor for the specified stack frame. 1.4237 + * 1.4238 + * @param aFrame Debugger.Frame 1.4239 + * The debuggee frame. 1.4240 + * @param aThreadActor ThreadActor 1.4241 + * The parent thread actor for this frame. 1.4242 + */ 1.4243 +function FrameActor(aFrame, aThreadActor) 1.4244 +{ 1.4245 + this.frame = aFrame; 1.4246 + this.threadActor = aThreadActor; 1.4247 +} 1.4248 + 1.4249 +FrameActor.prototype = { 1.4250 + actorPrefix: "frame", 1.4251 + 1.4252 + /** 1.4253 + * A pool that contains frame-lifetime objects, like the environment. 1.4254 + */ 1.4255 + _frameLifetimePool: null, 1.4256 + get frameLifetimePool() { 1.4257 + if (!this._frameLifetimePool) { 1.4258 + this._frameLifetimePool = new ActorPool(this.conn); 1.4259 + this.conn.addActorPool(this._frameLifetimePool); 1.4260 + } 1.4261 + return this._frameLifetimePool; 1.4262 + }, 1.4263 + 1.4264 + /** 1.4265 + * Finalization handler that is called when the actor is being evicted from 1.4266 + * the pool. 1.4267 + */ 1.4268 + disconnect: function () { 1.4269 + this.conn.removeActorPool(this._frameLifetimePool); 1.4270 + this._frameLifetimePool = null; 1.4271 + }, 1.4272 + 1.4273 + /** 1.4274 + * Returns a frame form for use in a protocol message. 1.4275 + */ 1.4276 + form: function () { 1.4277 + let form = { actor: this.actorID, 1.4278 + type: this.frame.type }; 1.4279 + if (this.frame.type === "call") { 1.4280 + form.callee = this.threadActor.createValueGrip(this.frame.callee); 1.4281 + } 1.4282 + 1.4283 + if (this.frame.environment) { 1.4284 + let envActor = this.threadActor 1.4285 + .createEnvironmentActor(this.frame.environment, 1.4286 + this.frameLifetimePool); 1.4287 + form.environment = envActor.form(); 1.4288 + } 1.4289 + form.this = this.threadActor.createValueGrip(this.frame.this); 1.4290 + form.arguments = this._args(); 1.4291 + if (this.frame.script) { 1.4292 + form.where = getFrameLocation(this.frame); 1.4293 + } 1.4294 + 1.4295 + if (!this.frame.older) { 1.4296 + form.oldest = true; 1.4297 + } 1.4298 + 1.4299 + return form; 1.4300 + }, 1.4301 + 1.4302 + _args: function () { 1.4303 + if (!this.frame.arguments) { 1.4304 + return []; 1.4305 + } 1.4306 + 1.4307 + return [this.threadActor.createValueGrip(arg) 1.4308 + for each (arg in this.frame.arguments)]; 1.4309 + }, 1.4310 + 1.4311 + /** 1.4312 + * Handle a protocol request to pop this frame from the stack. 1.4313 + * 1.4314 + * @param aRequest object 1.4315 + * The protocol request object. 1.4316 + */ 1.4317 + onPop: function (aRequest) { 1.4318 + // TODO: remove this when Debugger.Frame.prototype.pop is implemented 1.4319 + if (typeof this.frame.pop != "function") { 1.4320 + return { error: "notImplemented", 1.4321 + message: "Popping frames is not yet implemented." }; 1.4322 + } 1.4323 + 1.4324 + while (this.frame != this.threadActor.dbg.getNewestFrame()) { 1.4325 + this.threadActor.dbg.getNewestFrame().pop(); 1.4326 + } 1.4327 + this.frame.pop(aRequest.completionValue); 1.4328 + 1.4329 + // TODO: return the watches property when frame pop watch actors are 1.4330 + // implemented. 1.4331 + return { from: this.actorID }; 1.4332 + } 1.4333 +}; 1.4334 + 1.4335 +FrameActor.prototype.requestTypes = { 1.4336 + "pop": FrameActor.prototype.onPop, 1.4337 +}; 1.4338 + 1.4339 + 1.4340 +/** 1.4341 + * Creates a BreakpointActor. BreakpointActors exist for the lifetime of their 1.4342 + * containing thread and are responsible for deleting breakpoints, handling 1.4343 + * breakpoint hits and associating breakpoints with scripts. 1.4344 + * 1.4345 + * @param ThreadActor aThreadActor 1.4346 + * The parent thread actor that contains this breakpoint. 1.4347 + * @param object aLocation 1.4348 + * The location of the breakpoint as specified in the protocol. 1.4349 + */ 1.4350 +function BreakpointActor(aThreadActor, { url, line, column, condition }) 1.4351 +{ 1.4352 + this.scripts = []; 1.4353 + this.threadActor = aThreadActor; 1.4354 + this.location = { url: url, line: line, column: column }; 1.4355 + this.condition = condition; 1.4356 +} 1.4357 + 1.4358 +BreakpointActor.prototype = { 1.4359 + actorPrefix: "breakpoint", 1.4360 + condition: null, 1.4361 + 1.4362 + /** 1.4363 + * Called when this same breakpoint is added to another Debugger.Script 1.4364 + * instance, in the case of a page reload. 1.4365 + * 1.4366 + * @param aScript Debugger.Script 1.4367 + * The new source script on which the breakpoint has been set. 1.4368 + * @param ThreadActor aThreadActor 1.4369 + * The parent thread actor that contains this breakpoint. 1.4370 + */ 1.4371 + addScript: function (aScript, aThreadActor) { 1.4372 + this.threadActor = aThreadActor; 1.4373 + this.scripts.push(aScript); 1.4374 + }, 1.4375 + 1.4376 + /** 1.4377 + * Remove the breakpoints from associated scripts and clear the script cache. 1.4378 + */ 1.4379 + removeScripts: function () { 1.4380 + for (let script of this.scripts) { 1.4381 + script.clearBreakpoint(this); 1.4382 + } 1.4383 + this.scripts = []; 1.4384 + }, 1.4385 + 1.4386 + /** 1.4387 + * Check if this breakpoint has a condition that doesn't error and 1.4388 + * evaluates to true in aFrame 1.4389 + * 1.4390 + * @param aFrame Debugger.Frame 1.4391 + * The frame to evaluate the condition in 1.4392 + */ 1.4393 + isValidCondition: function(aFrame) { 1.4394 + if(!this.condition) { 1.4395 + return true; 1.4396 + } 1.4397 + var res = aFrame.eval(this.condition); 1.4398 + return res.return; 1.4399 + }, 1.4400 + 1.4401 + /** 1.4402 + * A function that the engine calls when a breakpoint has been hit. 1.4403 + * 1.4404 + * @param aFrame Debugger.Frame 1.4405 + * The stack frame that contained the breakpoint. 1.4406 + */ 1.4407 + hit: function (aFrame) { 1.4408 + // Don't pause if we are currently stepping (in or over) or the frame is 1.4409 + // black-boxed. 1.4410 + let { url } = this.threadActor.synchronize( 1.4411 + this.threadActor.sources.getOriginalLocation({ 1.4412 + url: this.location.url, 1.4413 + line: this.location.line, 1.4414 + column: this.location.column 1.4415 + })); 1.4416 + 1.4417 + if (this.threadActor.sources.isBlackBoxed(url) 1.4418 + || aFrame.onStep 1.4419 + || !this.isValidCondition(aFrame)) { 1.4420 + return undefined; 1.4421 + } 1.4422 + 1.4423 + let reason = {}; 1.4424 + if (this.threadActor._hiddenBreakpoints.has(this.actorID)) { 1.4425 + reason.type = "pauseOnDOMEvents"; 1.4426 + } else { 1.4427 + reason.type = "breakpoint"; 1.4428 + // TODO: add the rest of the breakpoints on that line (bug 676602). 1.4429 + reason.actors = [ this.actorID ]; 1.4430 + } 1.4431 + return this.threadActor._pauseAndRespond(aFrame, reason); 1.4432 + }, 1.4433 + 1.4434 + /** 1.4435 + * Handle a protocol request to remove this breakpoint. 1.4436 + * 1.4437 + * @param aRequest object 1.4438 + * The protocol request object. 1.4439 + */ 1.4440 + onDelete: function (aRequest) { 1.4441 + // Remove from the breakpoint store. 1.4442 + this.threadActor.breakpointStore.removeBreakpoint(this.location); 1.4443 + this.threadActor.threadLifetimePool.removeActor(this); 1.4444 + // Remove the actual breakpoint from the associated scripts. 1.4445 + this.removeScripts(); 1.4446 + return { from: this.actorID }; 1.4447 + } 1.4448 +}; 1.4449 + 1.4450 +BreakpointActor.prototype.requestTypes = { 1.4451 + "delete": BreakpointActor.prototype.onDelete 1.4452 +}; 1.4453 + 1.4454 + 1.4455 +/** 1.4456 + * Creates an EnvironmentActor. EnvironmentActors are responsible for listing 1.4457 + * the bindings introduced by a lexical environment and assigning new values to 1.4458 + * those identifier bindings. 1.4459 + * 1.4460 + * @param Debugger.Environment aEnvironment 1.4461 + * The lexical environment that will be used to create the actor. 1.4462 + * @param ThreadActor aThreadActor 1.4463 + * The parent thread actor that contains this environment. 1.4464 + */ 1.4465 +function EnvironmentActor(aEnvironment, aThreadActor) 1.4466 +{ 1.4467 + this.obj = aEnvironment; 1.4468 + this.threadActor = aThreadActor; 1.4469 +} 1.4470 + 1.4471 +EnvironmentActor.prototype = { 1.4472 + actorPrefix: "environment", 1.4473 + 1.4474 + /** 1.4475 + * Return an environment form for use in a protocol message. 1.4476 + */ 1.4477 + form: function () { 1.4478 + let form = { actor: this.actorID }; 1.4479 + 1.4480 + // What is this environment's type? 1.4481 + if (this.obj.type == "declarative") { 1.4482 + form.type = this.obj.callee ? "function" : "block"; 1.4483 + } else { 1.4484 + form.type = this.obj.type; 1.4485 + } 1.4486 + 1.4487 + // Does this environment have a parent? 1.4488 + if (this.obj.parent) { 1.4489 + form.parent = (this.threadActor 1.4490 + .createEnvironmentActor(this.obj.parent, 1.4491 + this.registeredPool) 1.4492 + .form()); 1.4493 + } 1.4494 + 1.4495 + // Does this environment reflect the properties of an object as variables? 1.4496 + if (this.obj.type == "object" || this.obj.type == "with") { 1.4497 + form.object = this.threadActor.createValueGrip(this.obj.object); 1.4498 + } 1.4499 + 1.4500 + // Is this the environment created for a function call? 1.4501 + if (this.obj.callee) { 1.4502 + form.function = this.threadActor.createValueGrip(this.obj.callee); 1.4503 + } 1.4504 + 1.4505 + // Shall we list this environment's bindings? 1.4506 + if (this.obj.type == "declarative") { 1.4507 + form.bindings = this._bindings(); 1.4508 + } 1.4509 + 1.4510 + return form; 1.4511 + }, 1.4512 + 1.4513 + /** 1.4514 + * Return the identifier bindings object as required by the remote protocol 1.4515 + * specification. 1.4516 + */ 1.4517 + _bindings: function () { 1.4518 + let bindings = { arguments: [], variables: {} }; 1.4519 + 1.4520 + // TODO: this part should be removed in favor of the commented-out part 1.4521 + // below when getVariableDescriptor lands (bug 725815). 1.4522 + if (typeof this.obj.getVariable != "function") { 1.4523 + //if (typeof this.obj.getVariableDescriptor != "function") { 1.4524 + return bindings; 1.4525 + } 1.4526 + 1.4527 + let parameterNames; 1.4528 + if (this.obj.callee) { 1.4529 + parameterNames = this.obj.callee.parameterNames; 1.4530 + } 1.4531 + for each (let name in parameterNames) { 1.4532 + let arg = {}; 1.4533 + 1.4534 + let value = this.obj.getVariable(name); 1.4535 + // The slot is optimized out. 1.4536 + // FIXME: Need actual UI, bug 941287. 1.4537 + if (value && value.optimizedOut) { 1.4538 + continue; 1.4539 + } 1.4540 + 1.4541 + // TODO: this part should be removed in favor of the commented-out part 1.4542 + // below when getVariableDescriptor lands (bug 725815). 1.4543 + let desc = { 1.4544 + value: value, 1.4545 + configurable: false, 1.4546 + writable: true, 1.4547 + enumerable: true 1.4548 + }; 1.4549 + 1.4550 + // let desc = this.obj.getVariableDescriptor(name); 1.4551 + let descForm = { 1.4552 + enumerable: true, 1.4553 + configurable: desc.configurable 1.4554 + }; 1.4555 + if ("value" in desc) { 1.4556 + descForm.value = this.threadActor.createValueGrip(desc.value); 1.4557 + descForm.writable = desc.writable; 1.4558 + } else { 1.4559 + descForm.get = this.threadActor.createValueGrip(desc.get); 1.4560 + descForm.set = this.threadActor.createValueGrip(desc.set); 1.4561 + } 1.4562 + arg[name] = descForm; 1.4563 + bindings.arguments.push(arg); 1.4564 + } 1.4565 + 1.4566 + for each (let name in this.obj.names()) { 1.4567 + if (bindings.arguments.some(function exists(element) { 1.4568 + return !!element[name]; 1.4569 + })) { 1.4570 + continue; 1.4571 + } 1.4572 + 1.4573 + let value = this.obj.getVariable(name); 1.4574 + // The slot is optimized out or arguments on a dead scope. 1.4575 + // FIXME: Need actual UI, bug 941287. 1.4576 + if (value && (value.optimizedOut || value.missingArguments)) { 1.4577 + continue; 1.4578 + } 1.4579 + 1.4580 + // TODO: this part should be removed in favor of the commented-out part 1.4581 + // below when getVariableDescriptor lands. 1.4582 + let desc = { 1.4583 + value: value, 1.4584 + configurable: false, 1.4585 + writable: true, 1.4586 + enumerable: true 1.4587 + }; 1.4588 + 1.4589 + //let desc = this.obj.getVariableDescriptor(name); 1.4590 + let descForm = { 1.4591 + enumerable: true, 1.4592 + configurable: desc.configurable 1.4593 + }; 1.4594 + if ("value" in desc) { 1.4595 + descForm.value = this.threadActor.createValueGrip(desc.value); 1.4596 + descForm.writable = desc.writable; 1.4597 + } else { 1.4598 + descForm.get = this.threadActor.createValueGrip(desc.get); 1.4599 + descForm.set = this.threadActor.createValueGrip(desc.set); 1.4600 + } 1.4601 + bindings.variables[name] = descForm; 1.4602 + } 1.4603 + 1.4604 + return bindings; 1.4605 + }, 1.4606 + 1.4607 + /** 1.4608 + * Handle a protocol request to change the value of a variable bound in this 1.4609 + * lexical environment. 1.4610 + * 1.4611 + * @param aRequest object 1.4612 + * The protocol request object. 1.4613 + */ 1.4614 + onAssign: function (aRequest) { 1.4615 + // TODO: enable the commented-out part when getVariableDescriptor lands 1.4616 + // (bug 725815). 1.4617 + /*let desc = this.obj.getVariableDescriptor(aRequest.name); 1.4618 + 1.4619 + if (!desc.writable) { 1.4620 + return { error: "immutableBinding", 1.4621 + message: "Changing the value of an immutable binding is not " + 1.4622 + "allowed" }; 1.4623 + }*/ 1.4624 + 1.4625 + try { 1.4626 + this.obj.setVariable(aRequest.name, aRequest.value); 1.4627 + } catch (e if e instanceof Debugger.DebuggeeWouldRun) { 1.4628 + return { error: "threadWouldRun", 1.4629 + cause: e.cause ? e.cause : "setter", 1.4630 + message: "Assigning a value would cause the debuggee to run" }; 1.4631 + } 1.4632 + return { from: this.actorID }; 1.4633 + }, 1.4634 + 1.4635 + /** 1.4636 + * Handle a protocol request to fully enumerate the bindings introduced by the 1.4637 + * lexical environment. 1.4638 + * 1.4639 + * @param aRequest object 1.4640 + * The protocol request object. 1.4641 + */ 1.4642 + onBindings: function (aRequest) { 1.4643 + return { from: this.actorID, 1.4644 + bindings: this._bindings() }; 1.4645 + } 1.4646 +}; 1.4647 + 1.4648 +EnvironmentActor.prototype.requestTypes = { 1.4649 + "assign": EnvironmentActor.prototype.onAssign, 1.4650 + "bindings": EnvironmentActor.prototype.onBindings 1.4651 +}; 1.4652 + 1.4653 +/** 1.4654 + * Override the toString method in order to get more meaningful script output 1.4655 + * for debugging the debugger. 1.4656 + */ 1.4657 +Debugger.Script.prototype.toString = function() { 1.4658 + let output = ""; 1.4659 + if (this.url) { 1.4660 + output += this.url; 1.4661 + } 1.4662 + if (typeof this.startLine != "undefined") { 1.4663 + output += ":" + this.startLine; 1.4664 + if (this.lineCount && this.lineCount > 1) { 1.4665 + output += "-" + (this.startLine + this.lineCount - 1); 1.4666 + } 1.4667 + } 1.4668 + if (this.strictMode) { 1.4669 + output += ":strict"; 1.4670 + } 1.4671 + return output; 1.4672 +}; 1.4673 + 1.4674 +/** 1.4675 + * Helper property for quickly getting to the line number a stack frame is 1.4676 + * currently paused at. 1.4677 + */ 1.4678 +Object.defineProperty(Debugger.Frame.prototype, "line", { 1.4679 + configurable: true, 1.4680 + get: function() { 1.4681 + if (this.script) { 1.4682 + return this.script.getOffsetLine(this.offset); 1.4683 + } else { 1.4684 + return null; 1.4685 + } 1.4686 + } 1.4687 +}); 1.4688 + 1.4689 + 1.4690 +/** 1.4691 + * Creates an actor for handling chrome debugging. ChromeDebuggerActor is a 1.4692 + * thin wrapper over ThreadActor, slightly changing some of its behavior. 1.4693 + * 1.4694 + * @param aConnection object 1.4695 + * The DebuggerServerConnection with which this ChromeDebuggerActor 1.4696 + * is associated. (Currently unused, but required to make this 1.4697 + * constructor usable with addGlobalActor.) 1.4698 + * 1.4699 + * @param aHooks object 1.4700 + * An object with preNest and postNest methods for calling when entering 1.4701 + * and exiting a nested event loop. 1.4702 + */ 1.4703 +function ChromeDebuggerActor(aConnection, aHooks) 1.4704 +{ 1.4705 + ThreadActor.call(this, aHooks); 1.4706 +} 1.4707 + 1.4708 +ChromeDebuggerActor.prototype = Object.create(ThreadActor.prototype); 1.4709 + 1.4710 +update(ChromeDebuggerActor.prototype, { 1.4711 + constructor: ChromeDebuggerActor, 1.4712 + 1.4713 + // A constant prefix that will be used to form the actor ID by the server. 1.4714 + actorPrefix: "chromeDebugger", 1.4715 + 1.4716 + /** 1.4717 + * Override the eligibility check for scripts and sources to make sure every 1.4718 + * script and source with a URL is stored when debugging chrome. 1.4719 + */ 1.4720 + _allowSource: function(aSourceURL) !!aSourceURL, 1.4721 + 1.4722 + /** 1.4723 + * An object that will be used by ThreadActors to tailor their behavior 1.4724 + * depending on the debugging context being required (chrome or content). 1.4725 + * The methods that this object provides must be bound to the ThreadActor 1.4726 + * before use. 1.4727 + */ 1.4728 + globalManager: { 1.4729 + findGlobals: function () { 1.4730 + // Add every global known to the debugger as debuggee. 1.4731 + this.dbg.addAllGlobalsAsDebuggees(); 1.4732 + }, 1.4733 + 1.4734 + /** 1.4735 + * A function that the engine calls when a new global object has been 1.4736 + * created. 1.4737 + * 1.4738 + * @param aGlobal Debugger.Object 1.4739 + * The new global object that was created. 1.4740 + */ 1.4741 + onNewGlobal: function (aGlobal) { 1.4742 + this.addDebuggee(aGlobal); 1.4743 + // Notify the client. 1.4744 + this.conn.send({ 1.4745 + from: this.actorID, 1.4746 + type: "newGlobal", 1.4747 + // TODO: after bug 801084 lands see if we need to JSONify this. 1.4748 + hostAnnotations: aGlobal.hostAnnotations 1.4749 + }); 1.4750 + } 1.4751 + } 1.4752 +}); 1.4753 + 1.4754 +/** 1.4755 + * Creates an actor for handling add-on debugging. AddonThreadActor is 1.4756 + * a thin wrapper over ThreadActor. 1.4757 + * 1.4758 + * @param aConnection object 1.4759 + * The DebuggerServerConnection with which this AddonThreadActor 1.4760 + * is associated. (Currently unused, but required to make this 1.4761 + * constructor usable with addGlobalActor.) 1.4762 + * 1.4763 + * @param aHooks object 1.4764 + * An object with preNest and postNest methods for calling 1.4765 + * when entering and exiting a nested event loops. 1.4766 + * 1.4767 + * @param aAddonID string 1.4768 + * ID of the add-on this actor will debug. It will be used to 1.4769 + * filter out globals marked for debugging. 1.4770 + */ 1.4771 + 1.4772 +function AddonThreadActor(aConnect, aHooks, aAddonID) { 1.4773 + this.addonID = aAddonID; 1.4774 + ThreadActor.call(this, aHooks); 1.4775 +} 1.4776 + 1.4777 +AddonThreadActor.prototype = Object.create(ThreadActor.prototype); 1.4778 + 1.4779 +update(AddonThreadActor.prototype, { 1.4780 + constructor: AddonThreadActor, 1.4781 + 1.4782 + // A constant prefix that will be used to form the actor ID by the server. 1.4783 + actorPrefix: "addonThread", 1.4784 + 1.4785 + onAttach: function(aRequest) { 1.4786 + if (!this.attached) { 1.4787 + Services.obs.addObserver(this, "chrome-document-global-created", false); 1.4788 + Services.obs.addObserver(this, "content-document-global-created", false); 1.4789 + } 1.4790 + return ThreadActor.prototype.onAttach.call(this, aRequest); 1.4791 + }, 1.4792 + 1.4793 + disconnect: function() { 1.4794 + if (this.attached) { 1.4795 + Services.obs.removeObserver(this, "content-document-global-created"); 1.4796 + Services.obs.removeObserver(this, "chrome-document-global-created"); 1.4797 + } 1.4798 + return ThreadActor.prototype.disconnect.call(this); 1.4799 + }, 1.4800 + 1.4801 + /** 1.4802 + * Called when a new DOM document global is created. Check if the DOM was 1.4803 + * loaded from an add-on and if so make the window a debuggee. 1.4804 + */ 1.4805 + observe: function(aSubject, aTopic, aData) { 1.4806 + let id = {}; 1.4807 + if (mapURIToAddonID(aSubject.location, id) && id.value === this.addonID) { 1.4808 + this.dbg.addDebuggee(aSubject.defaultView); 1.4809 + } 1.4810 + }, 1.4811 + 1.4812 + /** 1.4813 + * Override the eligibility check for scripts and sources to make 1.4814 + * sure every script and source with a URL is stored when debugging 1.4815 + * add-ons. 1.4816 + */ 1.4817 + _allowSource: function(aSourceURL) { 1.4818 + // Hide eval scripts 1.4819 + if (!aSourceURL) { 1.4820 + return false; 1.4821 + } 1.4822 + 1.4823 + // XPIProvider.jsm evals some code in every add-on's bootstrap.js. Hide it 1.4824 + if (aSourceURL == "resource://gre/modules/addons/XPIProvider.jsm") { 1.4825 + return false; 1.4826 + } 1.4827 + 1.4828 + return true; 1.4829 + }, 1.4830 + 1.4831 + /** 1.4832 + * An object that will be used by ThreadActors to tailor their 1.4833 + * behaviour depending on the debugging context being required (chrome, 1.4834 + * addon or content). The methods that this object provides must 1.4835 + * be bound to the ThreadActor before use. 1.4836 + */ 1.4837 + globalManager: { 1.4838 + findGlobals: function ADA_findGlobals() { 1.4839 + for (let global of this.dbg.findAllGlobals()) { 1.4840 + if (this._checkGlobal(global)) { 1.4841 + this.dbg.addDebuggee(global); 1.4842 + } 1.4843 + } 1.4844 + }, 1.4845 + 1.4846 + /** 1.4847 + * A function that the engine calls when a new global object 1.4848 + * has been created. 1.4849 + * 1.4850 + * @param aGlobal Debugger.Object 1.4851 + * The new global object that was created. 1.4852 + */ 1.4853 + onNewGlobal: function ADA_onNewGlobal(aGlobal) { 1.4854 + if (this._checkGlobal(aGlobal)) { 1.4855 + this.addDebuggee(aGlobal); 1.4856 + // Notify the client. 1.4857 + this.conn.send({ 1.4858 + from: this.actorID, 1.4859 + type: "newGlobal", 1.4860 + // TODO: after bug 801084 lands see if we need to JSONify this. 1.4861 + hostAnnotations: aGlobal.hostAnnotations 1.4862 + }); 1.4863 + } 1.4864 + } 1.4865 + }, 1.4866 + 1.4867 + /** 1.4868 + * Checks if the provided global belongs to the debugged add-on. 1.4869 + * 1.4870 + * @param aGlobal Debugger.Object 1.4871 + */ 1.4872 + _checkGlobal: function ADA_checkGlobal(aGlobal) { 1.4873 + let obj = null; 1.4874 + try { 1.4875 + obj = aGlobal.unsafeDereference(); 1.4876 + } 1.4877 + catch (e) { 1.4878 + // Because of bug 991399 we sometimes get bad objects here. If we can't 1.4879 + // dereference them then they won't be useful to us 1.4880 + return false; 1.4881 + } 1.4882 + 1.4883 + try { 1.4884 + // This will fail for non-Sandbox objects, hence the try-catch block. 1.4885 + let metadata = Cu.getSandboxMetadata(obj); 1.4886 + if (metadata) { 1.4887 + return metadata.addonID === this.addonID; 1.4888 + } 1.4889 + } catch (e) { 1.4890 + } 1.4891 + 1.4892 + if (obj instanceof Ci.nsIDOMWindow) { 1.4893 + let id = {}; 1.4894 + if (mapURIToAddonID(obj.document.documentURIObject, id)) { 1.4895 + return id.value === this.addonID; 1.4896 + } 1.4897 + return false; 1.4898 + } 1.4899 + 1.4900 + // Check the global for a __URI__ property and then try to map that to an 1.4901 + // add-on 1.4902 + let uridescriptor = aGlobal.getOwnPropertyDescriptor("__URI__"); 1.4903 + if (uridescriptor && "value" in uridescriptor && uridescriptor.value) { 1.4904 + let uri; 1.4905 + try { 1.4906 + uri = Services.io.newURI(uridescriptor.value, null, null); 1.4907 + } 1.4908 + catch (e) { 1.4909 + DevToolsUtils.reportException("AddonThreadActor.prototype._checkGlobal", 1.4910 + new Error("Invalid URI: " + uridescriptor.value)); 1.4911 + return false; 1.4912 + } 1.4913 + 1.4914 + let id = {}; 1.4915 + if (mapURIToAddonID(uri, id)) { 1.4916 + return id.value === this.addonID; 1.4917 + } 1.4918 + } 1.4919 + 1.4920 + return false; 1.4921 + } 1.4922 +}); 1.4923 + 1.4924 +AddonThreadActor.prototype.requestTypes = Object.create(ThreadActor.prototype.requestTypes); 1.4925 +update(AddonThreadActor.prototype.requestTypes, { 1.4926 + "attach": AddonThreadActor.prototype.onAttach 1.4927 +}); 1.4928 + 1.4929 +/** 1.4930 + * Manages the sources for a thread. Handles source maps, locations in the 1.4931 + * sources, etc for ThreadActors. 1.4932 + */ 1.4933 +function ThreadSources(aThreadActor, aUseSourceMaps, aAllowPredicate, 1.4934 + aOnNewSource) { 1.4935 + this._thread = aThreadActor; 1.4936 + this._useSourceMaps = aUseSourceMaps; 1.4937 + this._allow = aAllowPredicate; 1.4938 + this._onNewSource = aOnNewSource; 1.4939 + 1.4940 + // generated source url --> promise of SourceMapConsumer 1.4941 + this._sourceMapsByGeneratedSource = Object.create(null); 1.4942 + // original source url --> promise of SourceMapConsumer 1.4943 + this._sourceMapsByOriginalSource = Object.create(null); 1.4944 + // source url --> SourceActor 1.4945 + this._sourceActors = Object.create(null); 1.4946 + // original url --> generated url 1.4947 + this._generatedUrlsByOriginalUrl = Object.create(null); 1.4948 +} 1.4949 + 1.4950 +/** 1.4951 + * Must be a class property because it needs to persist across reloads, same as 1.4952 + * the breakpoint store. 1.4953 + */ 1.4954 +ThreadSources._blackBoxedSources = new Set(["self-hosted"]); 1.4955 +ThreadSources._prettyPrintedSources = new Map(); 1.4956 + 1.4957 +ThreadSources.prototype = { 1.4958 + /** 1.4959 + * Return the source actor representing |url|, creating one if none 1.4960 + * exists already. Returns null if |url| is not allowed by the 'allow' 1.4961 + * predicate. 1.4962 + * 1.4963 + * Right now this takes a URL, but in the future it should 1.4964 + * take a Debugger.Source. See bug 637572. 1.4965 + * 1.4966 + * @param String url 1.4967 + * The source URL. 1.4968 + * @param optional SourceMapConsumer sourceMap 1.4969 + * The source map that introduced this source, if any. 1.4970 + * @param optional String generatedSource 1.4971 + * The generated source url that introduced this source via source map, 1.4972 + * if any. 1.4973 + * @param optional String text 1.4974 + * The text content of the source, if immediately available. 1.4975 + * @param optional String contentType 1.4976 + * The content type of the source, if immediately available. 1.4977 + * @returns a SourceActor representing the source at aURL or null. 1.4978 + */ 1.4979 + source: function ({ url, sourceMap, generatedSource, text, contentType }) { 1.4980 + if (!this._allow(url)) { 1.4981 + return null; 1.4982 + } 1.4983 + 1.4984 + if (url in this._sourceActors) { 1.4985 + return this._sourceActors[url]; 1.4986 + } 1.4987 + 1.4988 + let actor = new SourceActor({ 1.4989 + url: url, 1.4990 + thread: this._thread, 1.4991 + sourceMap: sourceMap, 1.4992 + generatedSource: generatedSource, 1.4993 + text: text, 1.4994 + contentType: contentType 1.4995 + }); 1.4996 + this._thread.threadLifetimePool.addActor(actor); 1.4997 + this._sourceActors[url] = actor; 1.4998 + try { 1.4999 + this._onNewSource(actor); 1.5000 + } catch (e) { 1.5001 + reportError(e); 1.5002 + } 1.5003 + return actor; 1.5004 + }, 1.5005 + 1.5006 + /** 1.5007 + * Only to be used when we aren't source mapping. 1.5008 + */ 1.5009 + _sourceForScript: function (aScript) { 1.5010 + const spec = { 1.5011 + url: aScript.url 1.5012 + }; 1.5013 + 1.5014 + // XXX bug 915433: We can't rely on Debugger.Source.prototype.text if the 1.5015 + // source is an HTML-embedded <script> tag. Since we don't have an API 1.5016 + // implemented to detect whether this is the case, we need to be 1.5017 + // conservative and only use Debugger.Source.prototype.text if we get a 1.5018 + // normal .js file. 1.5019 + if (aScript.url) { 1.5020 + try { 1.5021 + const url = Services.io.newURI(aScript.url, null, null) 1.5022 + .QueryInterface(Ci.nsIURL); 1.5023 + if (url.fileExtension === "js") { 1.5024 + spec.contentType = "text/javascript"; 1.5025 + spec.text = aScript.source.text; 1.5026 + } 1.5027 + } catch(ex) { 1.5028 + // Not a valid URI. 1.5029 + } 1.5030 + } 1.5031 + 1.5032 + return this.source(spec); 1.5033 + }, 1.5034 + 1.5035 + /** 1.5036 + * Return a promise of an array of source actors representing all the 1.5037 + * sources of |aScript|. 1.5038 + * 1.5039 + * If source map handling is enabled and |aScript| has a source map, then 1.5040 + * use it to find all of |aScript|'s *original* sources; return a promise 1.5041 + * of an array of source actors for those. 1.5042 + */ 1.5043 + sourcesForScript: function (aScript) { 1.5044 + if (!this._useSourceMaps || !aScript.sourceMapURL) { 1.5045 + return resolve([this._sourceForScript(aScript)].filter(isNotNull)); 1.5046 + } 1.5047 + 1.5048 + return this.sourceMap(aScript) 1.5049 + .then((aSourceMap) => { 1.5050 + return [ 1.5051 + this.source({ url: s, 1.5052 + sourceMap: aSourceMap, 1.5053 + generatedSource: aScript.url }) 1.5054 + for (s of aSourceMap.sources) 1.5055 + ]; 1.5056 + }) 1.5057 + .then(null, (e) => { 1.5058 + reportError(e); 1.5059 + delete this._sourceMapsByGeneratedSource[aScript.url]; 1.5060 + return [this._sourceForScript(aScript)]; 1.5061 + }) 1.5062 + .then(ss => ss.filter(isNotNull)); 1.5063 + }, 1.5064 + 1.5065 + /** 1.5066 + * Return a promise of a SourceMapConsumer for the source map for 1.5067 + * |aScript|; if we already have such a promise extant, return that. 1.5068 + * |aScript| must have a non-null sourceMapURL. 1.5069 + */ 1.5070 + sourceMap: function (aScript) { 1.5071 + dbg_assert(aScript.sourceMapURL, "Script should have a sourceMapURL"); 1.5072 + let sourceMapURL = this._normalize(aScript.sourceMapURL, aScript.url); 1.5073 + let map = this._fetchSourceMap(sourceMapURL, aScript.url) 1.5074 + .then(aSourceMap => this.saveSourceMap(aSourceMap, aScript.url)); 1.5075 + this._sourceMapsByGeneratedSource[aScript.url] = map; 1.5076 + return map; 1.5077 + }, 1.5078 + 1.5079 + /** 1.5080 + * Save the given source map so that we can use it to query source locations 1.5081 + * down the line. 1.5082 + */ 1.5083 + saveSourceMap: function (aSourceMap, aGeneratedSource) { 1.5084 + if (!aSourceMap) { 1.5085 + delete this._sourceMapsByGeneratedSource[aGeneratedSource]; 1.5086 + return null; 1.5087 + } 1.5088 + this._sourceMapsByGeneratedSource[aGeneratedSource] = resolve(aSourceMap); 1.5089 + for (let s of aSourceMap.sources) { 1.5090 + this._generatedUrlsByOriginalUrl[s] = aGeneratedSource; 1.5091 + this._sourceMapsByOriginalSource[s] = resolve(aSourceMap); 1.5092 + } 1.5093 + return aSourceMap; 1.5094 + }, 1.5095 + 1.5096 + /** 1.5097 + * Return a promise of a SourceMapConsumer for the source map located at 1.5098 + * |aAbsSourceMapURL|, which must be absolute. If there is already such a 1.5099 + * promise extant, return it. 1.5100 + * 1.5101 + * @param string aAbsSourceMapURL 1.5102 + * The source map URL, in absolute form, not relative. 1.5103 + * @param string aScriptURL 1.5104 + * When the source map URL is a data URI, there is no sourceRoot on the 1.5105 + * source map, and the source map's sources are relative, we resolve 1.5106 + * them from aScriptURL. 1.5107 + */ 1.5108 + _fetchSourceMap: function (aAbsSourceMapURL, aScriptURL) { 1.5109 + return fetch(aAbsSourceMapURL, { loadFromCache: false }) 1.5110 + .then(({ content }) => { 1.5111 + let map = new SourceMapConsumer(content); 1.5112 + this._setSourceMapRoot(map, aAbsSourceMapURL, aScriptURL); 1.5113 + return map; 1.5114 + }); 1.5115 + }, 1.5116 + 1.5117 + /** 1.5118 + * Sets the source map's sourceRoot to be relative to the source map url. 1.5119 + */ 1.5120 + _setSourceMapRoot: function (aSourceMap, aAbsSourceMapURL, aScriptURL) { 1.5121 + const base = this._dirname( 1.5122 + aAbsSourceMapURL.indexOf("data:") === 0 1.5123 + ? aScriptURL 1.5124 + : aAbsSourceMapURL); 1.5125 + aSourceMap.sourceRoot = aSourceMap.sourceRoot 1.5126 + ? this._normalize(aSourceMap.sourceRoot, base) 1.5127 + : base; 1.5128 + }, 1.5129 + 1.5130 + _dirname: function (aPath) { 1.5131 + return Services.io.newURI( 1.5132 + ".", null, Services.io.newURI(aPath, null, null)).spec; 1.5133 + }, 1.5134 + 1.5135 + /** 1.5136 + * Returns a promise of the location in the original source if the source is 1.5137 + * source mapped, otherwise a promise of the same location. 1.5138 + */ 1.5139 + getOriginalLocation: function ({ url, line, column }) { 1.5140 + if (url in this._sourceMapsByGeneratedSource) { 1.5141 + column = column || 0; 1.5142 + 1.5143 + return this._sourceMapsByGeneratedSource[url] 1.5144 + .then((aSourceMap) => { 1.5145 + let { source: aSourceURL, line: aLine, column: aColumn } = aSourceMap.originalPositionFor({ 1.5146 + line: line, 1.5147 + column: column 1.5148 + }); 1.5149 + return { 1.5150 + url: aSourceURL, 1.5151 + line: aLine, 1.5152 + column: aColumn 1.5153 + }; 1.5154 + }) 1.5155 + .then(null, error => { 1.5156 + if (!DevToolsUtils.reportingDisabled) { 1.5157 + DevToolsUtils.reportException("ThreadSources.prototype.getOriginalLocation", error); 1.5158 + } 1.5159 + return { url: null, line: null, column: null }; 1.5160 + }); 1.5161 + } 1.5162 + 1.5163 + // No source map 1.5164 + return resolve({ 1.5165 + url: url, 1.5166 + line: line, 1.5167 + column: column 1.5168 + }); 1.5169 + }, 1.5170 + 1.5171 + /** 1.5172 + * Returns a promise of the location in the generated source corresponding to 1.5173 + * the original source and line given. 1.5174 + * 1.5175 + * When we pass a script S representing generated code to |sourceMap|, 1.5176 + * above, that returns a promise P. The process of resolving P populates 1.5177 + * the tables this function uses; thus, it won't know that S's original 1.5178 + * source URLs map to S until P is resolved. 1.5179 + */ 1.5180 + getGeneratedLocation: function ({ url, line, column }) { 1.5181 + if (url in this._sourceMapsByOriginalSource) { 1.5182 + return this._sourceMapsByOriginalSource[url] 1.5183 + .then((aSourceMap) => { 1.5184 + let { line: aLine, column: aColumn } = aSourceMap.generatedPositionFor({ 1.5185 + source: url, 1.5186 + line: line, 1.5187 + column: column == null ? Infinity : column 1.5188 + }); 1.5189 + return { 1.5190 + url: this._generatedUrlsByOriginalUrl[url], 1.5191 + line: aLine, 1.5192 + column: aColumn 1.5193 + }; 1.5194 + }); 1.5195 + } 1.5196 + 1.5197 + // No source map 1.5198 + return resolve({ 1.5199 + url: url, 1.5200 + line: line, 1.5201 + column: column 1.5202 + }); 1.5203 + }, 1.5204 + 1.5205 + /** 1.5206 + * Returns true if URL for the given source is black boxed. 1.5207 + * 1.5208 + * @param aURL String 1.5209 + * The URL of the source which we are checking whether it is black 1.5210 + * boxed or not. 1.5211 + */ 1.5212 + isBlackBoxed: function (aURL) { 1.5213 + return ThreadSources._blackBoxedSources.has(aURL); 1.5214 + }, 1.5215 + 1.5216 + /** 1.5217 + * Add the given source URL to the set of sources that are black boxed. 1.5218 + * 1.5219 + * @param aURL String 1.5220 + * The URL of the source which we are black boxing. 1.5221 + */ 1.5222 + blackBox: function (aURL) { 1.5223 + ThreadSources._blackBoxedSources.add(aURL); 1.5224 + }, 1.5225 + 1.5226 + /** 1.5227 + * Remove the given source URL to the set of sources that are black boxed. 1.5228 + * 1.5229 + * @param aURL String 1.5230 + * The URL of the source which we are no longer black boxing. 1.5231 + */ 1.5232 + unblackBox: function (aURL) { 1.5233 + ThreadSources._blackBoxedSources.delete(aURL); 1.5234 + }, 1.5235 + 1.5236 + /** 1.5237 + * Returns true if the given URL is pretty printed. 1.5238 + * 1.5239 + * @param aURL String 1.5240 + * The URL of the source that might be pretty printed. 1.5241 + */ 1.5242 + isPrettyPrinted: function (aURL) { 1.5243 + return ThreadSources._prettyPrintedSources.has(aURL); 1.5244 + }, 1.5245 + 1.5246 + /** 1.5247 + * Add the given URL to the set of sources that are pretty printed. 1.5248 + * 1.5249 + * @param aURL String 1.5250 + * The URL of the source to be pretty printed. 1.5251 + */ 1.5252 + prettyPrint: function (aURL, aIndent) { 1.5253 + ThreadSources._prettyPrintedSources.set(aURL, aIndent); 1.5254 + }, 1.5255 + 1.5256 + /** 1.5257 + * Return the indent the given URL was pretty printed by. 1.5258 + */ 1.5259 + prettyPrintIndent: function (aURL) { 1.5260 + return ThreadSources._prettyPrintedSources.get(aURL); 1.5261 + }, 1.5262 + 1.5263 + /** 1.5264 + * Remove the given URL from the set of sources that are pretty printed. 1.5265 + * 1.5266 + * @param aURL String 1.5267 + * The URL of the source that is no longer pretty printed. 1.5268 + */ 1.5269 + disablePrettyPrint: function (aURL) { 1.5270 + ThreadSources._prettyPrintedSources.delete(aURL); 1.5271 + }, 1.5272 + 1.5273 + /** 1.5274 + * Normalize multiple relative paths towards the base paths on the right. 1.5275 + */ 1.5276 + _normalize: function (...aURLs) { 1.5277 + dbg_assert(aURLs.length > 1, "Should have more than 1 URL"); 1.5278 + let base = Services.io.newURI(aURLs.pop(), null, null); 1.5279 + let url; 1.5280 + while ((url = aURLs.pop())) { 1.5281 + base = Services.io.newURI(url, null, base); 1.5282 + } 1.5283 + return base.spec; 1.5284 + }, 1.5285 + 1.5286 + iter: function* () { 1.5287 + for (let url in this._sourceActors) { 1.5288 + yield this._sourceActors[url]; 1.5289 + } 1.5290 + } 1.5291 +}; 1.5292 + 1.5293 +// Utility functions. 1.5294 + 1.5295 +// TODO bug 863089: use Debugger.Script.prototype.getOffsetColumn when it is 1.5296 +// implemented. 1.5297 +function getOffsetColumn(aOffset, aScript) { 1.5298 + let bestOffsetMapping = null; 1.5299 + for (let offsetMapping of aScript.getAllColumnOffsets()) { 1.5300 + if (!bestOffsetMapping || 1.5301 + (offsetMapping.offset <= aOffset && 1.5302 + offsetMapping.offset > bestOffsetMapping.offset)) { 1.5303 + bestOffsetMapping = offsetMapping; 1.5304 + } 1.5305 + } 1.5306 + 1.5307 + if (!bestOffsetMapping) { 1.5308 + // XXX: Try not to completely break the experience of using the debugger for 1.5309 + // the user by assuming column 0. Simultaneously, report the error so that 1.5310 + // there is a paper trail if the assumption is bad and the debugging 1.5311 + // experience becomes wonky. 1.5312 + reportError(new Error("Could not find a column for offset " + aOffset 1.5313 + + " in the script " + aScript)); 1.5314 + return 0; 1.5315 + } 1.5316 + 1.5317 + return bestOffsetMapping.columnNumber; 1.5318 +} 1.5319 + 1.5320 +/** 1.5321 + * Return the non-source-mapped location of the given Debugger.Frame. If the 1.5322 + * frame does not have a script, the location's properties are all null. 1.5323 + * 1.5324 + * @param Debugger.Frame aFrame 1.5325 + * The frame whose location we are getting. 1.5326 + * @returns Object 1.5327 + * Returns an object of the form { url, line, column } 1.5328 + */ 1.5329 +function getFrameLocation(aFrame) { 1.5330 + if (!aFrame || !aFrame.script) { 1.5331 + return { url: null, line: null, column: null }; 1.5332 + } 1.5333 + return { 1.5334 + url: aFrame.script.url, 1.5335 + line: aFrame.script.getOffsetLine(aFrame.offset), 1.5336 + column: getOffsetColumn(aFrame.offset, aFrame.script) 1.5337 + } 1.5338 +} 1.5339 + 1.5340 +/** 1.5341 + * Utility function for updating an object with the properties of another 1.5342 + * object. 1.5343 + * 1.5344 + * @param aTarget Object 1.5345 + * The object being updated. 1.5346 + * @param aNewAttrs Object 1.5347 + * The new attributes being set on the target. 1.5348 + */ 1.5349 +function update(aTarget, aNewAttrs) { 1.5350 + for (let key in aNewAttrs) { 1.5351 + let desc = Object.getOwnPropertyDescriptor(aNewAttrs, key); 1.5352 + 1.5353 + if (desc) { 1.5354 + Object.defineProperty(aTarget, key, desc); 1.5355 + } 1.5356 + } 1.5357 +} 1.5358 + 1.5359 +/** 1.5360 + * Returns true if its argument is not null. 1.5361 + */ 1.5362 +function isNotNull(aThing) { 1.5363 + return aThing !== null; 1.5364 +} 1.5365 + 1.5366 +/** 1.5367 + * Performs a request to load the desired URL and returns a promise. 1.5368 + * 1.5369 + * @param aURL String 1.5370 + * The URL we will request. 1.5371 + * @returns Promise 1.5372 + * A promise of the document at that URL, as a string. 1.5373 + * 1.5374 + * XXX: It may be better to use nsITraceableChannel to get to the sources 1.5375 + * without relying on caching when we can (not for eval, etc.): 1.5376 + * http://www.softwareishard.com/blog/firebug/nsitraceablechannel-intercept-http-traffic/ 1.5377 + */ 1.5378 +function fetch(aURL, aOptions={ loadFromCache: true }) { 1.5379 + let deferred = defer(); 1.5380 + let scheme; 1.5381 + let url = aURL.split(" -> ").pop(); 1.5382 + let charset; 1.5383 + let contentType; 1.5384 + 1.5385 + try { 1.5386 + scheme = Services.io.extractScheme(url); 1.5387 + } catch (e) { 1.5388 + // In the xpcshell tests, the script url is the absolute path of the test 1.5389 + // file, which will make a malformed URI error be thrown. Add the file 1.5390 + // scheme prefix ourselves. 1.5391 + url = "file://" + url; 1.5392 + scheme = Services.io.extractScheme(url); 1.5393 + } 1.5394 + 1.5395 + switch (scheme) { 1.5396 + case "file": 1.5397 + case "chrome": 1.5398 + case "resource": 1.5399 + try { 1.5400 + NetUtil.asyncFetch(url, function onFetch(aStream, aStatus, aRequest) { 1.5401 + if (!Components.isSuccessCode(aStatus)) { 1.5402 + deferred.reject(new Error("Request failed with status code = " 1.5403 + + aStatus 1.5404 + + " after NetUtil.asyncFetch for url = " 1.5405 + + url)); 1.5406 + return; 1.5407 + } 1.5408 + 1.5409 + let source = NetUtil.readInputStreamToString(aStream, aStream.available()); 1.5410 + contentType = aRequest.contentType; 1.5411 + deferred.resolve(source); 1.5412 + aStream.close(); 1.5413 + }); 1.5414 + } catch (ex) { 1.5415 + deferred.reject(ex); 1.5416 + } 1.5417 + break; 1.5418 + 1.5419 + default: 1.5420 + let channel; 1.5421 + try { 1.5422 + channel = Services.io.newChannel(url, null, null); 1.5423 + } catch (e if e.name == "NS_ERROR_UNKNOWN_PROTOCOL") { 1.5424 + // On Windows xpcshell tests, c:/foo/bar can pass as a valid URL, but 1.5425 + // newChannel won't be able to handle it. 1.5426 + url = "file:///" + url; 1.5427 + channel = Services.io.newChannel(url, null, null); 1.5428 + } 1.5429 + let chunks = []; 1.5430 + let streamListener = { 1.5431 + onStartRequest: function(aRequest, aContext, aStatusCode) { 1.5432 + if (!Components.isSuccessCode(aStatusCode)) { 1.5433 + deferred.reject(new Error("Request failed with status code = " 1.5434 + + aStatusCode 1.5435 + + " in onStartRequest handler for url = " 1.5436 + + url)); 1.5437 + } 1.5438 + }, 1.5439 + onDataAvailable: function(aRequest, aContext, aStream, aOffset, aCount) { 1.5440 + chunks.push(NetUtil.readInputStreamToString(aStream, aCount)); 1.5441 + }, 1.5442 + onStopRequest: function(aRequest, aContext, aStatusCode) { 1.5443 + if (!Components.isSuccessCode(aStatusCode)) { 1.5444 + deferred.reject(new Error("Request failed with status code = " 1.5445 + + aStatusCode 1.5446 + + " in onStopRequest handler for url = " 1.5447 + + url)); 1.5448 + return; 1.5449 + } 1.5450 + 1.5451 + charset = channel.contentCharset; 1.5452 + contentType = channel.contentType; 1.5453 + deferred.resolve(chunks.join("")); 1.5454 + } 1.5455 + }; 1.5456 + 1.5457 + channel.loadFlags = aOptions.loadFromCache 1.5458 + ? channel.LOAD_FROM_CACHE 1.5459 + : channel.LOAD_BYPASS_CACHE; 1.5460 + channel.asyncOpen(streamListener, null); 1.5461 + break; 1.5462 + } 1.5463 + 1.5464 + return deferred.promise.then(source => { 1.5465 + return { 1.5466 + content: convertToUnicode(source, charset), 1.5467 + contentType: contentType 1.5468 + }; 1.5469 + }); 1.5470 +} 1.5471 + 1.5472 +/** 1.5473 + * Convert a given string, encoded in a given character set, to unicode. 1.5474 + * 1.5475 + * @param string aString 1.5476 + * A string. 1.5477 + * @param string aCharset 1.5478 + * A character set. 1.5479 + */ 1.5480 +function convertToUnicode(aString, aCharset=null) { 1.5481 + // Decoding primitives. 1.5482 + let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"] 1.5483 + .createInstance(Ci.nsIScriptableUnicodeConverter); 1.5484 + try { 1.5485 + converter.charset = aCharset || "UTF-8"; 1.5486 + return converter.ConvertToUnicode(aString); 1.5487 + } catch(e) { 1.5488 + return aString; 1.5489 + } 1.5490 +} 1.5491 + 1.5492 +/** 1.5493 + * Report the given error in the error console and to stdout. 1.5494 + * 1.5495 + * @param Error aError 1.5496 + * The error object you wish to report. 1.5497 + * @param String aPrefix 1.5498 + * An optional prefix for the reported error message. 1.5499 + */ 1.5500 +function reportError(aError, aPrefix="") { 1.5501 + dbg_assert(aError instanceof Error, "Must pass Error objects to reportError"); 1.5502 + let msg = aPrefix + aError.message + ":\n" + aError.stack; 1.5503 + Cu.reportError(msg); 1.5504 + dumpn(msg); 1.5505 +} 1.5506 + 1.5507 +// The following are copied here verbatim from css-logic.js, until we create a 1.5508 +// server-friendly helper module. 1.5509 + 1.5510 +/** 1.5511 + * Find a unique CSS selector for a given element 1.5512 + * @returns a string such that ele.ownerDocument.querySelector(reply) === ele 1.5513 + * and ele.ownerDocument.querySelectorAll(reply).length === 1 1.5514 + */ 1.5515 +function findCssSelector(ele) { 1.5516 + var document = ele.ownerDocument; 1.5517 + if (ele.id && document.getElementById(ele.id) === ele) { 1.5518 + return '#' + ele.id; 1.5519 + } 1.5520 + 1.5521 + // Inherently unique by tag name 1.5522 + var tagName = ele.tagName.toLowerCase(); 1.5523 + if (tagName === 'html') { 1.5524 + return 'html'; 1.5525 + } 1.5526 + if (tagName === 'head') { 1.5527 + return 'head'; 1.5528 + } 1.5529 + if (tagName === 'body') { 1.5530 + return 'body'; 1.5531 + } 1.5532 + 1.5533 + if (ele.parentNode == null) { 1.5534 + console.log('danger: ' + tagName); 1.5535 + } 1.5536 + 1.5537 + // We might be able to find a unique class name 1.5538 + var selector, index, matches; 1.5539 + if (ele.classList.length > 0) { 1.5540 + for (var i = 0; i < ele.classList.length; i++) { 1.5541 + // Is this className unique by itself? 1.5542 + selector = '.' + ele.classList.item(i); 1.5543 + matches = document.querySelectorAll(selector); 1.5544 + if (matches.length === 1) { 1.5545 + return selector; 1.5546 + } 1.5547 + // Maybe it's unique with a tag name? 1.5548 + selector = tagName + selector; 1.5549 + matches = document.querySelectorAll(selector); 1.5550 + if (matches.length === 1) { 1.5551 + return selector; 1.5552 + } 1.5553 + // Maybe it's unique using a tag name and nth-child 1.5554 + index = positionInNodeList(ele, ele.parentNode.children) + 1; 1.5555 + selector = selector + ':nth-child(' + index + ')'; 1.5556 + matches = document.querySelectorAll(selector); 1.5557 + if (matches.length === 1) { 1.5558 + return selector; 1.5559 + } 1.5560 + } 1.5561 + } 1.5562 + 1.5563 + // So we can be unique w.r.t. our parent, and use recursion 1.5564 + index = positionInNodeList(ele, ele.parentNode.children) + 1; 1.5565 + selector = findCssSelector(ele.parentNode) + ' > ' + 1.5566 + tagName + ':nth-child(' + index + ')'; 1.5567 + 1.5568 + return selector; 1.5569 +}; 1.5570 + 1.5571 +/** 1.5572 + * Find the position of [element] in [nodeList]. 1.5573 + * @returns an index of the match, or -1 if there is no match 1.5574 + */ 1.5575 +function positionInNodeList(element, nodeList) { 1.5576 + for (var i = 0; i < nodeList.length; i++) { 1.5577 + if (element === nodeList[i]) { 1.5578 + return i; 1.5579 + } 1.5580 + } 1.5581 + return -1; 1.5582 +} 1.5583 + 1.5584 +/** 1.5585 + * Make a debuggee value for the given object, if needed. Primitive values 1.5586 + * are left the same. 1.5587 + * 1.5588 + * Use case: you have a raw JS object (after unsafe dereference) and you want to 1.5589 + * send it to the client. In that case you need to use an ObjectActor which 1.5590 + * requires a debuggee value. The Debugger.Object.prototype.makeDebuggeeValue() 1.5591 + * method works only for JS objects and functions. 1.5592 + * 1.5593 + * @param Debugger.Object obj 1.5594 + * @param any value 1.5595 + * @return object 1.5596 + */ 1.5597 +function makeDebuggeeValueIfNeeded(obj, value) { 1.5598 + if (value && (typeof value == "object" || typeof value == "function")) { 1.5599 + return obj.makeDebuggeeValue(value); 1.5600 + } 1.5601 + return value; 1.5602 +} 1.5603 + 1.5604 +function getInnerId(window) { 1.5605 + return window.QueryInterface(Ci.nsIInterfaceRequestor). 1.5606 + getInterface(Ci.nsIDOMWindowUtils).currentInnerWindowID; 1.5607 +};