toolkit/devtools/client/dbg-client.jsm

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

michael@0 1 /* -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
michael@0 2 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
michael@0 3 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 4 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 6
michael@0 7 "use strict";
michael@0 8 var Ci = Components.interfaces;
michael@0 9 var Cc = Components.classes;
michael@0 10 var Cu = Components.utils;
michael@0 11 var Cr = Components.results;
michael@0 12 // On B2G scope object misbehaves and we have to bind globals to `this`
michael@0 13 // in order to ensure theses variable to be visible in transport.js
michael@0 14 this.Ci = Ci;
michael@0 15 this.Cc = Cc;
michael@0 16 this.Cu = Cu;
michael@0 17 this.Cr = Cr;
michael@0 18
michael@0 19 this.EXPORTED_SYMBOLS = ["DebuggerTransport",
michael@0 20 "DebuggerClient",
michael@0 21 "RootClient",
michael@0 22 "debuggerSocketConnect",
michael@0 23 "LongStringClient",
michael@0 24 "EnvironmentClient",
michael@0 25 "ObjectClient"];
michael@0 26
michael@0 27 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
michael@0 28 Cu.import("resource://gre/modules/NetUtil.jsm");
michael@0 29 Cu.import("resource://gre/modules/Services.jsm");
michael@0 30 Cu.import("resource://gre/modules/Timer.jsm");
michael@0 31
michael@0 32 let promise = Cu.import("resource://gre/modules/devtools/deprecated-sync-thenables.js").Promise;
michael@0 33 const { defer, resolve, reject } = promise;
michael@0 34
michael@0 35 XPCOMUtils.defineLazyServiceGetter(this, "socketTransportService",
michael@0 36 "@mozilla.org/network/socket-transport-service;1",
michael@0 37 "nsISocketTransportService");
michael@0 38
michael@0 39 XPCOMUtils.defineLazyModuleGetter(this, "console",
michael@0 40 "resource://gre/modules/devtools/Console.jsm");
michael@0 41
michael@0 42 XPCOMUtils.defineLazyModuleGetter(this, "devtools",
michael@0 43 "resource://gre/modules/devtools/Loader.jsm");
michael@0 44
michael@0 45 Object.defineProperty(this, "WebConsoleClient", {
michael@0 46 get: function () {
michael@0 47 return devtools.require("devtools/toolkit/webconsole/client").WebConsoleClient;
michael@0 48 },
michael@0 49 configurable: true,
michael@0 50 enumerable: true
michael@0 51 });
michael@0 52
michael@0 53 Components.utils.import("resource://gre/modules/devtools/DevToolsUtils.jsm");
michael@0 54 this.makeInfallible = DevToolsUtils.makeInfallible;
michael@0 55
michael@0 56 let wantLogging = Services.prefs.getBoolPref("devtools.debugger.log");
michael@0 57
michael@0 58 function dumpn(str)
michael@0 59 {
michael@0 60 if (wantLogging) {
michael@0 61 dump("DBG-CLIENT: " + str + "\n");
michael@0 62 }
michael@0 63 }
michael@0 64
michael@0 65 let loader = Cc["@mozilla.org/moz/jssubscript-loader;1"]
michael@0 66 .getService(Ci.mozIJSSubScriptLoader);
michael@0 67 loader.loadSubScript("resource://gre/modules/devtools/server/transport.js", this);
michael@0 68
michael@0 69 /**
michael@0 70 * Add simple event notification to a prototype object. Any object that has
michael@0 71 * some use for event notifications or the observer pattern in general can be
michael@0 72 * augmented with the necessary facilities by passing its prototype to this
michael@0 73 * function.
michael@0 74 *
michael@0 75 * @param aProto object
michael@0 76 * The prototype object that will be modified.
michael@0 77 */
michael@0 78 function eventSource(aProto) {
michael@0 79 /**
michael@0 80 * Add a listener to the event source for a given event.
michael@0 81 *
michael@0 82 * @param aName string
michael@0 83 * The event to listen for.
michael@0 84 * @param aListener function
michael@0 85 * Called when the event is fired. If the same listener
michael@0 86 * is added more than once, it will be called once per
michael@0 87 * addListener call.
michael@0 88 */
michael@0 89 aProto.addListener = function (aName, aListener) {
michael@0 90 if (typeof aListener != "function") {
michael@0 91 throw TypeError("Listeners must be functions.");
michael@0 92 }
michael@0 93
michael@0 94 if (!this._listeners) {
michael@0 95 this._listeners = {};
michael@0 96 }
michael@0 97
michael@0 98 this._getListeners(aName).push(aListener);
michael@0 99 };
michael@0 100
michael@0 101 /**
michael@0 102 * Add a listener to the event source for a given event. The
michael@0 103 * listener will be removed after it is called for the first time.
michael@0 104 *
michael@0 105 * @param aName string
michael@0 106 * The event to listen for.
michael@0 107 * @param aListener function
michael@0 108 * Called when the event is fired.
michael@0 109 */
michael@0 110 aProto.addOneTimeListener = function (aName, aListener) {
michael@0 111 let l = (...args) => {
michael@0 112 this.removeListener(aName, l);
michael@0 113 aListener.apply(null, args);
michael@0 114 };
michael@0 115 this.addListener(aName, l);
michael@0 116 };
michael@0 117
michael@0 118 /**
michael@0 119 * Remove a listener from the event source previously added with
michael@0 120 * addListener().
michael@0 121 *
michael@0 122 * @param aName string
michael@0 123 * The event name used during addListener to add the listener.
michael@0 124 * @param aListener function
michael@0 125 * The callback to remove. If addListener was called multiple
michael@0 126 * times, all instances will be removed.
michael@0 127 */
michael@0 128 aProto.removeListener = function (aName, aListener) {
michael@0 129 if (!this._listeners || !this._listeners[aName]) {
michael@0 130 return;
michael@0 131 }
michael@0 132 this._listeners[aName] =
michael@0 133 this._listeners[aName].filter(function (l) { return l != aListener });
michael@0 134 };
michael@0 135
michael@0 136 /**
michael@0 137 * Returns the listeners for the specified event name. If none are defined it
michael@0 138 * initializes an empty list and returns that.
michael@0 139 *
michael@0 140 * @param aName string
michael@0 141 * The event name.
michael@0 142 */
michael@0 143 aProto._getListeners = function (aName) {
michael@0 144 if (aName in this._listeners) {
michael@0 145 return this._listeners[aName];
michael@0 146 }
michael@0 147 this._listeners[aName] = [];
michael@0 148 return this._listeners[aName];
michael@0 149 };
michael@0 150
michael@0 151 /**
michael@0 152 * Notify listeners of an event.
michael@0 153 *
michael@0 154 * @param aName string
michael@0 155 * The event to fire.
michael@0 156 * @param arguments
michael@0 157 * All arguments will be passed along to the listeners,
michael@0 158 * including the name argument.
michael@0 159 */
michael@0 160 aProto.notify = function () {
michael@0 161 if (!this._listeners) {
michael@0 162 return;
michael@0 163 }
michael@0 164
michael@0 165 let name = arguments[0];
michael@0 166 let listeners = this._getListeners(name).slice(0);
michael@0 167
michael@0 168 for each (let listener in listeners) {
michael@0 169 try {
michael@0 170 listener.apply(null, arguments);
michael@0 171 } catch (e) {
michael@0 172 // Prevent a bad listener from interfering with the others.
michael@0 173 DevToolsUtils.reportException("notify event '" + name + "'", e);
michael@0 174 }
michael@0 175 }
michael@0 176 }
michael@0 177 }
michael@0 178
michael@0 179 /**
michael@0 180 * Set of protocol messages that affect thread state, and the
michael@0 181 * state the actor is in after each message.
michael@0 182 */
michael@0 183 const ThreadStateTypes = {
michael@0 184 "paused": "paused",
michael@0 185 "resumed": "attached",
michael@0 186 "detached": "detached"
michael@0 187 };
michael@0 188
michael@0 189 /**
michael@0 190 * Set of protocol messages that are sent by the server without a prior request
michael@0 191 * by the client.
michael@0 192 */
michael@0 193 const UnsolicitedNotifications = {
michael@0 194 "consoleAPICall": "consoleAPICall",
michael@0 195 "eventNotification": "eventNotification",
michael@0 196 "fileActivity": "fileActivity",
michael@0 197 "lastPrivateContextExited": "lastPrivateContextExited",
michael@0 198 "logMessage": "logMessage",
michael@0 199 "networkEvent": "networkEvent",
michael@0 200 "networkEventUpdate": "networkEventUpdate",
michael@0 201 "newGlobal": "newGlobal",
michael@0 202 "newScript": "newScript",
michael@0 203 "newSource": "newSource",
michael@0 204 "tabDetached": "tabDetached",
michael@0 205 "tabListChanged": "tabListChanged",
michael@0 206 "reflowActivity": "reflowActivity",
michael@0 207 "addonListChanged": "addonListChanged",
michael@0 208 "tabNavigated": "tabNavigated",
michael@0 209 "pageError": "pageError",
michael@0 210 "documentLoad": "documentLoad",
michael@0 211 "enteredFrame": "enteredFrame",
michael@0 212 "exitedFrame": "exitedFrame",
michael@0 213 "appOpen": "appOpen",
michael@0 214 "appClose": "appClose",
michael@0 215 "appInstall": "appInstall",
michael@0 216 "appUninstall": "appUninstall"
michael@0 217 };
michael@0 218
michael@0 219 /**
michael@0 220 * Set of pause types that are sent by the server and not as an immediate
michael@0 221 * response to a client request.
michael@0 222 */
michael@0 223 const UnsolicitedPauses = {
michael@0 224 "resumeLimit": "resumeLimit",
michael@0 225 "debuggerStatement": "debuggerStatement",
michael@0 226 "breakpoint": "breakpoint",
michael@0 227 "DOMEvent": "DOMEvent",
michael@0 228 "watchpoint": "watchpoint",
michael@0 229 "exception": "exception"
michael@0 230 };
michael@0 231
michael@0 232 /**
michael@0 233 * Creates a client for the remote debugging protocol server. This client
michael@0 234 * provides the means to communicate with the server and exchange the messages
michael@0 235 * required by the protocol in a traditional JavaScript API.
michael@0 236 */
michael@0 237 this.DebuggerClient = function (aTransport)
michael@0 238 {
michael@0 239 this._transport = aTransport;
michael@0 240 this._transport.hooks = this;
michael@0 241
michael@0 242 // Map actor ID to client instance for each actor type.
michael@0 243 this._threadClients = new Map;
michael@0 244 this._addonClients = new Map;
michael@0 245 this._tabClients = new Map;
michael@0 246 this._tracerClients = new Map;
michael@0 247 this._consoleClients = new Map;
michael@0 248
michael@0 249 this._pendingRequests = [];
michael@0 250 this._activeRequests = new Map;
michael@0 251 this._eventsEnabled = true;
michael@0 252
michael@0 253 this.compat = new ProtocolCompatibility(this, []);
michael@0 254 this.traits = {};
michael@0 255
michael@0 256 this.request = this.request.bind(this);
michael@0 257 this.localTransport = this._transport.onOutputStreamReady === undefined;
michael@0 258
michael@0 259 /*
michael@0 260 * As the first thing on the connection, expect a greeting packet from
michael@0 261 * the connection's root actor.
michael@0 262 */
michael@0 263 this.mainRoot = null;
michael@0 264 this.expectReply("root", (aPacket) => {
michael@0 265 this.mainRoot = new RootClient(this, aPacket);
michael@0 266 this.notify("connected", aPacket.applicationType, aPacket.traits);
michael@0 267 });
michael@0 268 }
michael@0 269
michael@0 270 /**
michael@0 271 * A declarative helper for defining methods that send requests to the server.
michael@0 272 *
michael@0 273 * @param aPacketSkeleton
michael@0 274 * The form of the packet to send. Can specify fields to be filled from
michael@0 275 * the parameters by using the |args| function.
michael@0 276 * @param telemetry
michael@0 277 * The unique suffix of the telemetry histogram id.
michael@0 278 * @param before
michael@0 279 * The function to call before sending the packet. Is passed the packet,
michael@0 280 * and the return value is used as the new packet. The |this| context is
michael@0 281 * the instance of the client object we are defining a method for.
michael@0 282 * @param after
michael@0 283 * The function to call after the response is received. It is passed the
michael@0 284 * response, and the return value is considered the new response that
michael@0 285 * will be passed to the callback. The |this| context is the instance of
michael@0 286 * the client object we are defining a method for.
michael@0 287 */
michael@0 288 DebuggerClient.requester = function (aPacketSkeleton,
michael@0 289 { telemetry, before, after }) {
michael@0 290 return DevToolsUtils.makeInfallible(function (...args) {
michael@0 291 let histogram, startTime;
michael@0 292 if (telemetry) {
michael@0 293 let transportType = this._transport.onOutputStreamReady === undefined
michael@0 294 ? "LOCAL_"
michael@0 295 : "REMOTE_";
michael@0 296 let histogramId = "DEVTOOLS_DEBUGGER_RDP_"
michael@0 297 + transportType + telemetry + "_MS";
michael@0 298 histogram = Services.telemetry.getHistogramById(histogramId);
michael@0 299 startTime = +new Date;
michael@0 300 }
michael@0 301 let outgoingPacket = {
michael@0 302 to: aPacketSkeleton.to || this.actor
michael@0 303 };
michael@0 304
michael@0 305 let maxPosition = -1;
michael@0 306 for (let k of Object.keys(aPacketSkeleton)) {
michael@0 307 if (aPacketSkeleton[k] instanceof DebuggerClient.Argument) {
michael@0 308 let { position } = aPacketSkeleton[k];
michael@0 309 outgoingPacket[k] = aPacketSkeleton[k].getArgument(args);
michael@0 310 maxPosition = Math.max(position, maxPosition);
michael@0 311 } else {
michael@0 312 outgoingPacket[k] = aPacketSkeleton[k];
michael@0 313 }
michael@0 314 }
michael@0 315
michael@0 316 if (before) {
michael@0 317 outgoingPacket = before.call(this, outgoingPacket);
michael@0 318 }
michael@0 319
michael@0 320 this.request(outgoingPacket, DevToolsUtils.makeInfallible(function (aResponse) {
michael@0 321 if (after) {
michael@0 322 let { from } = aResponse;
michael@0 323 aResponse = after.call(this, aResponse);
michael@0 324 if (!aResponse.from) {
michael@0 325 aResponse.from = from;
michael@0 326 }
michael@0 327 }
michael@0 328
michael@0 329 // The callback is always the last parameter.
michael@0 330 let thisCallback = args[maxPosition + 1];
michael@0 331 if (thisCallback) {
michael@0 332 thisCallback(aResponse);
michael@0 333 }
michael@0 334
michael@0 335 if (histogram) {
michael@0 336 histogram.add(+new Date - startTime);
michael@0 337 }
michael@0 338 }.bind(this), "DebuggerClient.requester request callback"));
michael@0 339
michael@0 340 }, "DebuggerClient.requester");
michael@0 341 };
michael@0 342
michael@0 343 function args(aPos) {
michael@0 344 return new DebuggerClient.Argument(aPos);
michael@0 345 }
michael@0 346
michael@0 347 DebuggerClient.Argument = function (aPosition) {
michael@0 348 this.position = aPosition;
michael@0 349 };
michael@0 350
michael@0 351 DebuggerClient.Argument.prototype.getArgument = function (aParams) {
michael@0 352 if (!(this.position in aParams)) {
michael@0 353 throw new Error("Bad index into params: " + this.position);
michael@0 354 }
michael@0 355 return aParams[this.position];
michael@0 356 };
michael@0 357
michael@0 358 DebuggerClient.prototype = {
michael@0 359 /**
michael@0 360 * Connect to the server and start exchanging protocol messages.
michael@0 361 *
michael@0 362 * @param aOnConnected function
michael@0 363 * If specified, will be called when the greeting packet is
michael@0 364 * received from the debugging server.
michael@0 365 */
michael@0 366 connect: function (aOnConnected) {
michael@0 367 this.addOneTimeListener("connected", (aName, aApplicationType, aTraits) => {
michael@0 368 this.traits = aTraits;
michael@0 369 if (aOnConnected) {
michael@0 370 aOnConnected(aApplicationType, aTraits);
michael@0 371 }
michael@0 372 });
michael@0 373
michael@0 374 this._transport.ready();
michael@0 375 },
michael@0 376
michael@0 377 /**
michael@0 378 * Shut down communication with the debugging server.
michael@0 379 *
michael@0 380 * @param aOnClosed function
michael@0 381 * If specified, will be called when the debugging connection
michael@0 382 * has been closed.
michael@0 383 */
michael@0 384 close: function (aOnClosed) {
michael@0 385 // Disable detach event notifications, because event handlers will be in a
michael@0 386 // cleared scope by the time they run.
michael@0 387 this._eventsEnabled = false;
michael@0 388
michael@0 389 if (aOnClosed) {
michael@0 390 this.addOneTimeListener('closed', function (aEvent) {
michael@0 391 aOnClosed();
michael@0 392 });
michael@0 393 }
michael@0 394
michael@0 395 const detachClients = (clientMap, next) => {
michael@0 396 const clients = clientMap.values();
michael@0 397 const total = clientMap.size;
michael@0 398 let numFinished = 0;
michael@0 399
michael@0 400 if (total == 0) {
michael@0 401 next();
michael@0 402 return;
michael@0 403 }
michael@0 404
michael@0 405 for (let client of clients) {
michael@0 406 let method = client instanceof WebConsoleClient ? "close" : "detach";
michael@0 407 client[method](() => {
michael@0 408 if (++numFinished === total) {
michael@0 409 clientMap.clear();
michael@0 410 next();
michael@0 411 }
michael@0 412 });
michael@0 413 }
michael@0 414 };
michael@0 415
michael@0 416 detachClients(this._consoleClients, () => {
michael@0 417 detachClients(this._threadClients, () => {
michael@0 418 detachClients(this._tabClients, () => {
michael@0 419 detachClients(this._addonClients, () => {
michael@0 420 this._transport.close();
michael@0 421 this._transport = null;
michael@0 422 });
michael@0 423 });
michael@0 424 });
michael@0 425 });
michael@0 426 },
michael@0 427
michael@0 428 /*
michael@0 429 * This function exists only to preserve DebuggerClient's interface;
michael@0 430 * new code should say 'client.mainRoot.listTabs()'.
michael@0 431 */
michael@0 432 listTabs: function (aOnResponse) { return this.mainRoot.listTabs(aOnResponse); },
michael@0 433
michael@0 434 /*
michael@0 435 * This function exists only to preserve DebuggerClient's interface;
michael@0 436 * new code should say 'client.mainRoot.listAddons()'.
michael@0 437 */
michael@0 438 listAddons: function (aOnResponse) { return this.mainRoot.listAddons(aOnResponse); },
michael@0 439
michael@0 440 /**
michael@0 441 * Attach to a tab actor.
michael@0 442 *
michael@0 443 * @param string aTabActor
michael@0 444 * The actor ID for the tab to attach.
michael@0 445 * @param function aOnResponse
michael@0 446 * Called with the response packet and a TabClient
michael@0 447 * (which will be undefined on error).
michael@0 448 */
michael@0 449 attachTab: function (aTabActor, aOnResponse) {
michael@0 450 if (this._tabClients.has(aTabActor)) {
michael@0 451 let cachedTab = this._tabClients.get(aTabActor);
michael@0 452 let cachedResponse = {
michael@0 453 cacheEnabled: cachedTab.cacheEnabled,
michael@0 454 javascriptEnabled: cachedTab.javascriptEnabled,
michael@0 455 traits: cachedTab.traits,
michael@0 456 };
michael@0 457 setTimeout(() => aOnResponse(cachedResponse, cachedTab), 0);
michael@0 458 return;
michael@0 459 }
michael@0 460
michael@0 461 let packet = {
michael@0 462 to: aTabActor,
michael@0 463 type: "attach"
michael@0 464 };
michael@0 465 this.request(packet, (aResponse) => {
michael@0 466 let tabClient;
michael@0 467 if (!aResponse.error) {
michael@0 468 tabClient = new TabClient(this, aResponse);
michael@0 469 this._tabClients.set(aTabActor, tabClient);
michael@0 470 }
michael@0 471 aOnResponse(aResponse, tabClient);
michael@0 472 });
michael@0 473 },
michael@0 474
michael@0 475 /**
michael@0 476 * Attach to an addon actor.
michael@0 477 *
michael@0 478 * @param string aAddonActor
michael@0 479 * The actor ID for the addon to attach.
michael@0 480 * @param function aOnResponse
michael@0 481 * Called with the response packet and a AddonClient
michael@0 482 * (which will be undefined on error).
michael@0 483 */
michael@0 484 attachAddon: function DC_attachAddon(aAddonActor, aOnResponse) {
michael@0 485 let packet = {
michael@0 486 to: aAddonActor,
michael@0 487 type: "attach"
michael@0 488 };
michael@0 489 this.request(packet, aResponse => {
michael@0 490 let addonClient;
michael@0 491 if (!aResponse.error) {
michael@0 492 addonClient = new AddonClient(this, aAddonActor);
michael@0 493 this._addonClients[aAddonActor] = addonClient;
michael@0 494 this.activeAddon = addonClient;
michael@0 495 }
michael@0 496 aOnResponse(aResponse, addonClient);
michael@0 497 });
michael@0 498 },
michael@0 499
michael@0 500 /**
michael@0 501 * Attach to a Web Console actor.
michael@0 502 *
michael@0 503 * @param string aConsoleActor
michael@0 504 * The ID for the console actor to attach to.
michael@0 505 * @param array aListeners
michael@0 506 * The console listeners you want to start.
michael@0 507 * @param function aOnResponse
michael@0 508 * Called with the response packet and a WebConsoleClient
michael@0 509 * instance (which will be undefined on error).
michael@0 510 */
michael@0 511 attachConsole:
michael@0 512 function (aConsoleActor, aListeners, aOnResponse) {
michael@0 513 let packet = {
michael@0 514 to: aConsoleActor,
michael@0 515 type: "startListeners",
michael@0 516 listeners: aListeners,
michael@0 517 };
michael@0 518
michael@0 519 this.request(packet, (aResponse) => {
michael@0 520 let consoleClient;
michael@0 521 if (!aResponse.error) {
michael@0 522 if (this._consoleClients.has(aConsoleActor)) {
michael@0 523 consoleClient = this._consoleClients.get(aConsoleActor);
michael@0 524 } else {
michael@0 525 consoleClient = new WebConsoleClient(this, aResponse);
michael@0 526 this._consoleClients.set(aConsoleActor, consoleClient);
michael@0 527 }
michael@0 528 }
michael@0 529 aOnResponse(aResponse, consoleClient);
michael@0 530 });
michael@0 531 },
michael@0 532
michael@0 533 /**
michael@0 534 * Attach to a global-scoped thread actor for chrome debugging.
michael@0 535 *
michael@0 536 * @param string aThreadActor
michael@0 537 * The actor ID for the thread to attach.
michael@0 538 * @param function aOnResponse
michael@0 539 * Called with the response packet and a ThreadClient
michael@0 540 * (which will be undefined on error).
michael@0 541 * @param object aOptions
michael@0 542 * Configuration options.
michael@0 543 * - useSourceMaps: whether to use source maps or not.
michael@0 544 */
michael@0 545 attachThread: function (aThreadActor, aOnResponse, aOptions={}) {
michael@0 546 if (this._threadClients.has(aThreadActor)) {
michael@0 547 setTimeout(() => aOnResponse({}, this._threadClients.get(aThreadActor)), 0);
michael@0 548 return;
michael@0 549 }
michael@0 550
michael@0 551 let packet = {
michael@0 552 to: aThreadActor,
michael@0 553 type: "attach",
michael@0 554 options: aOptions
michael@0 555 };
michael@0 556 this.request(packet, (aResponse) => {
michael@0 557 if (!aResponse.error) {
michael@0 558 var threadClient = new ThreadClient(this, aThreadActor);
michael@0 559 this._threadClients.set(aThreadActor, threadClient);
michael@0 560 }
michael@0 561 aOnResponse(aResponse, threadClient);
michael@0 562 });
michael@0 563 },
michael@0 564
michael@0 565 /**
michael@0 566 * Attach to a trace actor.
michael@0 567 *
michael@0 568 * @param string aTraceActor
michael@0 569 * The actor ID for the tracer to attach.
michael@0 570 * @param function aOnResponse
michael@0 571 * Called with the response packet and a TraceClient
michael@0 572 * (which will be undefined on error).
michael@0 573 */
michael@0 574 attachTracer: function (aTraceActor, aOnResponse) {
michael@0 575 if (this._tracerClients.has(aTraceActor)) {
michael@0 576 setTimeout(() => aOnResponse({}, this._tracerClients.get(aTraceActor)), 0);
michael@0 577 return;
michael@0 578 }
michael@0 579
michael@0 580 let packet = {
michael@0 581 to: aTraceActor,
michael@0 582 type: "attach"
michael@0 583 };
michael@0 584 this.request(packet, (aResponse) => {
michael@0 585 if (!aResponse.error) {
michael@0 586 var traceClient = new TraceClient(this, aTraceActor);
michael@0 587 this._tracerClients.set(aTraceActor, traceClient);
michael@0 588 }
michael@0 589 aOnResponse(aResponse, traceClient);
michael@0 590 });
michael@0 591 },
michael@0 592
michael@0 593 /**
michael@0 594 * Release an object actor.
michael@0 595 *
michael@0 596 * @param string aActor
michael@0 597 * The actor ID to send the request to.
michael@0 598 * @param aOnResponse function
michael@0 599 * If specified, will be called with the response packet when
michael@0 600 * debugging server responds.
michael@0 601 */
michael@0 602 release: DebuggerClient.requester({
michael@0 603 to: args(0),
michael@0 604 type: "release"
michael@0 605 }, {
michael@0 606 telemetry: "RELEASE"
michael@0 607 }),
michael@0 608
michael@0 609 /**
michael@0 610 * Send a request to the debugging server.
michael@0 611 *
michael@0 612 * @param aRequest object
michael@0 613 * A JSON packet to send to the debugging server.
michael@0 614 * @param aOnResponse function
michael@0 615 * If specified, will be called with the response packet when
michael@0 616 * debugging server responds.
michael@0 617 */
michael@0 618 request: function (aRequest, aOnResponse) {
michael@0 619 if (!this.mainRoot) {
michael@0 620 throw Error("Have not yet received a hello packet from the server.");
michael@0 621 }
michael@0 622 if (!aRequest.to) {
michael@0 623 let type = aRequest.type || "";
michael@0 624 throw Error("'" + type + "' request packet has no destination.");
michael@0 625 }
michael@0 626
michael@0 627 this._pendingRequests.push({ to: aRequest.to,
michael@0 628 request: aRequest,
michael@0 629 onResponse: aOnResponse });
michael@0 630 this._sendRequests();
michael@0 631 },
michael@0 632
michael@0 633 /**
michael@0 634 * Send pending requests to any actors that don't already have an
michael@0 635 * active request.
michael@0 636 */
michael@0 637 _sendRequests: function () {
michael@0 638 this._pendingRequests = this._pendingRequests.filter((request) => {
michael@0 639 if (this._activeRequests.has(request.to)) {
michael@0 640 return true;
michael@0 641 }
michael@0 642
michael@0 643 this.expectReply(request.to, request.onResponse);
michael@0 644 this._transport.send(request.request);
michael@0 645
michael@0 646 return false;
michael@0 647 });
michael@0 648 },
michael@0 649
michael@0 650 /**
michael@0 651 * Arrange to hand the next reply from |aActor| to |aHandler|.
michael@0 652 *
michael@0 653 * DebuggerClient.prototype.request usually takes care of establishing
michael@0 654 * the handler for a given request, but in rare cases (well, greetings
michael@0 655 * from new root actors, is the only case at the moment) we must be
michael@0 656 * prepared for a "reply" that doesn't correspond to any request we sent.
michael@0 657 */
michael@0 658 expectReply: function (aActor, aHandler) {
michael@0 659 if (this._activeRequests.has(aActor)) {
michael@0 660 throw Error("clashing handlers for next reply from " + uneval(aActor));
michael@0 661 }
michael@0 662 this._activeRequests.set(aActor, aHandler);
michael@0 663 },
michael@0 664
michael@0 665 // Transport hooks.
michael@0 666
michael@0 667 /**
michael@0 668 * Called by DebuggerTransport to dispatch incoming packets as appropriate.
michael@0 669 *
michael@0 670 * @param aPacket object
michael@0 671 * The incoming packet.
michael@0 672 * @param aIgnoreCompatibility boolean
michael@0 673 * Set true to not pass the packet through the compatibility layer.
michael@0 674 */
michael@0 675 onPacket: function (aPacket, aIgnoreCompatibility=false) {
michael@0 676 let packet = aIgnoreCompatibility
michael@0 677 ? aPacket
michael@0 678 : this.compat.onPacket(aPacket);
michael@0 679
michael@0 680 resolve(packet).then(aPacket => {
michael@0 681 if (!aPacket.from) {
michael@0 682 DevToolsUtils.reportException(
michael@0 683 "onPacket",
michael@0 684 new Error("Server did not specify an actor, dropping packet: " +
michael@0 685 JSON.stringify(aPacket)));
michael@0 686 return;
michael@0 687 }
michael@0 688
michael@0 689 // If we have a registered Front for this actor, let it handle the packet
michael@0 690 // and skip all the rest of this unpleasantness.
michael@0 691 let front = this.getActor(aPacket.from);
michael@0 692 if (front) {
michael@0 693 front.onPacket(aPacket);
michael@0 694 return;
michael@0 695 }
michael@0 696
michael@0 697 let onResponse;
michael@0 698 // See if we have a handler function waiting for a reply from this
michael@0 699 // actor. (Don't count unsolicited notifications or pauses as
michael@0 700 // replies.)
michael@0 701 if (this._activeRequests.has(aPacket.from) &&
michael@0 702 !(aPacket.type in UnsolicitedNotifications) &&
michael@0 703 !(aPacket.type == ThreadStateTypes.paused &&
michael@0 704 aPacket.why.type in UnsolicitedPauses)) {
michael@0 705 onResponse = this._activeRequests.get(aPacket.from);
michael@0 706 this._activeRequests.delete(aPacket.from);
michael@0 707 }
michael@0 708
michael@0 709 // Packets that indicate thread state changes get special treatment.
michael@0 710 if (aPacket.type in ThreadStateTypes &&
michael@0 711 this._threadClients.has(aPacket.from)) {
michael@0 712 this._threadClients.get(aPacket.from)._onThreadState(aPacket);
michael@0 713 }
michael@0 714 // On navigation the server resumes, so the client must resume as well.
michael@0 715 // We achieve that by generating a fake resumption packet that triggers
michael@0 716 // the client's thread state change listeners.
michael@0 717 if (aPacket.type == UnsolicitedNotifications.tabNavigated &&
michael@0 718 this._tabClients.has(aPacket.from) &&
michael@0 719 this._tabClients.get(aPacket.from).thread) {
michael@0 720 let thread = this._tabClients.get(aPacket.from).thread;
michael@0 721 let resumption = { from: thread._actor, type: "resumed" };
michael@0 722 thread._onThreadState(resumption);
michael@0 723 }
michael@0 724 // Only try to notify listeners on events, not responses to requests
michael@0 725 // that lack a packet type.
michael@0 726 if (aPacket.type) {
michael@0 727 this.notify(aPacket.type, aPacket);
michael@0 728 }
michael@0 729
michael@0 730 if (onResponse) {
michael@0 731 onResponse(aPacket);
michael@0 732 }
michael@0 733
michael@0 734 this._sendRequests();
michael@0 735 }, ex => DevToolsUtils.reportException("onPacket handler", ex));
michael@0 736 },
michael@0 737
michael@0 738 /**
michael@0 739 * Called by DebuggerTransport when the underlying stream is closed.
michael@0 740 *
michael@0 741 * @param aStatus nsresult
michael@0 742 * The status code that corresponds to the reason for closing
michael@0 743 * the stream.
michael@0 744 */
michael@0 745 onClosed: function (aStatus) {
michael@0 746 this.notify("closed");
michael@0 747 },
michael@0 748
michael@0 749 /**
michael@0 750 * Actor lifetime management, echos the server's actor pools.
michael@0 751 */
michael@0 752 __pools: null,
michael@0 753 get _pools() {
michael@0 754 if (this.__pools) {
michael@0 755 return this.__pools;
michael@0 756 }
michael@0 757 this.__pools = new Set();
michael@0 758 return this.__pools;
michael@0 759 },
michael@0 760
michael@0 761 addActorPool: function (pool) {
michael@0 762 this._pools.add(pool);
michael@0 763 },
michael@0 764 removeActorPool: function (pool) {
michael@0 765 this._pools.delete(pool);
michael@0 766 },
michael@0 767 getActor: function (actorID) {
michael@0 768 let pool = this.poolFor(actorID);
michael@0 769 return pool ? pool.get(actorID) : null;
michael@0 770 },
michael@0 771
michael@0 772 poolFor: function (actorID) {
michael@0 773 for (let pool of this._pools) {
michael@0 774 if (pool.has(actorID)) return pool;
michael@0 775 }
michael@0 776 return null;
michael@0 777 },
michael@0 778
michael@0 779 /**
michael@0 780 * Currently attached addon.
michael@0 781 */
michael@0 782 activeAddon: null
michael@0 783 }
michael@0 784
michael@0 785 eventSource(DebuggerClient.prototype);
michael@0 786
michael@0 787 // Constants returned by `FeatureCompatibilityShim.onPacketTest`.
michael@0 788 const SUPPORTED = 1;
michael@0 789 const NOT_SUPPORTED = 2;
michael@0 790 const SKIP = 3;
michael@0 791
michael@0 792 /**
michael@0 793 * This object provides an abstraction layer over all of our backwards
michael@0 794 * compatibility, feature detection, and shimming with regards to the remote
michael@0 795 * debugging prototcol.
michael@0 796 *
michael@0 797 * @param aFeatures Array
michael@0 798 * An array of FeatureCompatibilityShim objects
michael@0 799 */
michael@0 800 function ProtocolCompatibility(aClient, aFeatures) {
michael@0 801 this._client = aClient;
michael@0 802 this._featuresWithUnknownSupport = new Set(aFeatures);
michael@0 803 this._featuresWithoutSupport = new Set();
michael@0 804
michael@0 805 this._featureDeferreds = Object.create(null)
michael@0 806 for (let f of aFeatures) {
michael@0 807 this._featureDeferreds[f.name] = defer();
michael@0 808 }
michael@0 809 }
michael@0 810
michael@0 811 ProtocolCompatibility.prototype = {
michael@0 812 /**
michael@0 813 * Returns a promise that resolves to true if the RDP supports the feature,
michael@0 814 * and is rejected otherwise.
michael@0 815 *
michael@0 816 * @param aFeatureName String
michael@0 817 * The name of the feature we are testing.
michael@0 818 */
michael@0 819 supportsFeature: function (aFeatureName) {
michael@0 820 return this._featureDeferreds[aFeatureName].promise;
michael@0 821 },
michael@0 822
michael@0 823 /**
michael@0 824 * Force a feature to be considered unsupported.
michael@0 825 *
michael@0 826 * @param aFeatureName String
michael@0 827 * The name of the feature we are testing.
michael@0 828 */
michael@0 829 rejectFeature: function (aFeatureName) {
michael@0 830 this._featureDeferreds[aFeatureName].reject(false);
michael@0 831 },
michael@0 832
michael@0 833 /**
michael@0 834 * Called for each packet received over the RDP from the server. Tests for
michael@0 835 * protocol features and shims packets to support needed features.
michael@0 836 *
michael@0 837 * @param aPacket Object
michael@0 838 * Packet received over the RDP from the server.
michael@0 839 */
michael@0 840 onPacket: function (aPacket) {
michael@0 841 this._detectFeatures(aPacket);
michael@0 842 return this._shimPacket(aPacket);
michael@0 843 },
michael@0 844
michael@0 845 /**
michael@0 846 * For each of the features we don't know whether the server supports or not,
michael@0 847 * attempt to detect support based on the packet we just received.
michael@0 848 */
michael@0 849 _detectFeatures: function (aPacket) {
michael@0 850 for (let feature of this._featuresWithUnknownSupport) {
michael@0 851 try {
michael@0 852 switch (feature.onPacketTest(aPacket)) {
michael@0 853 case SKIP:
michael@0 854 break;
michael@0 855 case SUPPORTED:
michael@0 856 this._featuresWithUnknownSupport.delete(feature);
michael@0 857 this._featureDeferreds[feature.name].resolve(true);
michael@0 858 break;
michael@0 859 case NOT_SUPPORTED:
michael@0 860 this._featuresWithUnknownSupport.delete(feature);
michael@0 861 this._featuresWithoutSupport.add(feature);
michael@0 862 this.rejectFeature(feature.name);
michael@0 863 break;
michael@0 864 default:
michael@0 865 DevToolsUtils.reportException(
michael@0 866 "PC__detectFeatures",
michael@0 867 new Error("Bad return value from `onPacketTest` for feature '"
michael@0 868 + feature.name + "'"));
michael@0 869 }
michael@0 870 } catch (ex) {
michael@0 871 DevToolsUtils.reportException("PC__detectFeatures", ex);
michael@0 872 }
michael@0 873 }
michael@0 874 },
michael@0 875
michael@0 876 /**
michael@0 877 * Go through each of the features that we know are unsupported by the current
michael@0 878 * server and attempt to shim support.
michael@0 879 */
michael@0 880 _shimPacket: function (aPacket) {
michael@0 881 let extraPackets = [];
michael@0 882
michael@0 883 let loop = function (aFeatures, aPacket) {
michael@0 884 if (aFeatures.length === 0) {
michael@0 885 for (let packet of extraPackets) {
michael@0 886 this._client.onPacket(packet, true);
michael@0 887 }
michael@0 888 return aPacket;
michael@0 889 } else {
michael@0 890 let replacePacket = function (aNewPacket) {
michael@0 891 return aNewPacket;
michael@0 892 };
michael@0 893 let extraPacket = function (aExtraPacket) {
michael@0 894 extraPackets.push(aExtraPacket);
michael@0 895 return aPacket;
michael@0 896 };
michael@0 897 let keepPacket = function () {
michael@0 898 return aPacket;
michael@0 899 };
michael@0 900 let newPacket = aFeatures[0].translatePacket(aPacket,
michael@0 901 replacePacket,
michael@0 902 extraPacket,
michael@0 903 keepPacket);
michael@0 904 return resolve(newPacket).then(loop.bind(null, aFeatures.slice(1)));
michael@0 905 }
michael@0 906 }.bind(this);
michael@0 907
michael@0 908 return loop([f for (f of this._featuresWithoutSupport)],
michael@0 909 aPacket);
michael@0 910 }
michael@0 911 };
michael@0 912
michael@0 913 /**
michael@0 914 * Interface defining what methods a feature compatibility shim should have.
michael@0 915 */
michael@0 916 const FeatureCompatibilityShim = {
michael@0 917 // The name of the feature
michael@0 918 name: null,
michael@0 919
michael@0 920 /**
michael@0 921 * Takes a packet and returns boolean (or promise of boolean) indicating
michael@0 922 * whether the server supports the RDP feature we are possibly shimming.
michael@0 923 */
michael@0 924 onPacketTest: function (aPacket) {
michael@0 925 throw new Error("Not yet implemented");
michael@0 926 },
michael@0 927
michael@0 928 /**
michael@0 929 * Takes a packet actually sent from the server and decides whether to replace
michael@0 930 * it with a new packet, create an extra packet, or keep it.
michael@0 931 */
michael@0 932 translatePacket: function (aPacket, aReplacePacket, aExtraPacket, aKeepPacket) {
michael@0 933 throw new Error("Not yet implemented");
michael@0 934 }
michael@0 935 };
michael@0 936
michael@0 937 /**
michael@0 938 * Creates a tab client for the remote debugging protocol server. This client
michael@0 939 * is a front to the tab actor created in the server side, hiding the protocol
michael@0 940 * details in a traditional JavaScript API.
michael@0 941 *
michael@0 942 * @param aClient DebuggerClient
michael@0 943 * The debugger client parent.
michael@0 944 * @param aForm object
michael@0 945 * The protocol form for this tab.
michael@0 946 */
michael@0 947 function TabClient(aClient, aForm) {
michael@0 948 this.client = aClient;
michael@0 949 this._actor = aForm.from;
michael@0 950 this._threadActor = aForm.threadActor;
michael@0 951 this.javascriptEnabled = aForm.javascriptEnabled;
michael@0 952 this.cacheEnabled = aForm.cacheEnabled;
michael@0 953 this.thread = null;
michael@0 954 this.request = this.client.request;
michael@0 955 this.traits = aForm.traits || {};
michael@0 956 }
michael@0 957
michael@0 958 TabClient.prototype = {
michael@0 959 get actor() { return this._actor },
michael@0 960 get _transport() { return this.client._transport; },
michael@0 961
michael@0 962 /**
michael@0 963 * Attach to a thread actor.
michael@0 964 *
michael@0 965 * @param object aOptions
michael@0 966 * Configuration options.
michael@0 967 * - useSourceMaps: whether to use source maps or not.
michael@0 968 * @param function aOnResponse
michael@0 969 * Called with the response packet and a ThreadClient
michael@0 970 * (which will be undefined on error).
michael@0 971 */
michael@0 972 attachThread: function(aOptions={}, aOnResponse) {
michael@0 973 if (this.thread) {
michael@0 974 setTimeout(() => aOnResponse({}, this.thread), 0);
michael@0 975 return;
michael@0 976 }
michael@0 977
michael@0 978 let packet = {
michael@0 979 to: this._threadActor,
michael@0 980 type: "attach",
michael@0 981 options: aOptions
michael@0 982 };
michael@0 983 this.request(packet, (aResponse) => {
michael@0 984 if (!aResponse.error) {
michael@0 985 this.thread = new ThreadClient(this, this._threadActor);
michael@0 986 this.client._threadClients.set(this._threadActor, this.thread);
michael@0 987 }
michael@0 988 aOnResponse(aResponse, this.thread);
michael@0 989 });
michael@0 990 },
michael@0 991
michael@0 992 /**
michael@0 993 * Detach the client from the tab actor.
michael@0 994 *
michael@0 995 * @param function aOnResponse
michael@0 996 * Called with the response packet.
michael@0 997 */
michael@0 998 detach: DebuggerClient.requester({
michael@0 999 type: "detach"
michael@0 1000 }, {
michael@0 1001 before: function (aPacket) {
michael@0 1002 if (this.thread) {
michael@0 1003 this.thread.detach();
michael@0 1004 }
michael@0 1005 return aPacket;
michael@0 1006 },
michael@0 1007 after: function (aResponse) {
michael@0 1008 this.client._tabClients.delete(this.actor);
michael@0 1009 return aResponse;
michael@0 1010 },
michael@0 1011 telemetry: "TABDETACH"
michael@0 1012 }),
michael@0 1013
michael@0 1014 /**
michael@0 1015 * Reload the page in this tab.
michael@0 1016 */
michael@0 1017 reload: DebuggerClient.requester({
michael@0 1018 type: "reload"
michael@0 1019 }, {
michael@0 1020 telemetry: "RELOAD"
michael@0 1021 }),
michael@0 1022
michael@0 1023 /**
michael@0 1024 * Navigate to another URL.
michael@0 1025 *
michael@0 1026 * @param string url
michael@0 1027 * The URL to navigate to.
michael@0 1028 */
michael@0 1029 navigateTo: DebuggerClient.requester({
michael@0 1030 type: "navigateTo",
michael@0 1031 url: args(0)
michael@0 1032 }, {
michael@0 1033 telemetry: "NAVIGATETO"
michael@0 1034 }),
michael@0 1035
michael@0 1036 /**
michael@0 1037 * Reconfigure the tab actor.
michael@0 1038 *
michael@0 1039 * @param object aOptions
michael@0 1040 * A dictionary object of the new options to use in the tab actor.
michael@0 1041 * @param function aOnResponse
michael@0 1042 * Called with the response packet.
michael@0 1043 */
michael@0 1044 reconfigure: DebuggerClient.requester({
michael@0 1045 type: "reconfigure",
michael@0 1046 options: args(0)
michael@0 1047 }, {
michael@0 1048 telemetry: "RECONFIGURETAB"
michael@0 1049 }),
michael@0 1050 };
michael@0 1051
michael@0 1052 eventSource(TabClient.prototype);
michael@0 1053
michael@0 1054 function AddonClient(aClient, aActor) {
michael@0 1055 this._client = aClient;
michael@0 1056 this._actor = aActor;
michael@0 1057 this.request = this._client.request;
michael@0 1058 }
michael@0 1059
michael@0 1060 AddonClient.prototype = {
michael@0 1061 get actor() { return this._actor; },
michael@0 1062 get _transport() { return this._client._transport; },
michael@0 1063
michael@0 1064 /**
michael@0 1065 * Detach the client from the addon actor.
michael@0 1066 *
michael@0 1067 * @param function aOnResponse
michael@0 1068 * Called with the response packet.
michael@0 1069 */
michael@0 1070 detach: DebuggerClient.requester({
michael@0 1071 type: "detach"
michael@0 1072 }, {
michael@0 1073 after: function(aResponse) {
michael@0 1074 if (this._client.activeAddon === this._client._addonClients[this.actor]) {
michael@0 1075 this._client.activeAddon = null
michael@0 1076 }
michael@0 1077 delete this._client._addonClients[this.actor];
michael@0 1078 return aResponse;
michael@0 1079 },
michael@0 1080 telemetry: "ADDONDETACH"
michael@0 1081 })
michael@0 1082 };
michael@0 1083
michael@0 1084 /**
michael@0 1085 * A RootClient object represents a root actor on the server. Each
michael@0 1086 * DebuggerClient keeps a RootClient instance representing the root actor
michael@0 1087 * for the initial connection; DebuggerClient's 'listTabs' and
michael@0 1088 * 'listChildProcesses' methods forward to that root actor.
michael@0 1089 *
michael@0 1090 * @param aClient object
michael@0 1091 * The client connection to which this actor belongs.
michael@0 1092 * @param aGreeting string
michael@0 1093 * The greeting packet from the root actor we're to represent.
michael@0 1094 *
michael@0 1095 * Properties of a RootClient instance:
michael@0 1096 *
michael@0 1097 * @property actor string
michael@0 1098 * The name of this child's root actor.
michael@0 1099 * @property applicationType string
michael@0 1100 * The application type, as given in the root actor's greeting packet.
michael@0 1101 * @property traits object
michael@0 1102 * The traits object, as given in the root actor's greeting packet.
michael@0 1103 */
michael@0 1104 function RootClient(aClient, aGreeting) {
michael@0 1105 this._client = aClient;
michael@0 1106 this.actor = aGreeting.from;
michael@0 1107 this.applicationType = aGreeting.applicationType;
michael@0 1108 this.traits = aGreeting.traits;
michael@0 1109 }
michael@0 1110
michael@0 1111 RootClient.prototype = {
michael@0 1112 constructor: RootClient,
michael@0 1113
michael@0 1114 /**
michael@0 1115 * List the open tabs.
michael@0 1116 *
michael@0 1117 * @param function aOnResponse
michael@0 1118 * Called with the response packet.
michael@0 1119 */
michael@0 1120 listTabs: DebuggerClient.requester({ type: "listTabs" },
michael@0 1121 { telemetry: "LISTTABS" }),
michael@0 1122
michael@0 1123 /**
michael@0 1124 * List the installed addons.
michael@0 1125 *
michael@0 1126 * @param function aOnResponse
michael@0 1127 * Called with the response packet.
michael@0 1128 */
michael@0 1129 listAddons: DebuggerClient.requester({ type: "listAddons" },
michael@0 1130 { telemetry: "LISTADDONS" }),
michael@0 1131
michael@0 1132 /*
michael@0 1133 * Methods constructed by DebuggerClient.requester require these forwards
michael@0 1134 * on their 'this'.
michael@0 1135 */
michael@0 1136 get _transport() { return this._client._transport; },
michael@0 1137 get request() { return this._client.request; }
michael@0 1138 };
michael@0 1139
michael@0 1140 /**
michael@0 1141 * Creates a thread client for the remote debugging protocol server. This client
michael@0 1142 * is a front to the thread actor created in the server side, hiding the
michael@0 1143 * protocol details in a traditional JavaScript API.
michael@0 1144 *
michael@0 1145 * @param aClient DebuggerClient|TabClient
michael@0 1146 * The parent of the thread (tab for tab-scoped debuggers, DebuggerClient
michael@0 1147 * for chrome debuggers).
michael@0 1148 * @param aActor string
michael@0 1149 * The actor ID for this thread.
michael@0 1150 */
michael@0 1151 function ThreadClient(aClient, aActor) {
michael@0 1152 this._parent = aClient;
michael@0 1153 this.client = aClient instanceof DebuggerClient ? aClient : aClient.client;
michael@0 1154 this._actor = aActor;
michael@0 1155 this._frameCache = [];
michael@0 1156 this._scriptCache = {};
michael@0 1157 this._pauseGrips = {};
michael@0 1158 this._threadGrips = {};
michael@0 1159 this.request = this.client.request;
michael@0 1160 }
michael@0 1161
michael@0 1162 ThreadClient.prototype = {
michael@0 1163 _state: "paused",
michael@0 1164 get state() { return this._state; },
michael@0 1165 get paused() { return this._state === "paused"; },
michael@0 1166
michael@0 1167 _pauseOnExceptions: false,
michael@0 1168 _ignoreCaughtExceptions: false,
michael@0 1169 _pauseOnDOMEvents: null,
michael@0 1170
michael@0 1171 _actor: null,
michael@0 1172 get actor() { return this._actor; },
michael@0 1173
michael@0 1174 get compat() { return this.client.compat; },
michael@0 1175 get _transport() { return this.client._transport; },
michael@0 1176
michael@0 1177 _assertPaused: function (aCommand) {
michael@0 1178 if (!this.paused) {
michael@0 1179 throw Error(aCommand + " command sent while not paused. Currently " + this._state);
michael@0 1180 }
michael@0 1181 },
michael@0 1182
michael@0 1183 /**
michael@0 1184 * Resume a paused thread. If the optional aLimit parameter is present, then
michael@0 1185 * the thread will also pause when that limit is reached.
michael@0 1186 *
michael@0 1187 * @param [optional] object aLimit
michael@0 1188 * An object with a type property set to the appropriate limit (next,
michael@0 1189 * step, or finish) per the remote debugging protocol specification.
michael@0 1190 * Use null to specify no limit.
michael@0 1191 * @param function aOnResponse
michael@0 1192 * Called with the response packet.
michael@0 1193 */
michael@0 1194 _doResume: DebuggerClient.requester({
michael@0 1195 type: "resume",
michael@0 1196 resumeLimit: args(0)
michael@0 1197 }, {
michael@0 1198 before: function (aPacket) {
michael@0 1199 this._assertPaused("resume");
michael@0 1200
michael@0 1201 // Put the client in a tentative "resuming" state so we can prevent
michael@0 1202 // further requests that should only be sent in the paused state.
michael@0 1203 this._state = "resuming";
michael@0 1204
michael@0 1205 if (this._pauseOnExceptions) {
michael@0 1206 aPacket.pauseOnExceptions = this._pauseOnExceptions;
michael@0 1207 }
michael@0 1208 if (this._ignoreCaughtExceptions) {
michael@0 1209 aPacket.ignoreCaughtExceptions = this._ignoreCaughtExceptions;
michael@0 1210 }
michael@0 1211 if (this._pauseOnDOMEvents) {
michael@0 1212 aPacket.pauseOnDOMEvents = this._pauseOnDOMEvents;
michael@0 1213 }
michael@0 1214 return aPacket;
michael@0 1215 },
michael@0 1216 after: function (aResponse) {
michael@0 1217 if (aResponse.error) {
michael@0 1218 // There was an error resuming, back to paused state.
michael@0 1219 this._state = "paused";
michael@0 1220 }
michael@0 1221 return aResponse;
michael@0 1222 },
michael@0 1223 telemetry: "RESUME"
michael@0 1224 }),
michael@0 1225
michael@0 1226 /**
michael@0 1227 * Reconfigure the thread actor.
michael@0 1228 *
michael@0 1229 * @param object aOptions
michael@0 1230 * A dictionary object of the new options to use in the thread actor.
michael@0 1231 * @param function aOnResponse
michael@0 1232 * Called with the response packet.
michael@0 1233 */
michael@0 1234 reconfigure: DebuggerClient.requester({
michael@0 1235 type: "reconfigure",
michael@0 1236 options: args(0)
michael@0 1237 }, {
michael@0 1238 telemetry: "RECONFIGURETHREAD"
michael@0 1239 }),
michael@0 1240
michael@0 1241 /**
michael@0 1242 * Resume a paused thread.
michael@0 1243 */
michael@0 1244 resume: function (aOnResponse) {
michael@0 1245 this._doResume(null, aOnResponse);
michael@0 1246 },
michael@0 1247
michael@0 1248 /**
michael@0 1249 * Step over a function call.
michael@0 1250 *
michael@0 1251 * @param function aOnResponse
michael@0 1252 * Called with the response packet.
michael@0 1253 */
michael@0 1254 stepOver: function (aOnResponse) {
michael@0 1255 this._doResume({ type: "next" }, aOnResponse);
michael@0 1256 },
michael@0 1257
michael@0 1258 /**
michael@0 1259 * Step into a function call.
michael@0 1260 *
michael@0 1261 * @param function aOnResponse
michael@0 1262 * Called with the response packet.
michael@0 1263 */
michael@0 1264 stepIn: function (aOnResponse) {
michael@0 1265 this._doResume({ type: "step" }, aOnResponse);
michael@0 1266 },
michael@0 1267
michael@0 1268 /**
michael@0 1269 * Step out of a function call.
michael@0 1270 *
michael@0 1271 * @param function aOnResponse
michael@0 1272 * Called with the response packet.
michael@0 1273 */
michael@0 1274 stepOut: function (aOnResponse) {
michael@0 1275 this._doResume({ type: "finish" }, aOnResponse);
michael@0 1276 },
michael@0 1277
michael@0 1278 /**
michael@0 1279 * Interrupt a running thread.
michael@0 1280 *
michael@0 1281 * @param function aOnResponse
michael@0 1282 * Called with the response packet.
michael@0 1283 */
michael@0 1284 interrupt: DebuggerClient.requester({
michael@0 1285 type: "interrupt"
michael@0 1286 }, {
michael@0 1287 telemetry: "INTERRUPT"
michael@0 1288 }),
michael@0 1289
michael@0 1290 /**
michael@0 1291 * Enable or disable pausing when an exception is thrown.
michael@0 1292 *
michael@0 1293 * @param boolean aFlag
michael@0 1294 * Enables pausing if true, disables otherwise.
michael@0 1295 * @param function aOnResponse
michael@0 1296 * Called with the response packet.
michael@0 1297 */
michael@0 1298 pauseOnExceptions: function (aPauseOnExceptions,
michael@0 1299 aIgnoreCaughtExceptions,
michael@0 1300 aOnResponse) {
michael@0 1301 this._pauseOnExceptions = aPauseOnExceptions;
michael@0 1302 this._ignoreCaughtExceptions = aIgnoreCaughtExceptions;
michael@0 1303
michael@0 1304 // If the debuggee is paused, we have to send the flag via a reconfigure
michael@0 1305 // request.
michael@0 1306 if (this.paused) {
michael@0 1307 this.reconfigure({
michael@0 1308 pauseOnExceptions: aPauseOnExceptions,
michael@0 1309 ignoreCaughtExceptions: aIgnoreCaughtExceptions
michael@0 1310 }, aOnResponse);
michael@0 1311 return;
michael@0 1312 }
michael@0 1313 // Otherwise send the flag using a standard resume request.
michael@0 1314 this.interrupt(aResponse => {
michael@0 1315 if (aResponse.error) {
michael@0 1316 // Can't continue if pausing failed.
michael@0 1317 aOnResponse(aResponse);
michael@0 1318 return;
michael@0 1319 }
michael@0 1320 this.resume(aOnResponse);
michael@0 1321 });
michael@0 1322 },
michael@0 1323
michael@0 1324 /**
michael@0 1325 * Enable pausing when the specified DOM events are triggered. Disabling
michael@0 1326 * pausing on an event can be realized by calling this method with the updated
michael@0 1327 * array of events that doesn't contain it.
michael@0 1328 *
michael@0 1329 * @param array|string events
michael@0 1330 * An array of strings, representing the DOM event types to pause on,
michael@0 1331 * or "*" to pause on all DOM events. Pass an empty array to
michael@0 1332 * completely disable pausing on DOM events.
michael@0 1333 * @param function onResponse
michael@0 1334 * Called with the response packet in a future turn of the event loop.
michael@0 1335 */
michael@0 1336 pauseOnDOMEvents: function (events, onResponse) {
michael@0 1337 this._pauseOnDOMEvents = events;
michael@0 1338 // If the debuggee is paused, the value of the array will be communicated in
michael@0 1339 // the next resumption. Otherwise we have to force a pause in order to send
michael@0 1340 // the array.
michael@0 1341 if (this.paused) {
michael@0 1342 setTimeout(() => onResponse({}), 0);
michael@0 1343 return;
michael@0 1344 }
michael@0 1345 this.interrupt(response => {
michael@0 1346 // Can't continue if pausing failed.
michael@0 1347 if (response.error) {
michael@0 1348 onResponse(response);
michael@0 1349 return;
michael@0 1350 }
michael@0 1351 this.resume(onResponse);
michael@0 1352 });
michael@0 1353 },
michael@0 1354
michael@0 1355 /**
michael@0 1356 * Send a clientEvaluate packet to the debuggee. Response
michael@0 1357 * will be a resume packet.
michael@0 1358 *
michael@0 1359 * @param string aFrame
michael@0 1360 * The actor ID of the frame where the evaluation should take place.
michael@0 1361 * @param string aExpression
michael@0 1362 * The expression that will be evaluated in the scope of the frame
michael@0 1363 * above.
michael@0 1364 * @param function aOnResponse
michael@0 1365 * Called with the response packet.
michael@0 1366 */
michael@0 1367 eval: DebuggerClient.requester({
michael@0 1368 type: "clientEvaluate",
michael@0 1369 frame: args(0),
michael@0 1370 expression: args(1)
michael@0 1371 }, {
michael@0 1372 before: function (aPacket) {
michael@0 1373 this._assertPaused("eval");
michael@0 1374 // Put the client in a tentative "resuming" state so we can prevent
michael@0 1375 // further requests that should only be sent in the paused state.
michael@0 1376 this._state = "resuming";
michael@0 1377 return aPacket;
michael@0 1378 },
michael@0 1379 after: function (aResponse) {
michael@0 1380 if (aResponse.error) {
michael@0 1381 // There was an error resuming, back to paused state.
michael@0 1382 this._state = "paused";
michael@0 1383 }
michael@0 1384 return aResponse;
michael@0 1385 },
michael@0 1386 telemetry: "CLIENTEVALUATE"
michael@0 1387 }),
michael@0 1388
michael@0 1389 /**
michael@0 1390 * Detach from the thread actor.
michael@0 1391 *
michael@0 1392 * @param function aOnResponse
michael@0 1393 * Called with the response packet.
michael@0 1394 */
michael@0 1395 detach: DebuggerClient.requester({
michael@0 1396 type: "detach"
michael@0 1397 }, {
michael@0 1398 after: function (aResponse) {
michael@0 1399 this.client._threadClients.delete(this.actor);
michael@0 1400 this._parent.thread = null;
michael@0 1401 return aResponse;
michael@0 1402 },
michael@0 1403 telemetry: "THREADDETACH"
michael@0 1404 }),
michael@0 1405
michael@0 1406 /**
michael@0 1407 * Request to set a breakpoint in the specified location.
michael@0 1408 *
michael@0 1409 * @param object aLocation
michael@0 1410 * The source location object where the breakpoint will be set.
michael@0 1411 * @param function aOnResponse
michael@0 1412 * Called with the thread's response.
michael@0 1413 */
michael@0 1414 setBreakpoint: function ({ url, line, column, condition }, aOnResponse) {
michael@0 1415 // A helper function that sets the breakpoint.
michael@0 1416 let doSetBreakpoint = function (aCallback) {
michael@0 1417 const location = {
michael@0 1418 url: url,
michael@0 1419 line: line,
michael@0 1420 column: column
michael@0 1421 };
michael@0 1422
michael@0 1423 let packet = {
michael@0 1424 to: this._actor,
michael@0 1425 type: "setBreakpoint",
michael@0 1426 location: location,
michael@0 1427 condition: condition
michael@0 1428 };
michael@0 1429 this.client.request(packet, function (aResponse) {
michael@0 1430 // Ignoring errors, since the user may be setting a breakpoint in a
michael@0 1431 // dead script that will reappear on a page reload.
michael@0 1432 if (aOnResponse) {
michael@0 1433 let root = this.client.mainRoot;
michael@0 1434 let bpClient = new BreakpointClient(
michael@0 1435 this.client,
michael@0 1436 aResponse.actor,
michael@0 1437 location,
michael@0 1438 root.traits.conditionalBreakpoints ? condition : undefined
michael@0 1439 );
michael@0 1440 aOnResponse(aResponse, bpClient);
michael@0 1441 }
michael@0 1442 if (aCallback) {
michael@0 1443 aCallback();
michael@0 1444 }
michael@0 1445 }.bind(this));
michael@0 1446 }.bind(this);
michael@0 1447
michael@0 1448 // If the debuggee is paused, just set the breakpoint.
michael@0 1449 if (this.paused) {
michael@0 1450 doSetBreakpoint();
michael@0 1451 return;
michael@0 1452 }
michael@0 1453 // Otherwise, force a pause in order to set the breakpoint.
michael@0 1454 this.interrupt(function (aResponse) {
michael@0 1455 if (aResponse.error) {
michael@0 1456 // Can't set the breakpoint if pausing failed.
michael@0 1457 aOnResponse(aResponse);
michael@0 1458 return;
michael@0 1459 }
michael@0 1460 doSetBreakpoint(this.resume.bind(this));
michael@0 1461 }.bind(this));
michael@0 1462 },
michael@0 1463
michael@0 1464 /**
michael@0 1465 * Release multiple thread-lifetime object actors. If any pause-lifetime
michael@0 1466 * actors are included in the request, a |notReleasable| error will return,
michael@0 1467 * but all the thread-lifetime ones will have been released.
michael@0 1468 *
michael@0 1469 * @param array actors
michael@0 1470 * An array with actor IDs to release.
michael@0 1471 */
michael@0 1472 releaseMany: DebuggerClient.requester({
michael@0 1473 type: "releaseMany",
michael@0 1474 actors: args(0),
michael@0 1475 }, {
michael@0 1476 telemetry: "RELEASEMANY"
michael@0 1477 }),
michael@0 1478
michael@0 1479 /**
michael@0 1480 * Promote multiple pause-lifetime object actors to thread-lifetime ones.
michael@0 1481 *
michael@0 1482 * @param array actors
michael@0 1483 * An array with actor IDs to promote.
michael@0 1484 */
michael@0 1485 threadGrips: DebuggerClient.requester({
michael@0 1486 type: "threadGrips",
michael@0 1487 actors: args(0)
michael@0 1488 }, {
michael@0 1489 telemetry: "THREADGRIPS"
michael@0 1490 }),
michael@0 1491
michael@0 1492 /**
michael@0 1493 * Return the event listeners defined on the page.
michael@0 1494 *
michael@0 1495 * @param aOnResponse Function
michael@0 1496 * Called with the thread's response.
michael@0 1497 */
michael@0 1498 eventListeners: DebuggerClient.requester({
michael@0 1499 type: "eventListeners"
michael@0 1500 }, {
michael@0 1501 telemetry: "EVENTLISTENERS"
michael@0 1502 }),
michael@0 1503
michael@0 1504 /**
michael@0 1505 * Request the loaded sources for the current thread.
michael@0 1506 *
michael@0 1507 * @param aOnResponse Function
michael@0 1508 * Called with the thread's response.
michael@0 1509 */
michael@0 1510 getSources: DebuggerClient.requester({
michael@0 1511 type: "sources"
michael@0 1512 }, {
michael@0 1513 telemetry: "SOURCES"
michael@0 1514 }),
michael@0 1515
michael@0 1516 _doInterrupted: function (aAction, aError) {
michael@0 1517 if (this.paused) {
michael@0 1518 aAction();
michael@0 1519 return;
michael@0 1520 }
michael@0 1521 this.interrupt(function (aResponse) {
michael@0 1522 if (aResponse) {
michael@0 1523 aError(aResponse);
michael@0 1524 return;
michael@0 1525 }
michael@0 1526 aAction();
michael@0 1527 this.resume();
michael@0 1528 }.bind(this));
michael@0 1529 },
michael@0 1530
michael@0 1531 /**
michael@0 1532 * Clear the thread's source script cache. A scriptscleared event
michael@0 1533 * will be sent.
michael@0 1534 */
michael@0 1535 _clearScripts: function () {
michael@0 1536 if (Object.keys(this._scriptCache).length > 0) {
michael@0 1537 this._scriptCache = {}
michael@0 1538 this.notify("scriptscleared");
michael@0 1539 }
michael@0 1540 },
michael@0 1541
michael@0 1542 /**
michael@0 1543 * Request frames from the callstack for the current thread.
michael@0 1544 *
michael@0 1545 * @param aStart integer
michael@0 1546 * The number of the youngest stack frame to return (the youngest
michael@0 1547 * frame is 0).
michael@0 1548 * @param aCount integer
michael@0 1549 * The maximum number of frames to return, or null to return all
michael@0 1550 * frames.
michael@0 1551 * @param aOnResponse function
michael@0 1552 * Called with the thread's response.
michael@0 1553 */
michael@0 1554 getFrames: DebuggerClient.requester({
michael@0 1555 type: "frames",
michael@0 1556 start: args(0),
michael@0 1557 count: args(1)
michael@0 1558 }, {
michael@0 1559 telemetry: "FRAMES"
michael@0 1560 }),
michael@0 1561
michael@0 1562 /**
michael@0 1563 * An array of cached frames. Clients can observe the framesadded and
michael@0 1564 * framescleared event to keep up to date on changes to this cache,
michael@0 1565 * and can fill it using the fillFrames method.
michael@0 1566 */
michael@0 1567 get cachedFrames() { return this._frameCache; },
michael@0 1568
michael@0 1569 /**
michael@0 1570 * true if there are more stack frames available on the server.
michael@0 1571 */
michael@0 1572 get moreFrames() {
michael@0 1573 return this.paused && (!this._frameCache || this._frameCache.length == 0
michael@0 1574 || !this._frameCache[this._frameCache.length - 1].oldest);
michael@0 1575 },
michael@0 1576
michael@0 1577 /**
michael@0 1578 * Ensure that at least aTotal stack frames have been loaded in the
michael@0 1579 * ThreadClient's stack frame cache. A framesadded event will be
michael@0 1580 * sent when the stack frame cache is updated.
michael@0 1581 *
michael@0 1582 * @param aTotal number
michael@0 1583 * The minimum number of stack frames to be included.
michael@0 1584 * @param aCallback function
michael@0 1585 * Optional callback function called when frames have been loaded
michael@0 1586 * @returns true if a framesadded notification should be expected.
michael@0 1587 */
michael@0 1588 fillFrames: function (aTotal, aCallback=noop) {
michael@0 1589 this._assertPaused("fillFrames");
michael@0 1590 if (this._frameCache.length >= aTotal) {
michael@0 1591 return false;
michael@0 1592 }
michael@0 1593
michael@0 1594 let numFrames = this._frameCache.length;
michael@0 1595
michael@0 1596 this.getFrames(numFrames, aTotal - numFrames, (aResponse) => {
michael@0 1597 if (aResponse.error) {
michael@0 1598 aCallback(aResponse);
michael@0 1599 return;
michael@0 1600 }
michael@0 1601
michael@0 1602 for each (let frame in aResponse.frames) {
michael@0 1603 this._frameCache[frame.depth] = frame;
michael@0 1604 }
michael@0 1605
michael@0 1606 // If we got as many frames as we asked for, there might be more
michael@0 1607 // frames available.
michael@0 1608 this.notify("framesadded");
michael@0 1609
michael@0 1610 aCallback(aResponse);
michael@0 1611 });
michael@0 1612
michael@0 1613 return true;
michael@0 1614 },
michael@0 1615
michael@0 1616 /**
michael@0 1617 * Clear the thread's stack frame cache. A framescleared event
michael@0 1618 * will be sent.
michael@0 1619 */
michael@0 1620 _clearFrames: function () {
michael@0 1621 if (this._frameCache.length > 0) {
michael@0 1622 this._frameCache = [];
michael@0 1623 this.notify("framescleared");
michael@0 1624 }
michael@0 1625 },
michael@0 1626
michael@0 1627 /**
michael@0 1628 * Return a ObjectClient object for the given object grip.
michael@0 1629 *
michael@0 1630 * @param aGrip object
michael@0 1631 * A pause-lifetime object grip returned by the protocol.
michael@0 1632 */
michael@0 1633 pauseGrip: function (aGrip) {
michael@0 1634 if (aGrip.actor in this._pauseGrips) {
michael@0 1635 return this._pauseGrips[aGrip.actor];
michael@0 1636 }
michael@0 1637
michael@0 1638 let client = new ObjectClient(this.client, aGrip);
michael@0 1639 this._pauseGrips[aGrip.actor] = client;
michael@0 1640 return client;
michael@0 1641 },
michael@0 1642
michael@0 1643 /**
michael@0 1644 * Get or create a long string client, checking the grip client cache if it
michael@0 1645 * already exists.
michael@0 1646 *
michael@0 1647 * @param aGrip Object
michael@0 1648 * The long string grip returned by the protocol.
michael@0 1649 * @param aGripCacheName String
michael@0 1650 * The property name of the grip client cache to check for existing
michael@0 1651 * clients in.
michael@0 1652 */
michael@0 1653 _longString: function (aGrip, aGripCacheName) {
michael@0 1654 if (aGrip.actor in this[aGripCacheName]) {
michael@0 1655 return this[aGripCacheName][aGrip.actor];
michael@0 1656 }
michael@0 1657
michael@0 1658 let client = new LongStringClient(this.client, aGrip);
michael@0 1659 this[aGripCacheName][aGrip.actor] = client;
michael@0 1660 return client;
michael@0 1661 },
michael@0 1662
michael@0 1663 /**
michael@0 1664 * Return an instance of LongStringClient for the given long string grip that
michael@0 1665 * is scoped to the current pause.
michael@0 1666 *
michael@0 1667 * @param aGrip Object
michael@0 1668 * The long string grip returned by the protocol.
michael@0 1669 */
michael@0 1670 pauseLongString: function (aGrip) {
michael@0 1671 return this._longString(aGrip, "_pauseGrips");
michael@0 1672 },
michael@0 1673
michael@0 1674 /**
michael@0 1675 * Return an instance of LongStringClient for the given long string grip that
michael@0 1676 * is scoped to the thread lifetime.
michael@0 1677 *
michael@0 1678 * @param aGrip Object
michael@0 1679 * The long string grip returned by the protocol.
michael@0 1680 */
michael@0 1681 threadLongString: function (aGrip) {
michael@0 1682 return this._longString(aGrip, "_threadGrips");
michael@0 1683 },
michael@0 1684
michael@0 1685 /**
michael@0 1686 * Clear and invalidate all the grip clients from the given cache.
michael@0 1687 *
michael@0 1688 * @param aGripCacheName
michael@0 1689 * The property name of the grip cache we want to clear.
michael@0 1690 */
michael@0 1691 _clearObjectClients: function (aGripCacheName) {
michael@0 1692 for each (let grip in this[aGripCacheName]) {
michael@0 1693 grip.valid = false;
michael@0 1694 }
michael@0 1695 this[aGripCacheName] = {};
michael@0 1696 },
michael@0 1697
michael@0 1698 /**
michael@0 1699 * Invalidate pause-lifetime grip clients and clear the list of current grip
michael@0 1700 * clients.
michael@0 1701 */
michael@0 1702 _clearPauseGrips: function () {
michael@0 1703 this._clearObjectClients("_pauseGrips");
michael@0 1704 },
michael@0 1705
michael@0 1706 /**
michael@0 1707 * Invalidate thread-lifetime grip clients and clear the list of current grip
michael@0 1708 * clients.
michael@0 1709 */
michael@0 1710 _clearThreadGrips: function () {
michael@0 1711 this._clearObjectClients("_threadGrips");
michael@0 1712 },
michael@0 1713
michael@0 1714 /**
michael@0 1715 * Handle thread state change by doing necessary cleanup and notifying all
michael@0 1716 * registered listeners.
michael@0 1717 */
michael@0 1718 _onThreadState: function (aPacket) {
michael@0 1719 this._state = ThreadStateTypes[aPacket.type];
michael@0 1720 this._clearFrames();
michael@0 1721 this._clearPauseGrips();
michael@0 1722 aPacket.type === ThreadStateTypes.detached && this._clearThreadGrips();
michael@0 1723 this.client._eventsEnabled && this.notify(aPacket.type, aPacket);
michael@0 1724 },
michael@0 1725
michael@0 1726 /**
michael@0 1727 * Return an EnvironmentClient instance for the given environment actor form.
michael@0 1728 */
michael@0 1729 environment: function (aForm) {
michael@0 1730 return new EnvironmentClient(this.client, aForm);
michael@0 1731 },
michael@0 1732
michael@0 1733 /**
michael@0 1734 * Return an instance of SourceClient for the given source actor form.
michael@0 1735 */
michael@0 1736 source: function (aForm) {
michael@0 1737 if (aForm.actor in this._threadGrips) {
michael@0 1738 return this._threadGrips[aForm.actor];
michael@0 1739 }
michael@0 1740
michael@0 1741 return this._threadGrips[aForm.actor] = new SourceClient(this, aForm);
michael@0 1742 },
michael@0 1743
michael@0 1744 /**
michael@0 1745 * Request the prototype and own properties of mutlipleObjects.
michael@0 1746 *
michael@0 1747 * @param aOnResponse function
michael@0 1748 * Called with the request's response.
michael@0 1749 * @param actors [string]
michael@0 1750 * List of actor ID of the queried objects.
michael@0 1751 */
michael@0 1752 getPrototypesAndProperties: DebuggerClient.requester({
michael@0 1753 type: "prototypesAndProperties",
michael@0 1754 actors: args(0)
michael@0 1755 }, {
michael@0 1756 telemetry: "PROTOTYPESANDPROPERTIES"
michael@0 1757 })
michael@0 1758 };
michael@0 1759
michael@0 1760 eventSource(ThreadClient.prototype);
michael@0 1761
michael@0 1762 /**
michael@0 1763 * Creates a tracing profiler client for the remote debugging protocol
michael@0 1764 * server. This client is a front to the trace actor created on the
michael@0 1765 * server side, hiding the protocol details in a traditional
michael@0 1766 * JavaScript API.
michael@0 1767 *
michael@0 1768 * @param aClient DebuggerClient
michael@0 1769 * The debugger client parent.
michael@0 1770 * @param aActor string
michael@0 1771 * The actor ID for this thread.
michael@0 1772 */
michael@0 1773 function TraceClient(aClient, aActor) {
michael@0 1774 this._client = aClient;
michael@0 1775 this._actor = aActor;
michael@0 1776 this._activeTraces = new Set();
michael@0 1777 this._waitingPackets = new Map();
michael@0 1778 this._expectedPacket = 0;
michael@0 1779 this.request = this._client.request;
michael@0 1780 }
michael@0 1781
michael@0 1782 TraceClient.prototype = {
michael@0 1783 get actor() { return this._actor; },
michael@0 1784 get tracing() { return this._activeTraces.size > 0; },
michael@0 1785
michael@0 1786 get _transport() { return this._client._transport; },
michael@0 1787
michael@0 1788 /**
michael@0 1789 * Detach from the trace actor.
michael@0 1790 */
michael@0 1791 detach: DebuggerClient.requester({
michael@0 1792 type: "detach"
michael@0 1793 }, {
michael@0 1794 after: function (aResponse) {
michael@0 1795 this._client._tracerClients.delete(this.actor);
michael@0 1796 return aResponse;
michael@0 1797 },
michael@0 1798 telemetry: "TRACERDETACH"
michael@0 1799 }),
michael@0 1800
michael@0 1801 /**
michael@0 1802 * Start a new trace.
michael@0 1803 *
michael@0 1804 * @param aTrace [string]
michael@0 1805 * An array of trace types to be recorded by the new trace.
michael@0 1806 *
michael@0 1807 * @param aName string
michael@0 1808 * The name of the new trace.
michael@0 1809 *
michael@0 1810 * @param aOnResponse function
michael@0 1811 * Called with the request's response.
michael@0 1812 */
michael@0 1813 startTrace: DebuggerClient.requester({
michael@0 1814 type: "startTrace",
michael@0 1815 name: args(1),
michael@0 1816 trace: args(0)
michael@0 1817 }, {
michael@0 1818 after: function (aResponse) {
michael@0 1819 if (aResponse.error) {
michael@0 1820 return aResponse;
michael@0 1821 }
michael@0 1822
michael@0 1823 if (!this.tracing) {
michael@0 1824 this._waitingPackets.clear();
michael@0 1825 this._expectedPacket = 0;
michael@0 1826 }
michael@0 1827 this._activeTraces.add(aResponse.name);
michael@0 1828
michael@0 1829 return aResponse;
michael@0 1830 },
michael@0 1831 telemetry: "STARTTRACE"
michael@0 1832 }),
michael@0 1833
michael@0 1834 /**
michael@0 1835 * End a trace. If a name is provided, stop the named
michael@0 1836 * trace. Otherwise, stop the most recently started trace.
michael@0 1837 *
michael@0 1838 * @param aName string
michael@0 1839 * The name of the trace to stop.
michael@0 1840 *
michael@0 1841 * @param aOnResponse function
michael@0 1842 * Called with the request's response.
michael@0 1843 */
michael@0 1844 stopTrace: DebuggerClient.requester({
michael@0 1845 type: "stopTrace",
michael@0 1846 name: args(0)
michael@0 1847 }, {
michael@0 1848 after: function (aResponse) {
michael@0 1849 if (aResponse.error) {
michael@0 1850 return aResponse;
michael@0 1851 }
michael@0 1852
michael@0 1853 this._activeTraces.delete(aResponse.name);
michael@0 1854
michael@0 1855 return aResponse;
michael@0 1856 },
michael@0 1857 telemetry: "STOPTRACE"
michael@0 1858 })
michael@0 1859 };
michael@0 1860
michael@0 1861 /**
michael@0 1862 * Grip clients are used to retrieve information about the relevant object.
michael@0 1863 *
michael@0 1864 * @param aClient DebuggerClient
michael@0 1865 * The debugger client parent.
michael@0 1866 * @param aGrip object
michael@0 1867 * A pause-lifetime object grip returned by the protocol.
michael@0 1868 */
michael@0 1869 function ObjectClient(aClient, aGrip)
michael@0 1870 {
michael@0 1871 this._grip = aGrip;
michael@0 1872 this._client = aClient;
michael@0 1873 this.request = this._client.request;
michael@0 1874 }
michael@0 1875
michael@0 1876 ObjectClient.prototype = {
michael@0 1877 get actor() { return this._grip.actor },
michael@0 1878 get _transport() { return this._client._transport; },
michael@0 1879
michael@0 1880 valid: true,
michael@0 1881
michael@0 1882 get isFrozen() this._grip.frozen,
michael@0 1883 get isSealed() this._grip.sealed,
michael@0 1884 get isExtensible() this._grip.extensible,
michael@0 1885
michael@0 1886 getDefinitionSite: DebuggerClient.requester({
michael@0 1887 type: "definitionSite"
michael@0 1888 }, {
michael@0 1889 before: function (aPacket) {
michael@0 1890 if (this._grip.class != "Function") {
michael@0 1891 throw new Error("getDefinitionSite is only valid for function grips.");
michael@0 1892 }
michael@0 1893 return aPacket;
michael@0 1894 }
michael@0 1895 }),
michael@0 1896
michael@0 1897 /**
michael@0 1898 * Request the names of a function's formal parameters.
michael@0 1899 *
michael@0 1900 * @param aOnResponse function
michael@0 1901 * Called with an object of the form:
michael@0 1902 * { parameterNames:[<parameterName>, ...] }
michael@0 1903 * where each <parameterName> is the name of a parameter.
michael@0 1904 */
michael@0 1905 getParameterNames: DebuggerClient.requester({
michael@0 1906 type: "parameterNames"
michael@0 1907 }, {
michael@0 1908 before: function (aPacket) {
michael@0 1909 if (this._grip["class"] !== "Function") {
michael@0 1910 throw new Error("getParameterNames is only valid for function grips.");
michael@0 1911 }
michael@0 1912 return aPacket;
michael@0 1913 },
michael@0 1914 telemetry: "PARAMETERNAMES"
michael@0 1915 }),
michael@0 1916
michael@0 1917 /**
michael@0 1918 * Request the names of the properties defined on the object and not its
michael@0 1919 * prototype.
michael@0 1920 *
michael@0 1921 * @param aOnResponse function Called with the request's response.
michael@0 1922 */
michael@0 1923 getOwnPropertyNames: DebuggerClient.requester({
michael@0 1924 type: "ownPropertyNames"
michael@0 1925 }, {
michael@0 1926 telemetry: "OWNPROPERTYNAMES"
michael@0 1927 }),
michael@0 1928
michael@0 1929 /**
michael@0 1930 * Request the prototype and own properties of the object.
michael@0 1931 *
michael@0 1932 * @param aOnResponse function Called with the request's response.
michael@0 1933 */
michael@0 1934 getPrototypeAndProperties: DebuggerClient.requester({
michael@0 1935 type: "prototypeAndProperties"
michael@0 1936 }, {
michael@0 1937 telemetry: "PROTOTYPEANDPROPERTIES"
michael@0 1938 }),
michael@0 1939
michael@0 1940 /**
michael@0 1941 * Request the property descriptor of the object's specified property.
michael@0 1942 *
michael@0 1943 * @param aName string The name of the requested property.
michael@0 1944 * @param aOnResponse function Called with the request's response.
michael@0 1945 */
michael@0 1946 getProperty: DebuggerClient.requester({
michael@0 1947 type: "property",
michael@0 1948 name: args(0)
michael@0 1949 }, {
michael@0 1950 telemetry: "PROPERTY"
michael@0 1951 }),
michael@0 1952
michael@0 1953 /**
michael@0 1954 * Request the prototype of the object.
michael@0 1955 *
michael@0 1956 * @param aOnResponse function Called with the request's response.
michael@0 1957 */
michael@0 1958 getPrototype: DebuggerClient.requester({
michael@0 1959 type: "prototype"
michael@0 1960 }, {
michael@0 1961 telemetry: "PROTOTYPE"
michael@0 1962 }),
michael@0 1963
michael@0 1964 /**
michael@0 1965 * Request the display string of the object.
michael@0 1966 *
michael@0 1967 * @param aOnResponse function Called with the request's response.
michael@0 1968 */
michael@0 1969 getDisplayString: DebuggerClient.requester({
michael@0 1970 type: "displayString"
michael@0 1971 }, {
michael@0 1972 telemetry: "DISPLAYSTRING"
michael@0 1973 }),
michael@0 1974
michael@0 1975 /**
michael@0 1976 * Request the scope of the object.
michael@0 1977 *
michael@0 1978 * @param aOnResponse function Called with the request's response.
michael@0 1979 */
michael@0 1980 getScope: DebuggerClient.requester({
michael@0 1981 type: "scope"
michael@0 1982 }, {
michael@0 1983 before: function (aPacket) {
michael@0 1984 if (this._grip.class !== "Function") {
michael@0 1985 throw new Error("scope is only valid for function grips.");
michael@0 1986 }
michael@0 1987 return aPacket;
michael@0 1988 },
michael@0 1989 telemetry: "SCOPE"
michael@0 1990 })
michael@0 1991 };
michael@0 1992
michael@0 1993 /**
michael@0 1994 * A LongStringClient provides a way to access "very long" strings from the
michael@0 1995 * debugger server.
michael@0 1996 *
michael@0 1997 * @param aClient DebuggerClient
michael@0 1998 * The debugger client parent.
michael@0 1999 * @param aGrip Object
michael@0 2000 * A pause-lifetime long string grip returned by the protocol.
michael@0 2001 */
michael@0 2002 function LongStringClient(aClient, aGrip) {
michael@0 2003 this._grip = aGrip;
michael@0 2004 this._client = aClient;
michael@0 2005 this.request = this._client.request;
michael@0 2006 }
michael@0 2007
michael@0 2008 LongStringClient.prototype = {
michael@0 2009 get actor() { return this._grip.actor; },
michael@0 2010 get length() { return this._grip.length; },
michael@0 2011 get initial() { return this._grip.initial; },
michael@0 2012 get _transport() { return this._client._transport; },
michael@0 2013
michael@0 2014 valid: true,
michael@0 2015
michael@0 2016 /**
michael@0 2017 * Get the substring of this LongString from aStart to aEnd.
michael@0 2018 *
michael@0 2019 * @param aStart Number
michael@0 2020 * The starting index.
michael@0 2021 * @param aEnd Number
michael@0 2022 * The ending index.
michael@0 2023 * @param aCallback Function
michael@0 2024 * The function called when we receive the substring.
michael@0 2025 */
michael@0 2026 substring: DebuggerClient.requester({
michael@0 2027 type: "substring",
michael@0 2028 start: args(0),
michael@0 2029 end: args(1)
michael@0 2030 }, {
michael@0 2031 telemetry: "SUBSTRING"
michael@0 2032 }),
michael@0 2033 };
michael@0 2034
michael@0 2035 /**
michael@0 2036 * A SourceClient provides a way to access the source text of a script.
michael@0 2037 *
michael@0 2038 * @param aClient ThreadClient
michael@0 2039 * The thread client parent.
michael@0 2040 * @param aForm Object
michael@0 2041 * The form sent across the remote debugging protocol.
michael@0 2042 */
michael@0 2043 function SourceClient(aClient, aForm) {
michael@0 2044 this._form = aForm;
michael@0 2045 this._isBlackBoxed = aForm.isBlackBoxed;
michael@0 2046 this._isPrettyPrinted = aForm.isPrettyPrinted;
michael@0 2047 this._activeThread = aClient;
michael@0 2048 this._client = aClient.client;
michael@0 2049 }
michael@0 2050
michael@0 2051 SourceClient.prototype = {
michael@0 2052 get _transport() this._client._transport,
michael@0 2053 get isBlackBoxed() this._isBlackBoxed,
michael@0 2054 get isPrettyPrinted() this._isPrettyPrinted,
michael@0 2055 get actor() this._form.actor,
michael@0 2056 get request() this._client.request,
michael@0 2057 get url() this._form.url,
michael@0 2058
michael@0 2059 /**
michael@0 2060 * Black box this SourceClient's source.
michael@0 2061 *
michael@0 2062 * @param aCallback Function
michael@0 2063 * The callback function called when we receive the response from the server.
michael@0 2064 */
michael@0 2065 blackBox: DebuggerClient.requester({
michael@0 2066 type: "blackbox"
michael@0 2067 }, {
michael@0 2068 telemetry: "BLACKBOX",
michael@0 2069 after: function (aResponse) {
michael@0 2070 if (!aResponse.error) {
michael@0 2071 this._isBlackBoxed = true;
michael@0 2072 if (this._activeThread) {
michael@0 2073 this._activeThread.notify("blackboxchange", this);
michael@0 2074 }
michael@0 2075 }
michael@0 2076 return aResponse;
michael@0 2077 }
michael@0 2078 }),
michael@0 2079
michael@0 2080 /**
michael@0 2081 * Un-black box this SourceClient's source.
michael@0 2082 *
michael@0 2083 * @param aCallback Function
michael@0 2084 * The callback function called when we receive the response from the server.
michael@0 2085 */
michael@0 2086 unblackBox: DebuggerClient.requester({
michael@0 2087 type: "unblackbox"
michael@0 2088 }, {
michael@0 2089 telemetry: "UNBLACKBOX",
michael@0 2090 after: function (aResponse) {
michael@0 2091 if (!aResponse.error) {
michael@0 2092 this._isBlackBoxed = false;
michael@0 2093 if (this._activeThread) {
michael@0 2094 this._activeThread.notify("blackboxchange", this);
michael@0 2095 }
michael@0 2096 }
michael@0 2097 return aResponse;
michael@0 2098 }
michael@0 2099 }),
michael@0 2100
michael@0 2101 /**
michael@0 2102 * Get a long string grip for this SourceClient's source.
michael@0 2103 */
michael@0 2104 source: function (aCallback) {
michael@0 2105 let packet = {
michael@0 2106 to: this._form.actor,
michael@0 2107 type: "source"
michael@0 2108 };
michael@0 2109 this._client.request(packet, aResponse => {
michael@0 2110 this._onSourceResponse(aResponse, aCallback)
michael@0 2111 });
michael@0 2112 },
michael@0 2113
michael@0 2114 /**
michael@0 2115 * Pretty print this source's text.
michael@0 2116 */
michael@0 2117 prettyPrint: function (aIndent, aCallback) {
michael@0 2118 const packet = {
michael@0 2119 to: this._form.actor,
michael@0 2120 type: "prettyPrint",
michael@0 2121 indent: aIndent
michael@0 2122 };
michael@0 2123 this._client.request(packet, aResponse => {
michael@0 2124 if (!aResponse.error) {
michael@0 2125 this._isPrettyPrinted = true;
michael@0 2126 this._activeThread._clearFrames();
michael@0 2127 this._activeThread.notify("prettyprintchange", this);
michael@0 2128 }
michael@0 2129 this._onSourceResponse(aResponse, aCallback);
michael@0 2130 });
michael@0 2131 },
michael@0 2132
michael@0 2133 /**
michael@0 2134 * Stop pretty printing this source's text.
michael@0 2135 */
michael@0 2136 disablePrettyPrint: function (aCallback) {
michael@0 2137 const packet = {
michael@0 2138 to: this._form.actor,
michael@0 2139 type: "disablePrettyPrint"
michael@0 2140 };
michael@0 2141 this._client.request(packet, aResponse => {
michael@0 2142 if (!aResponse.error) {
michael@0 2143 this._isPrettyPrinted = false;
michael@0 2144 this._activeThread._clearFrames();
michael@0 2145 this._activeThread.notify("prettyprintchange", this);
michael@0 2146 }
michael@0 2147 this._onSourceResponse(aResponse, aCallback);
michael@0 2148 });
michael@0 2149 },
michael@0 2150
michael@0 2151 _onSourceResponse: function (aResponse, aCallback) {
michael@0 2152 if (aResponse.error) {
michael@0 2153 aCallback(aResponse);
michael@0 2154 return;
michael@0 2155 }
michael@0 2156
michael@0 2157 if (typeof aResponse.source === "string") {
michael@0 2158 aCallback(aResponse);
michael@0 2159 return;
michael@0 2160 }
michael@0 2161
michael@0 2162 let { contentType, source } = aResponse;
michael@0 2163 let longString = this._activeThread.threadLongString(source);
michael@0 2164 longString.substring(0, longString.length, function (aResponse) {
michael@0 2165 if (aResponse.error) {
michael@0 2166 aCallback(aResponse);
michael@0 2167 return;
michael@0 2168 }
michael@0 2169
michael@0 2170 aCallback({
michael@0 2171 source: aResponse.substring,
michael@0 2172 contentType: contentType
michael@0 2173 });
michael@0 2174 });
michael@0 2175 }
michael@0 2176 };
michael@0 2177
michael@0 2178 /**
michael@0 2179 * Breakpoint clients are used to remove breakpoints that are no longer used.
michael@0 2180 *
michael@0 2181 * @param aClient DebuggerClient
michael@0 2182 * The debugger client parent.
michael@0 2183 * @param aActor string
michael@0 2184 * The actor ID for this breakpoint.
michael@0 2185 * @param aLocation object
michael@0 2186 * The location of the breakpoint. This is an object with two properties:
michael@0 2187 * url and line.
michael@0 2188 * @param aCondition string
michael@0 2189 * The conditional expression of the breakpoint
michael@0 2190 */
michael@0 2191 function BreakpointClient(aClient, aActor, aLocation, aCondition) {
michael@0 2192 this._client = aClient;
michael@0 2193 this._actor = aActor;
michael@0 2194 this.location = aLocation;
michael@0 2195 this.request = this._client.request;
michael@0 2196
michael@0 2197 // The condition property should only exist if it's a truthy value
michael@0 2198 if (aCondition) {
michael@0 2199 this.condition = aCondition;
michael@0 2200 }
michael@0 2201 }
michael@0 2202
michael@0 2203 BreakpointClient.prototype = {
michael@0 2204
michael@0 2205 _actor: null,
michael@0 2206 get actor() { return this._actor; },
michael@0 2207 get _transport() { return this._client._transport; },
michael@0 2208
michael@0 2209 /**
michael@0 2210 * Remove the breakpoint from the server.
michael@0 2211 */
michael@0 2212 remove: DebuggerClient.requester({
michael@0 2213 type: "delete"
michael@0 2214 }, {
michael@0 2215 telemetry: "DELETE"
michael@0 2216 }),
michael@0 2217
michael@0 2218 /**
michael@0 2219 * Determines if this breakpoint has a condition
michael@0 2220 */
michael@0 2221 hasCondition: function() {
michael@0 2222 let root = this._client.mainRoot;
michael@0 2223 // XXX bug 990137: We will remove support for client-side handling of
michael@0 2224 // conditional breakpoints
michael@0 2225 if (root.traits.conditionalBreakpoints) {
michael@0 2226 return "condition" in this;
michael@0 2227 } else {
michael@0 2228 return "conditionalExpression" in this;
michael@0 2229 }
michael@0 2230 },
michael@0 2231
michael@0 2232 /**
michael@0 2233 * Get the condition of this breakpoint. Currently we have to
michael@0 2234 * support locally emulated conditional breakpoints until the
michael@0 2235 * debugger servers are updated (see bug 990137). We used a
michael@0 2236 * different property when moving it server-side to ensure that we
michael@0 2237 * are testing the right code.
michael@0 2238 */
michael@0 2239 getCondition: function() {
michael@0 2240 let root = this._client.mainRoot;
michael@0 2241 if (root.traits.conditionalBreakpoints) {
michael@0 2242 return this.condition;
michael@0 2243 } else {
michael@0 2244 return this.conditionalExpression;
michael@0 2245 }
michael@0 2246 },
michael@0 2247
michael@0 2248 /**
michael@0 2249 * Set the condition of this breakpoint
michael@0 2250 */
michael@0 2251 setCondition: function(gThreadClient, aCondition) {
michael@0 2252 let root = this._client.mainRoot;
michael@0 2253 let deferred = promise.defer();
michael@0 2254
michael@0 2255 if (root.traits.conditionalBreakpoints) {
michael@0 2256 let info = {
michael@0 2257 url: this.location.url,
michael@0 2258 line: this.location.line,
michael@0 2259 column: this.location.column,
michael@0 2260 condition: aCondition
michael@0 2261 };
michael@0 2262
michael@0 2263 // Remove the current breakpoint and add a new one with the
michael@0 2264 // condition.
michael@0 2265 this.remove(aResponse => {
michael@0 2266 if (aResponse && aResponse.error) {
michael@0 2267 deferred.reject(aResponse);
michael@0 2268 return;
michael@0 2269 }
michael@0 2270
michael@0 2271 gThreadClient.setBreakpoint(info, (aResponse, aNewBreakpoint) => {
michael@0 2272 if (aResponse && aResponse.error) {
michael@0 2273 deferred.reject(aResponse);
michael@0 2274 } else {
michael@0 2275 deferred.resolve(aNewBreakpoint);
michael@0 2276 }
michael@0 2277 });
michael@0 2278 });
michael@0 2279 } else {
michael@0 2280 // The property shouldn't even exist if the condition is blank
michael@0 2281 if(aCondition === "") {
michael@0 2282 delete this.conditionalExpression;
michael@0 2283 }
michael@0 2284 else {
michael@0 2285 this.conditionalExpression = aCondition;
michael@0 2286 }
michael@0 2287 deferred.resolve(this);
michael@0 2288 }
michael@0 2289
michael@0 2290 return deferred.promise;
michael@0 2291 }
michael@0 2292 };
michael@0 2293
michael@0 2294 eventSource(BreakpointClient.prototype);
michael@0 2295
michael@0 2296 /**
michael@0 2297 * Environment clients are used to manipulate the lexical environment actors.
michael@0 2298 *
michael@0 2299 * @param aClient DebuggerClient
michael@0 2300 * The debugger client parent.
michael@0 2301 * @param aForm Object
michael@0 2302 * The form sent across the remote debugging protocol.
michael@0 2303 */
michael@0 2304 function EnvironmentClient(aClient, aForm) {
michael@0 2305 this._client = aClient;
michael@0 2306 this._form = aForm;
michael@0 2307 this.request = this._client.request;
michael@0 2308 }
michael@0 2309
michael@0 2310 EnvironmentClient.prototype = {
michael@0 2311
michael@0 2312 get actor() this._form.actor,
michael@0 2313 get _transport() { return this._client._transport; },
michael@0 2314
michael@0 2315 /**
michael@0 2316 * Fetches the bindings introduced by this lexical environment.
michael@0 2317 */
michael@0 2318 getBindings: DebuggerClient.requester({
michael@0 2319 type: "bindings"
michael@0 2320 }, {
michael@0 2321 telemetry: "BINDINGS"
michael@0 2322 }),
michael@0 2323
michael@0 2324 /**
michael@0 2325 * Changes the value of the identifier whose name is name (a string) to that
michael@0 2326 * represented by value (a grip).
michael@0 2327 */
michael@0 2328 assign: DebuggerClient.requester({
michael@0 2329 type: "assign",
michael@0 2330 name: args(0),
michael@0 2331 value: args(1)
michael@0 2332 }, {
michael@0 2333 telemetry: "ASSIGN"
michael@0 2334 })
michael@0 2335 };
michael@0 2336
michael@0 2337 eventSource(EnvironmentClient.prototype);
michael@0 2338
michael@0 2339 /**
michael@0 2340 * Connects to a debugger server socket and returns a DebuggerTransport.
michael@0 2341 *
michael@0 2342 * @param aHost string
michael@0 2343 * The host name or IP address of the debugger server.
michael@0 2344 * @param aPort number
michael@0 2345 * The port number of the debugger server.
michael@0 2346 */
michael@0 2347 this.debuggerSocketConnect = function (aHost, aPort)
michael@0 2348 {
michael@0 2349 let s = socketTransportService.createTransport(null, 0, aHost, aPort, null);
michael@0 2350 // By default the CONNECT socket timeout is very long, 65535 seconds,
michael@0 2351 // so that if we race to be in CONNECT state while the server socket is still
michael@0 2352 // initializing, the connection is stuck in connecting state for 18.20 hours!
michael@0 2353 s.setTimeout(Ci.nsISocketTransport.TIMEOUT_CONNECT, 2);
michael@0 2354
michael@0 2355 // openOutputStream may throw NS_ERROR_NOT_INITIALIZED if we hit some race
michael@0 2356 // where the nsISocketTransport gets shutdown in between its instantiation and
michael@0 2357 // the call to this method.
michael@0 2358 let transport;
michael@0 2359 try {
michael@0 2360 transport = new DebuggerTransport(s.openInputStream(0, 0, 0),
michael@0 2361 s.openOutputStream(0, 0, 0));
michael@0 2362 } catch(e) {
michael@0 2363 DevToolsUtils.reportException("debuggerSocketConnect", e);
michael@0 2364 throw e;
michael@0 2365 }
michael@0 2366 return transport;
michael@0 2367 }
michael@0 2368
michael@0 2369 /**
michael@0 2370 * Takes a pair of items and returns them as an array.
michael@0 2371 */
michael@0 2372 function pair(aItemOne, aItemTwo) {
michael@0 2373 return [aItemOne, aItemTwo];
michael@0 2374 }
michael@0 2375 function noop() {}

mercurial