copy from devtools/shared/client/main.js
copy to devtools/shared/client/addon-client.js
--- a/devtools/shared/client/main.js
+++ b/devtools/shared/client/addon-client.js
@@ -1,1575 +1,15 @@
-/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
-/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
-const { Ci, Cu } = require("chrome");
-const DevToolsUtils = require("devtools/shared/DevToolsUtils");
-const { getStack, callFunctionWithAsyncStack } = require("devtools/shared/platform/stack");
-
-const promise = Cu.import("resource://devtools/shared/deprecated-sync-thenables.js", {}).Promise;
-
-loader.lazyRequireGetter(this, "EventEmitter", "devtools/shared/event-emitter");
-loader.lazyRequireGetter(this, "WebConsoleClient", "devtools/shared/webconsole/client", true);
-loader.lazyRequireGetter(this, "DebuggerSocket", "devtools/shared/security/socket", true);
-loader.lazyRequireGetter(this, "Authentication", "devtools/shared/security/auth");
-
-const noop = () => {};
-
-/**
- * TODO: Get rid of this API in favor of EventTarget (bug 1042642)
- *
- * Add simple event notification to a prototype object. Any object that has
- * some use for event notifications or the observer pattern in general can be
- * augmented with the necessary facilities by passing its prototype to this
- * function.
- *
- * @param proto object
- * The prototype object that will be modified.
- */
-function eventSource(proto) {
- /**
- * Add a listener to the event source for a given event.
- *
- * @param name string
- * The event to listen for.
- * @param listener function
- * Called when the event is fired. If the same listener
- * is added more than once, it will be called once per
- * addListener call.
- */
- proto.addListener = function (name, listener) {
- if (typeof listener != "function") {
- throw TypeError("Listeners must be functions.");
- }
-
- if (!this._listeners) {
- this._listeners = {};
- }
-
- this._getListeners(name).push(listener);
- };
-
- /**
- * Add a listener to the event source for a given event. The
- * listener will be removed after it is called for the first time.
- *
- * @param name string
- * The event to listen for.
- * @param listener function
- * Called when the event is fired.
- */
- proto.addOneTimeListener = function (name, listener) {
- let l = (...args) => {
- this.removeListener(name, l);
- listener.apply(null, args);
- };
- this.addListener(name, l);
- };
-
- /**
- * Remove a listener from the event source previously added with
- * addListener().
- *
- * @param name string
- * The event name used during addListener to add the listener.
- * @param listener function
- * The callback to remove. If addListener was called multiple
- * times, all instances will be removed.
- */
- proto.removeListener = function (name, listener) {
- if (!this._listeners || (listener && !this._listeners[name])) {
- return;
- }
-
- if (!listener) {
- this._listeners[name] = [];
- } else {
- this._listeners[name] =
- this._listeners[name].filter(l => l != listener);
- }
- };
-
- /**
- * Returns the listeners for the specified event name. If none are defined it
- * initializes an empty list and returns that.
- *
- * @param name string
- * The event name.
- */
- proto._getListeners = function (name) {
- if (name in this._listeners) {
- return this._listeners[name];
- }
- this._listeners[name] = [];
- return this._listeners[name];
- };
-
- /**
- * Notify listeners of an event.
- *
- * @param name string
- * The event to fire.
- * @param arguments
- * All arguments will be passed along to the listeners,
- * including the name argument.
- */
- proto.emit = function () {
- if (!this._listeners) {
- return;
- }
-
- let name = arguments[0];
- let listeners = this._getListeners(name).slice(0);
-
- for (let listener of listeners) {
- try {
- listener.apply(null, arguments);
- } catch (e) {
- // Prevent a bad listener from interfering with the others.
- DevToolsUtils.reportException("notify event '" + name + "'", e);
- }
- }
- };
-}
-
-/**
- * Set of protocol messages that affect thread state, and the
- * state the actor is in after each message.
- */
-const ThreadStateTypes = {
- "paused": "paused",
- "resumed": "attached",
- "detached": "detached",
- "running": "attached"
-};
-
-/**
- * Set of protocol messages that are sent by the server without a prior request
- * by the client.
- */
-const UnsolicitedNotifications = {
- "consoleAPICall": "consoleAPICall",
- "eventNotification": "eventNotification",
- "fileActivity": "fileActivity",
- "lastPrivateContextExited": "lastPrivateContextExited",
- "logMessage": "logMessage",
- "networkEvent": "networkEvent",
- "networkEventUpdate": "networkEventUpdate",
- "newGlobal": "newGlobal",
- "newScript": "newScript",
- "tabDetached": "tabDetached",
- "tabListChanged": "tabListChanged",
- "reflowActivity": "reflowActivity",
- "addonListChanged": "addonListChanged",
- "workerListChanged": "workerListChanged",
- "serviceWorkerRegistrationListChanged": "serviceWorkerRegistrationList",
- "tabNavigated": "tabNavigated",
- "frameUpdate": "frameUpdate",
- "pageError": "pageError",
- "documentLoad": "documentLoad",
- "enteredFrame": "enteredFrame",
- "exitedFrame": "exitedFrame",
- "appOpen": "appOpen",
- "appClose": "appClose",
- "appInstall": "appInstall",
- "appUninstall": "appUninstall",
- "evaluationResult": "evaluationResult",
- "newSource": "newSource",
- "updatedSource": "updatedSource",
- "inspectObject": "inspectObject"
-};
-
-/**
- * Set of pause types that are sent by the server and not as an immediate
- * response to a client request.
- */
-const UnsolicitedPauses = {
- "resumeLimit": "resumeLimit",
- "debuggerStatement": "debuggerStatement",
- "breakpoint": "breakpoint",
- "DOMEvent": "DOMEvent",
- "watchpoint": "watchpoint",
- "exception": "exception"
-};
-
-/**
- * Creates a client for the remote debugging protocol server. This client
- * provides the means to communicate with the server and exchange the messages
- * required by the protocol in a traditional JavaScript API.
- */
-const DebuggerClient = exports.DebuggerClient = function (transport) {
- this._transport = transport;
- this._transport.hooks = this;
-
- // Map actor ID to client instance for each actor type.
- this._clients = new Map();
-
- this._pendingRequests = new Map();
- this._activeRequests = new Map();
- this._eventsEnabled = true;
-
- this.traits = {};
-
- this.request = this.request.bind(this);
- this.localTransport = this._transport.onOutputStreamReady === undefined;
-
- /*
- * As the first thing on the connection, expect a greeting packet from
- * the connection's root actor.
- */
- this.mainRoot = null;
- this.expectReply("root", (packet) => {
- this.mainRoot = new RootClient(this, packet);
- this.emit("connected", packet.applicationType, packet.traits);
- });
-};
-
-/**
- * A declarative helper for defining methods that send requests to the server.
- *
- * @param packetSkeleton
- * The form of the packet to send. Can specify fields to be filled from
- * the parameters by using the |arg| function.
- * @param before
- * The function to call before sending the packet. Is passed the packet,
- * and the return value is used as the new packet. The |this| context is
- * the instance of the client object we are defining a method for.
- * @param after
- * The function to call after the response is received. It is passed the
- * response, and the return value is considered the new response that
- * will be passed to the callback. The |this| context is the instance of
- * the client object we are defining a method for.
- * @return Request
- * The `Request` object that is a Promise object and resolves once
- * we receive the response. (See request method for more details)
- */
-DebuggerClient.requester = function (packetSkeleton, config = {}) {
- let { before, after } = config;
- return DevToolsUtils.makeInfallible(function (...args) {
- let outgoingPacket = {
- to: packetSkeleton.to || this.actor
- };
-
- let maxPosition = -1;
- for (let k of Object.keys(packetSkeleton)) {
- if (packetSkeleton[k] instanceof DebuggerClient.Argument) {
- let { position } = packetSkeleton[k];
- outgoingPacket[k] = packetSkeleton[k].getArgument(args);
- maxPosition = Math.max(position, maxPosition);
- } else {
- outgoingPacket[k] = packetSkeleton[k];
- }
- }
-
- if (before) {
- outgoingPacket = before.call(this, outgoingPacket);
- }
-
- return this.request(outgoingPacket, DevToolsUtils.makeInfallible((response) => {
- if (after) {
- let { from } = response;
- response = after.call(this, response);
- if (!response.from) {
- response.from = from;
- }
- }
-
- // The callback is always the last parameter.
- let thisCallback = args[maxPosition + 1];
- if (thisCallback) {
- thisCallback(response);
- }
- return response;
- }, "DebuggerClient.requester request callback"));
- }, "DebuggerClient.requester");
-};
-
-function arg(pos) {
- return new DebuggerClient.Argument(pos);
-}
-
-DebuggerClient.Argument = function (position) {
- this.position = position;
-};
-
-DebuggerClient.Argument.prototype.getArgument = function (params) {
- if (!(this.position in params)) {
- throw new Error("Bad index into params: " + this.position);
- }
- return params[this.position];
-};
-
-// Expose these to save callers the trouble of importing DebuggerSocket
-DebuggerClient.socketConnect = function (options) {
- // Defined here instead of just copying the function to allow lazy-load
- return DebuggerSocket.connect(options);
-};
-DevToolsUtils.defineLazyGetter(DebuggerClient, "Authenticators", () => {
- return Authentication.Authenticators;
-});
-DevToolsUtils.defineLazyGetter(DebuggerClient, "AuthenticationResult", () => {
- return Authentication.AuthenticationResult;
-});
-
-DebuggerClient.prototype = {
- /**
- * Connect to the server and start exchanging protocol messages.
- *
- * @param onConnected function
- * If specified, will be called when the greeting packet is
- * received from the debugging server.
- *
- * @return Promise
- * Resolves once connected with an array whose first element
- * is the application type, by default "browser", and the second
- * element is the traits object (help figure out the features
- * and behaviors of the server we connect to. See RootActor).
- */
- connect: function (onConnected) {
- let deferred = promise.defer();
- this.emit("connect");
-
- // Also emit the event on the |DebuggerClient| object (not on the instance),
- // so it's possible to track all instances.
- EventEmitter.emit(DebuggerClient, "connect", this);
-
- this.addOneTimeListener("connected", (name, applicationType, traits) => {
- this.traits = traits;
- if (onConnected) {
- onConnected(applicationType, traits);
- }
- deferred.resolve([applicationType, traits]);
- });
-
- this._transport.ready();
- return deferred.promise;
- },
-
- /**
- * Shut down communication with the debugging server.
- *
- * @param onClosed function
- * If specified, will be called when the debugging connection
- * has been closed. This parameter is deprecated - please use
- * the returned Promise.
- * @return Promise
- * Resolves after the underlying transport is closed.
- */
- close: function (onClosed) {
- let deferred = promise.defer();
- if (onClosed) {
- deferred.promise.then(onClosed);
- }
-
- // Disable detach event notifications, because event handlers will be in a
- // cleared scope by the time they run.
- this._eventsEnabled = false;
-
- let cleanup = () => {
- this._transport.close();
- this._transport = null;
- };
-
- // If the connection is already closed,
- // there is no need to detach client
- // as we won't be able to send any message.
- if (this._closed) {
- cleanup();
- deferred.resolve();
- return deferred.promise;
- }
-
- this.addOneTimeListener("closed", deferred.resolve);
-
- // Call each client's `detach` method by calling
- // lastly registered ones first to give a chance
- // to detach child clients first.
- let clients = [...this._clients.values()];
- this._clients.clear();
- const detachClients = () => {
- let client = clients.pop();
- if (!client) {
- // All clients detached.
- cleanup();
- return;
- }
- if (client.detach) {
- client.detach(detachClients);
- return;
- }
- detachClients();
- };
- detachClients();
-
- return deferred.promise;
- },
-
- /*
- * This function exists only to preserve DebuggerClient's interface;
- * new code should say 'client.mainRoot.listTabs()'.
- */
- listTabs: function (onResponse) {
- return this.mainRoot.listTabs(onResponse);
- },
-
- /*
- * This function exists only to preserve DebuggerClient's interface;
- * new code should say 'client.mainRoot.listAddons()'.
- */
- listAddons: function (onResponse) {
- return this.mainRoot.listAddons(onResponse);
- },
-
- getTab: function (filter) {
- return this.mainRoot.getTab(filter);
- },
-
- /**
- * Attach to a tab actor.
- *
- * @param string tabActor
- * The actor ID for the tab to attach.
- * @param function onResponse
- * Called with the response packet and a TabClient
- * (which will be undefined on error).
- */
- attachTab: function (tabActor, onResponse = noop) {
- if (this._clients.has(tabActor)) {
- let cachedTab = this._clients.get(tabActor);
- let cachedResponse = {
- cacheDisabled: cachedTab.cacheDisabled,
- javascriptEnabled: cachedTab.javascriptEnabled,
- traits: cachedTab.traits,
- };
- DevToolsUtils.executeSoon(() => onResponse(cachedResponse, cachedTab));
- return promise.resolve([cachedResponse, cachedTab]);
- }
-
- let packet = {
- to: tabActor,
- type: "attach"
- };
- return this.request(packet).then(response => {
- let tabClient;
- if (!response.error) {
- tabClient = new TabClient(this, response);
- this.registerClient(tabClient);
- }
- onResponse(response, tabClient);
- return [response, tabClient];
- });
- },
-
- attachWorker: function (workerActor, onResponse = noop) {
- let workerClient = this._clients.get(workerActor);
- if (workerClient !== undefined) {
- let response = {
- from: workerClient.actor,
- type: "attached",
- url: workerClient.url
- };
- DevToolsUtils.executeSoon(() => onResponse(response, workerClient));
- return promise.resolve([response, workerClient]);
- }
-
- return this.request({ to: workerActor, type: "attach" }).then(response => {
- if (response.error) {
- onResponse(response, null);
- return [response, null];
- }
-
- workerClient = new WorkerClient(this, response);
- this.registerClient(workerClient);
- onResponse(response, workerClient);
- return [response, workerClient];
- });
- },
-
- /**
- * Attach to an addon actor.
- *
- * @param string addonActor
- * The actor ID for the addon to attach.
- * @param function onResponse
- * Called with the response packet and a AddonClient
- * (which will be undefined on error).
- */
- attachAddon: function (addonActor, onResponse = noop) {
- let packet = {
- to: addonActor,
- type: "attach"
- };
- return this.request(packet).then(response => {
- let addonClient;
- if (!response.error) {
- addonClient = new AddonClient(this, addonActor);
- this.registerClient(addonClient);
- this.activeAddon = addonClient;
- }
- onResponse(response, addonClient);
- return [response, addonClient];
- });
- },
-
- /**
- * Attach to a Web Console actor.
- *
- * @param string consoleActor
- * The ID for the console actor to attach to.
- * @param array listeners
- * The console listeners you want to start.
- * @param function onResponse
- * Called with the response packet and a WebConsoleClient
- * instance (which will be undefined on error).
- */
- attachConsole:
- function (consoleActor, listeners, onResponse = noop) {
- let packet = {
- to: consoleActor,
- type: "startListeners",
- listeners: listeners,
- };
-
- return this.request(packet).then(response => {
- let consoleClient;
- if (!response.error) {
- if (this._clients.has(consoleActor)) {
- consoleClient = this._clients.get(consoleActor);
- } else {
- consoleClient = new WebConsoleClient(this, response);
- this.registerClient(consoleClient);
- }
- }
- onResponse(response, consoleClient);
- return [response, consoleClient];
- });
- },
-
- /**
- * Attach to a global-scoped thread actor for chrome debugging.
- *
- * @param string threadActor
- * The actor ID for the thread to attach.
- * @param function onResponse
- * Called with the response packet and a ThreadClient
- * (which will be undefined on error).
- * @param object options
- * Configuration options.
- * - useSourceMaps: whether to use source maps or not.
- */
- attachThread: function (threadActor, onResponse = noop, options = {}) {
- if (this._clients.has(threadActor)) {
- let client = this._clients.get(threadActor);
- DevToolsUtils.executeSoon(() => onResponse({}, client));
- return promise.resolve([{}, client]);
- }
-
- let packet = {
- to: threadActor,
- type: "attach",
- options,
- };
- return this.request(packet).then(response => {
- let threadClient;
- if (!response.error) {
- threadClient = new ThreadClient(this, threadActor);
- this.registerClient(threadClient);
- }
- onResponse(response, threadClient);
- return [response, threadClient];
- });
- },
-
- /**
- * Attach to a trace actor.
- *
- * @param string traceActor
- * The actor ID for the tracer to attach.
- * @param function onResponse
- * Called with the response packet and a TraceClient
- * (which will be undefined on error).
- */
- attachTracer: function (traceActor, onResponse = noop) {
- if (this._clients.has(traceActor)) {
- let client = this._clients.get(traceActor);
- DevToolsUtils.executeSoon(() => onResponse({}, client));
- return promise.resolve([{}, client]);
- }
-
- let packet = {
- to: traceActor,
- type: "attach"
- };
- return this.request(packet).then(response => {
- let traceClient;
- if (!response.error) {
- traceClient = new TraceClient(this, traceActor);
- this.registerClient(traceClient);
- }
- onResponse(response, traceClient);
- return [response, traceClient];
- });
- },
-
- /**
- * Fetch the ChromeActor for the main process or ChildProcessActor for a
- * a given child process ID.
- *
- * @param number id
- * The ID for the process to attach (returned by `listProcesses`).
- * Connected to the main process if omitted, or is 0.
- */
- getProcess: function (id) {
- let packet = {
- to: "root",
- type: "getProcess"
- };
- if (typeof (id) == "number") {
- packet.id = id;
- }
- return this.request(packet);
- },
-
- /**
- * Release an object actor.
- *
- * @param string actor
- * The actor ID to send the request to.
- * @param onResponse function
- * If specified, will be called with the response packet when
- * debugging server responds.
- */
- release: DebuggerClient.requester({
- to: arg(0),
- type: "release"
- }),
-
- /**
- * Send a request to the debugging server.
- *
- * @param packet object
- * A JSON packet to send to the debugging server.
- * @param onResponse function
- * If specified, will be called with the JSON response packet when
- * debugging server responds.
- * @return Request
- * This object emits a number of events to allow you to respond to
- * different parts of the request lifecycle.
- * It is also a Promise object, with a `then` method, that is resolved
- * whenever a JSON or a Bulk response is received; and is rejected
- * if the response is an error.
- * Note: This return value can be ignored if you are using JSON alone,
- * because the callback provided in |onResponse| will be bound to the
- * "json-reply" event automatically.
- *
- * Events emitted:
- * * json-reply: The server replied with a JSON packet, which is
- * passed as event data.
- * * bulk-reply: The server replied with bulk data, which you can read
- * using the event data object containing:
- * * actor: Name of actor that received the packet
- * * type: Name of actor's method that was called on receipt
- * * length: Size of the data to be read
- * * stream: This input stream should only be used directly if you
- * can ensure that you will read exactly |length| bytes
- * and will not close the stream when reading is complete
- * * done: If you use the stream directly (instead of |copyTo|
- * below), you must signal completion by resolving /
- * rejecting this deferred. If it's rejected, the
- * transport will be closed. If an Error is supplied as a
- * rejection value, it will be logged via |dumpn|. If you
- * do use |copyTo|, resolving is taken care of for you
- * when copying completes.
- * * copyTo: A helper function for getting your data out of the
- * stream that meets the stream handling requirements
- * above, and has the following signature:
- * @param output nsIAsyncOutputStream
- * The stream to copy to.
- * @return Promise
- * The promise is resolved when copying completes or
- * rejected if any (unexpected) errors occur.
- * This object also emits "progress" events for each chunk
- * that is copied. See stream-utils.js.
- */
- request: function (packet, onResponse) {
- if (!this.mainRoot) {
- throw Error("Have not yet received a hello packet from the server.");
- }
- let type = packet.type || "";
- if (!packet.to) {
- throw Error("'" + type + "' request packet has no destination.");
- }
-
- // The onResponse callback might modify the response, so we need to call
- // it and resolve the promise with its result if it's truthy.
- const safeOnResponse = response => {
- if (!onResponse) {
- return response;
- }
- return onResponse(response) || response;
- };
-
- if (this._closed) {
- let msg = "'" + type + "' request packet to " +
- "'" + packet.to + "' " +
- "can't be sent as the connection is closed.";
- let resp = { error: "connectionClosed", message: msg };
- return promise.reject(safeOnResponse(resp));
- }
-
- let request = new Request(packet);
- request.format = "json";
- request.stack = getStack();
-
- // Implement a Promise like API on the returned object
- // that resolves/rejects on request response
- let deferred = promise.defer();
- function listenerJson(resp) {
- removeRequestListeners();
- if (resp.error) {
- deferred.reject(safeOnResponse(resp));
- } else {
- deferred.resolve(safeOnResponse(resp));
- }
- }
- function listenerBulk(resp) {
- removeRequestListeners();
- deferred.resolve(safeOnResponse(resp));
- }
-
- const removeRequestListeners = () => {
- request.off("json-reply", listenerJson);
- request.off("bulk-reply", listenerBulk);
- };
-
- request.on("json-reply", listenerJson);
- request.on("bulk-reply", listenerBulk);
-
- this._sendOrQueueRequest(request);
- request.then = deferred.promise.then.bind(deferred.promise);
-
- return request;
- },
-
- /**
- * Transmit streaming data via a bulk request.
- *
- * This method initiates the bulk send process by queuing up the header data.
- * The caller receives eventual access to a stream for writing.
- *
- * Since this opens up more options for how the server might respond (it could
- * send back either JSON or bulk data), and the returned Request object emits
- * events for different stages of the request process that you may want to
- * react to.
- *
- * @param request Object
- * This is modeled after the format of JSON packets above, but does not
- * actually contain the data, but is instead just a routing header:
- * * actor: Name of actor that will receive the packet
- * * type: Name of actor's method that should be called on receipt
- * * length: Size of the data to be sent
- * @return Request
- * This object emits a number of events to allow you to respond to
- * different parts of the request lifecycle.
- *
- * Events emitted:
- * * bulk-send-ready: Ready to send bulk data to the server, using the
- * event data object containing:
- * * stream: This output stream should only be used directly if
- * you can ensure that you will write exactly |length|
- * bytes and will not close the stream when writing is
- * complete
- * * done: If you use the stream directly (instead of |copyFrom|
- * below), you must signal completion by resolving /
- * rejecting this deferred. If it's rejected, the
- * transport will be closed. If an Error is supplied as
- * a rejection value, it will be logged via |dumpn|. If
- * you do use |copyFrom|, resolving is taken care of for
- * you when copying completes.
- * * copyFrom: A helper function for getting your data onto the
- * stream that meets the stream handling requirements
- * above, and has the following signature:
- * @param input nsIAsyncInputStream
- * The stream to copy from.
- * @return Promise
- * The promise is resolved when copying completes or
- * rejected if any (unexpected) errors occur.
- * This object also emits "progress" events for each chunk
- * that is copied. See stream-utils.js.
- * * json-reply: The server replied with a JSON packet, which is
- * passed as event data.
- * * bulk-reply: The server replied with bulk data, which you can read
- * using the event data object containing:
- * * actor: Name of actor that received the packet
- * * type: Name of actor's method that was called on receipt
- * * length: Size of the data to be read
- * * stream: This input stream should only be used directly if you
- * can ensure that you will read exactly |length| bytes
- * and will not close the stream when reading is complete
- * * done: If you use the stream directly (instead of |copyTo|
- * below), you must signal completion by resolving /
- * rejecting this deferred. If it's rejected, the
- * transport will be closed. If an Error is supplied as a
- * rejection value, it will be logged via |dumpn|. If you
- * do use |copyTo|, resolving is taken care of for you
- * when copying completes.
- * * copyTo: A helper function for getting your data out of the
- * stream that meets the stream handling requirements
- * above, and has the following signature:
- * @param output nsIAsyncOutputStream
- * The stream to copy to.
- * @return Promise
- * The promise is resolved when copying completes or
- * rejected if any (unexpected) errors occur.
- * This object also emits "progress" events for each chunk
- * that is copied. See stream-utils.js.
- */
- startBulkRequest: function (request) {
- if (!this.traits.bulk) {
- throw Error("Server doesn't support bulk transfers");
- }
- if (!this.mainRoot) {
- throw Error("Have not yet received a hello packet from the server.");
- }
- if (!request.type) {
- throw Error("Bulk packet is missing the required 'type' field.");
- }
- if (!request.actor) {
- throw Error("'" + request.type + "' bulk packet has no destination.");
- }
- if (!request.length) {
- throw Error("'" + request.type + "' bulk packet has no length.");
- }
-
- request = new Request(request);
- request.format = "bulk";
-
- this._sendOrQueueRequest(request);
-
- return request;
- },
-
- /**
- * If a new request can be sent immediately, do so. Otherwise, queue it.
- */
- _sendOrQueueRequest(request) {
- let actor = request.actor;
- if (!this._activeRequests.has(actor)) {
- this._sendRequest(request);
- } else {
- this._queueRequest(request);
- }
- },
-
- /**
- * Send a request.
- * @throws Error if there is already an active request in flight for the same
- * actor.
- */
- _sendRequest(request) {
- let actor = request.actor;
- this.expectReply(actor, request);
-
- if (request.format === "json") {
- this._transport.send(request.request);
- return;
- }
-
- this._transport.startBulkSend(request.request).then((...args) => {
- request.emit("bulk-send-ready", ...args);
- });
- },
-
- /**
- * Queue a request to be sent later. Queues are only drained when an in
- * flight request to a given actor completes.
- */
- _queueRequest(request) {
- let actor = request.actor;
- let queue = this._pendingRequests.get(actor) || [];
- queue.push(request);
- this._pendingRequests.set(actor, queue);
- },
-
- /**
- * Attempt the next request to a given actor (if any).
- */
- _attemptNextRequest(actor) {
- if (this._activeRequests.has(actor)) {
- return;
- }
- let queue = this._pendingRequests.get(actor);
- if (!queue) {
- return;
- }
- let request = queue.shift();
- if (queue.length === 0) {
- this._pendingRequests.delete(actor);
- }
- this._sendRequest(request);
- },
-
- /**
- * Arrange to hand the next reply from |actor| to the handler bound to
- * |request|.
- *
- * DebuggerClient.prototype.request / startBulkRequest usually takes care of
- * establishing the handler for a given request, but in rare cases (well,
- * greetings from new root actors, is the only case at the moment) we must be
- * prepared for a "reply" that doesn't correspond to any request we sent.
- */
- expectReply: function (actor, request) {
- if (this._activeRequests.has(actor)) {
- throw Error("clashing handlers for next reply from " + actor);
- }
-
- // If a handler is passed directly (as it is with the handler for the root
- // actor greeting), create a dummy request to bind this to.
- if (typeof request === "function") {
- let handler = request;
- request = new Request();
- request.on("json-reply", handler);
- }
-
- this._activeRequests.set(actor, request);
- },
-
- // Transport hooks.
-
- /**
- * Called by DebuggerTransport to dispatch incoming packets as appropriate.
- *
- * @param packet object
- * The incoming packet.
- */
- onPacket: function (packet) {
- if (!packet.from) {
- DevToolsUtils.reportException(
- "onPacket",
- new Error("Server did not specify an actor, dropping packet: " +
- JSON.stringify(packet)));
- return;
- }
-
- // If we have a registered Front for this actor, let it handle the packet
- // and skip all the rest of this unpleasantness.
- let front = this.getActor(packet.from);
- if (front) {
- front.onPacket(packet);
- return;
- }
-
- // Check for "forwardingCancelled" here instead of using a client to handle it.
- // This is necessary because we might receive this event while the client is closing,
- // and the clients have already been removed by that point.
- if (this.mainRoot &&
- packet.from == this.mainRoot.actor &&
- packet.type == "forwardingCancelled") {
- this.purgeRequests(packet.prefix);
- return;
- }
-
- if (this._clients.has(packet.from) && packet.type) {
- let client = this._clients.get(packet.from);
- let type = packet.type;
- if (client.events.indexOf(type) != -1) {
- client.emit(type, packet);
- // we ignore the rest, as the client is expected to handle this packet.
- return;
- }
- }
-
- let activeRequest;
- // See if we have a handler function waiting for a reply from this
- // actor. (Don't count unsolicited notifications or pauses as
- // replies.)
- if (this._activeRequests.has(packet.from) &&
- !(packet.type in UnsolicitedNotifications) &&
- !(packet.type == ThreadStateTypes.paused &&
- packet.why.type in UnsolicitedPauses)) {
- activeRequest = this._activeRequests.get(packet.from);
- this._activeRequests.delete(packet.from);
- }
-
- // If there is a subsequent request for the same actor, hand it off to the
- // transport. Delivery of packets on the other end is always async, even
- // in the local transport case.
- this._attemptNextRequest(packet.from);
-
- // Packets that indicate thread state changes get special treatment.
- if (packet.type in ThreadStateTypes &&
- this._clients.has(packet.from) &&
- typeof this._clients.get(packet.from)._onThreadState == "function") {
- this._clients.get(packet.from)._onThreadState(packet);
- }
-
- // TODO: Bug 1151156 - Remove once Gecko 40 is on b2g-stable.
- if (!this.traits.noNeedToFakeResumptionOnNavigation) {
- // On navigation the server resumes, so the client must resume as well.
- // We achieve that by generating a fake resumption packet that triggers
- // the client's thread state change listeners.
- if (packet.type == UnsolicitedNotifications.tabNavigated &&
- this._clients.has(packet.from) &&
- this._clients.get(packet.from).thread) {
- let thread = this._clients.get(packet.from).thread;
- let resumption = { from: thread._actor, type: "resumed" };
- thread._onThreadState(resumption);
- }
- }
-
- // Only try to notify listeners on events, not responses to requests
- // that lack a packet type.
- if (packet.type) {
- this.emit(packet.type, packet);
- }
-
- if (activeRequest) {
- let emitReply = () => activeRequest.emit("json-reply", packet);
- if (activeRequest.stack) {
- callFunctionWithAsyncStack(emitReply, activeRequest.stack,
- "DevTools RDP");
- } else {
- emitReply();
- }
- }
- },
-
- /**
- * Called by the DebuggerTransport to dispatch incoming bulk packets as
- * appropriate.
- *
- * @param packet object
- * The incoming packet, which contains:
- * * actor: Name of actor that will receive the packet
- * * type: Name of actor's method that should be called on receipt
- * * length: Size of the data to be read
- * * stream: This input stream should only be used directly if you can
- * ensure that you will read exactly |length| bytes and will
- * not close the stream when reading is complete
- * * done: If you use the stream directly (instead of |copyTo|
- * below), you must signal completion by resolving /
- * rejecting this deferred. If it's rejected, the transport
- * will be closed. If an Error is supplied as a rejection
- * value, it will be logged via |dumpn|. If you do use
- * |copyTo|, resolving is taken care of for you when copying
- * completes.
- * * copyTo: A helper function for getting your data out of the stream
- * that meets the stream handling requirements above, and has
- * the following signature:
- * @param output nsIAsyncOutputStream
- * The stream to copy to.
- * @return Promise
- * The promise is resolved when copying completes or rejected
- * if any (unexpected) errors occur.
- * This object also emits "progress" events for each chunk
- * that is copied. See stream-utils.js.
- */
- onBulkPacket: function (packet) {
- let { actor } = packet;
-
- if (!actor) {
- DevToolsUtils.reportException(
- "onBulkPacket",
- new Error("Server did not specify an actor, dropping bulk packet: " +
- JSON.stringify(packet)));
- return;
- }
-
- // See if we have a handler function waiting for a reply from this
- // actor.
- if (!this._activeRequests.has(actor)) {
- return;
- }
-
- let activeRequest = this._activeRequests.get(actor);
- this._activeRequests.delete(actor);
-
- // If there is a subsequent request for the same actor, hand it off to the
- // transport. Delivery of packets on the other end is always async, even
- // in the local transport case.
- this._attemptNextRequest(actor);
-
- activeRequest.emit("bulk-reply", packet);
- },
-
- /**
- * Called by DebuggerTransport when the underlying stream is closed.
- *
- * @param status nsresult
- * The status code that corresponds to the reason for closing
- * the stream.
- */
- onClosed: function () {
- this._closed = true;
- this.emit("closed");
-
- this.purgeRequests();
-
- // The |_pools| array on the client-side currently is used only by
- // protocol.js to store active fronts, mirroring the actor pools found in
- // the server. So, read all usages of "pool" as "protocol.js front".
- //
- // In the normal case where we shutdown cleanly, the toolbox tells each tool
- // to close, and they each call |destroy| on any fronts they were using.
- // When |destroy| or |cleanup| is called on a protocol.js front, it also
- // removes itself from the |_pools| array. Once the toolbox has shutdown,
- // the connection is closed, and we reach here. All fronts (should have
- // been) |destroy|ed, so |_pools| should empty.
- //
- // If the connection instead aborts unexpectedly, we may end up here with
- // all fronts used during the life of the connection. So, we call |cleanup|
- // on them clear their state, reject pending requests, and remove themselves
- // from |_pools|. This saves the toolbox from hanging indefinitely, in case
- // it waits for some server response before shutdown that will now never
- // arrive.
- for (let pool of this._pools) {
- pool.cleanup();
- }
- },
-
- /**
- * Purge pending and active requests in this client.
- *
- * @param prefix string (optional)
- * If a prefix is given, only requests for actor IDs that start with the prefix
- * will be cleaned up. This is useful when forwarding of a portion of requests
- * is cancelled on the server.
- */
- purgeRequests(prefix = "") {
- let reject = function (type, request) {
- // Server can send packets on its own and client only pass a callback
- // to expectReply, so that there is no request object.
- let msg;
- if (request.request) {
- msg = "'" + request.request.type + "' " + type + " request packet" +
- " to '" + request.actor + "' " +
- "can't be sent as the connection just closed.";
- } else {
- msg = "server side packet can't be received as the connection just closed.";
- }
- let packet = { error: "connectionClosed", message: msg };
- request.emit("json-reply", packet);
- };
-
- let pendingRequestsToReject = [];
- this._pendingRequests.forEach((requests, actor) => {
- if (!actor.startsWith(prefix)) {
- return;
- }
- this._pendingRequests.delete(actor);
- pendingRequestsToReject = pendingRequestsToReject.concat(requests);
- });
- pendingRequestsToReject.forEach(request => reject("pending", request));
-
- let activeRequestsToReject = [];
- this._activeRequests.forEach((request, actor) => {
- if (!actor.startsWith(prefix)) {
- return;
- }
- this._activeRequests.delete(actor);
- activeRequestsToReject = activeRequestsToReject.concat(request);
- });
- activeRequestsToReject.forEach(request => reject("active", request));
- },
-
- /**
- * Search for all requests in process for this client, including those made via
- * protocol.js and wait all of them to complete. Since the requests seen when this is
- * first called may in turn trigger more requests, we keep recursing through this
- * function until there is no more activity.
- *
- * This is a fairly heavy weight process, so it's only meant to be used in tests.
- *
- * @return Promise
- * Resolved when all requests have settled.
- */
- waitForRequestsToSettle() {
- let requests = [];
-
- // Gather all pending and active requests in this client
- // The request object supports a Promise API for completion (it has .then())
- this._pendingRequests.forEach(requestsForActor => {
- // Each value is an array of pending requests
- requests = requests.concat(requestsForActor);
- });
- this._activeRequests.forEach(requestForActor => {
- // Each value is a single active request
- requests = requests.concat(requestForActor);
- });
-
- // protocol.js
- // Use a Set because some fronts (like domwalker) seem to have multiple parents.
- let fronts = new Set();
- let poolsToVisit = [...this._pools];
-
- // With protocol.js, each front can potentially have it's own pools containing child
- // fronts, forming a tree. Descend through all the pools to locate all child fronts.
- while (poolsToVisit.length) {
- let pool = poolsToVisit.shift();
- fronts.add(pool);
- for (let child of pool.poolChildren()) {
- poolsToVisit.push(child);
- }
- }
-
- // For each front, wait for its requests to settle
- for (let front of fronts) {
- if (front.hasRequests()) {
- requests.push(front.waitForRequestsToSettle());
- }
- }
-
- // Abort early if there are no requests
- if (!requests.length) {
- return Promise.resolve();
- }
-
- return DevToolsUtils.settleAll(requests).catch(() => {
- // One of the requests might have failed, but ignore that situation here and pipe
- // both success and failure through the same path. The important part is just that
- // we waited.
- }).then(() => {
- // Repeat, more requests may have started in response to those we just waited for
- return this.waitForRequestsToSettle();
- });
- },
-
- registerClient: function (client) {
- let actorID = client.actor;
- if (!actorID) {
- throw new Error("DebuggerServer.registerClient expects " +
- "a client instance with an `actor` attribute.");
- }
- if (!Array.isArray(client.events)) {
- throw new Error("DebuggerServer.registerClient expects " +
- "a client instance with an `events` attribute " +
- "that is an array.");
- }
- if (client.events.length > 0 && typeof (client.emit) != "function") {
- throw new Error("DebuggerServer.registerClient expects " +
- "a client instance with non-empty `events` array to" +
- "have an `emit` function.");
- }
- if (this._clients.has(actorID)) {
- throw new Error("DebuggerServer.registerClient already registered " +
- "a client for this actor.");
- }
- this._clients.set(actorID, client);
- },
-
- unregisterClient: function (client) {
- let actorID = client.actor;
- if (!actorID) {
- throw new Error("DebuggerServer.unregisterClient expects " +
- "a Client instance with a `actor` attribute.");
- }
- this._clients.delete(actorID);
- },
-
- /**
- * Actor lifetime management, echos the server's actor pools.
- */
- __pools: null,
- get _pools() {
- if (this.__pools) {
- return this.__pools;
- }
- this.__pools = new Set();
- return this.__pools;
- },
-
- addActorPool: function (pool) {
- this._pools.add(pool);
- },
- removeActorPool: function (pool) {
- this._pools.delete(pool);
- },
- getActor: function (actorID) {
- let pool = this.poolFor(actorID);
- return pool ? pool.get(actorID) : null;
- },
-
- poolFor: function (actorID) {
- for (let pool of this._pools) {
- if (pool.has(actorID)) {
- return pool;
- }
- }
- return null;
- },
-
- /**
- * Currently attached addon.
- */
- activeAddon: null
-};
-
-eventSource(DebuggerClient.prototype);
-
-class Request extends EventEmitter {
- constructor(request) {
- super();
- this.request = request;
- }
-
- get actor() {
- return this.request.to || this.request.actor;
- }
-}
-
-/**
- * Creates a tab client for the remote debugging protocol server. This client
- * is a front to the tab actor created in the server side, hiding the protocol
- * details in a traditional JavaScript API.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param form object
- * The protocol form for this tab.
- */
-function TabClient(client, form) {
- this.client = client;
- this._actor = form.from;
- this._threadActor = form.threadActor;
- this.javascriptEnabled = form.javascriptEnabled;
- this.cacheDisabled = form.cacheDisabled;
- this.thread = null;
- this.request = this.client.request;
- this.traits = form.traits || {};
- this.events = ["workerListChanged"];
-}
-
-TabClient.prototype = {
- get actor() {
- return this._actor;
- },
- get _transport() {
- return this.client._transport;
- },
-
- /**
- * Attach to a thread actor.
- *
- * @param object options
- * Configuration options.
- * - useSourceMaps: whether to use source maps or not.
- * @param function onResponse
- * Called with the response packet and a ThreadClient
- * (which will be undefined on error).
- */
- attachThread: function (options = {}, onResponse = noop) {
- if (this.thread) {
- DevToolsUtils.executeSoon(() => onResponse({}, this.thread));
- return promise.resolve([{}, this.thread]);
- }
-
- let packet = {
- to: this._threadActor,
- type: "attach",
- options,
- };
- return this.request(packet).then(response => {
- if (!response.error) {
- this.thread = new ThreadClient(this, this._threadActor);
- this.client.registerClient(this.thread);
- }
- onResponse(response, this.thread);
- return [response, this.thread];
- });
- },
-
- /**
- * Detach the client from the tab actor.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- detach: DebuggerClient.requester({
- type: "detach"
- }, {
- before: function (packet) {
- if (this.thread) {
- this.thread.detach();
- }
- return packet;
- },
- after: function (response) {
- this.client.unregisterClient(this);
- return response;
- },
- }),
-
- /**
- * Bring the window to the front.
- */
- focus: DebuggerClient.requester({
- type: "focus"
- }, {}),
-
- /**
- * Reload the page in this tab.
- *
- * @param [optional] object options
- * An object with a `force` property indicating whether or not
- * this reload should skip the cache
- */
- reload: function (options = { force: false }) {
- return this._reload(options);
- },
- _reload: DebuggerClient.requester({
- type: "reload",
- options: arg(0)
- }),
-
- /**
- * Navigate to another URL.
- *
- * @param string url
- * The URL to navigate to.
- */
- navigateTo: DebuggerClient.requester({
- type: "navigateTo",
- url: arg(0)
- }),
-
- /**
- * Reconfigure the tab actor.
- *
- * @param object options
- * A dictionary object of the new options to use in the tab actor.
- * @param function onResponse
- * Called with the response packet.
- */
- reconfigure: DebuggerClient.requester({
- type: "reconfigure",
- options: arg(0)
- }),
-
- listWorkers: DebuggerClient.requester({
- type: "listWorkers"
- }),
-
- attachWorker: function (workerActor, onResponse) {
- return this.client.attachWorker(workerActor, onResponse);
- },
-};
-
-eventSource(TabClient.prototype);
-
-function WorkerClient(client, form) {
- this.client = client;
- this._actor = form.from;
- this._isClosed = false;
- this._url = form.url;
-
- this._onClose = this._onClose.bind(this);
-
- this.addListener("close", this._onClose);
-
- this.traits = {};
-}
-
-WorkerClient.prototype = {
- get _transport() {
- return this.client._transport;
- },
-
- get request() {
- return this.client.request;
- },
-
- get actor() {
- return this._actor;
- },
-
- get url() {
- return this._url;
- },
-
- get isClosed() {
- return this._isClosed;
- },
-
- detach: DebuggerClient.requester({ type: "detach" }, {
- after: function (response) {
- if (this.thread) {
- this.client.unregisterClient(this.thread);
- }
- this.client.unregisterClient(this);
- return response;
- },
- }),
-
- attachThread: function (options = {}, onResponse = noop) {
- if (this.thread) {
- let response = [{
- type: "connected",
- threadActor: this.thread._actor,
- consoleActor: this.consoleActor,
- }, this.thread];
- DevToolsUtils.executeSoon(() => onResponse(response));
- return response;
- }
-
- // The connect call on server doesn't attach the thread as of version 44.
- return this.request({
- to: this._actor,
- type: "connect",
- options,
- }).then(connectResponse => {
- if (connectResponse.error) {
- onResponse(connectResponse, null);
- return [connectResponse, null];
- }
-
- return this.request({
- to: connectResponse.threadActor,
- type: "attach",
- options,
- }).then(attachResponse => {
- if (attachResponse.error) {
- onResponse(attachResponse, null);
- }
-
- this.thread = new ThreadClient(this, connectResponse.threadActor);
- this.consoleActor = connectResponse.consoleActor;
- this.client.registerClient(this.thread);
-
- onResponse(connectResponse, this.thread);
- return [connectResponse, this.thread];
- });
- }, error => {
- onResponse(error, null);
- });
- },
-
- _onClose: function () {
- this.removeListener("close", this._onClose);
-
- if (this.thread) {
- this.client.unregisterClient(this.thread);
- }
- this.client.unregisterClient(this);
- this._isClosed = true;
- },
-
- reconfigure: function () {
- return Promise.resolve();
- },
-
- events: ["close"]
-};
-
-eventSource(WorkerClient.prototype);
+const {DebuggerClient} = require("./debugger-client");
function AddonClient(client, actor) {
this._client = client;
this._actor = actor;
this.request = this._client.request;
this.events = [];
}
@@ -1595,1832 +35,9 @@ AddonClient.prototype = {
this._client.activeAddon = null;
}
this._client.unregisterClient(this);
return response;
},
})
};
-/**
- * A RootClient object represents a root actor on the server. Each
- * DebuggerClient keeps a RootClient instance representing the root actor
- * for the initial connection; DebuggerClient's 'listTabs' and
- * 'listChildProcesses' methods forward to that root actor.
- *
- * @param client object
- * The client connection to which this actor belongs.
- * @param greeting string
- * The greeting packet from the root actor we're to represent.
- *
- * Properties of a RootClient instance:
- *
- * @property actor string
- * The name of this child's root actor.
- * @property applicationType string
- * The application type, as given in the root actor's greeting packet.
- * @property traits object
- * The traits object, as given in the root actor's greeting packet.
- */
-function RootClient(client, greeting) {
- this._client = client;
- this.actor = greeting.from;
- this.applicationType = greeting.applicationType;
- this.traits = greeting.traits;
-}
-exports.RootClient = RootClient;
-
-RootClient.prototype = {
- constructor: RootClient,
-
- /**
- * Gets the "root" form, which lists all the global actors that affect the entire
- * browser. This can replace usages of `listTabs` that only wanted the global actors
- * and didn't actually care about tabs.
- */
- getRoot: DebuggerClient.requester({ type: "getRoot" }),
-
- /**
- * List the open tabs.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- listTabs: DebuggerClient.requester({ type: "listTabs" }),
-
- /**
- * List the installed addons.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- listAddons: DebuggerClient.requester({ type: "listAddons" }),
-
- /**
- * List the registered workers.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- listWorkers: DebuggerClient.requester({ type: "listWorkers" }),
-
- /**
- * List the registered service workers.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- listServiceWorkerRegistrations: DebuggerClient.requester({
- type: "listServiceWorkerRegistrations"
- }),
-
- /**
- * List the running processes.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- listProcesses: DebuggerClient.requester({ type: "listProcesses" }),
-
- /**
- * Fetch the TabActor for the currently selected tab, or for a specific
- * tab given as first parameter.
- *
- * @param [optional] object filter
- * A dictionary object with following optional attributes:
- * - outerWindowID: used to match tabs in parent process
- * - tabId: used to match tabs in child processes
- * - tab: a reference to xul:tab element
- * If nothing is specified, returns the actor for the currently
- * selected tab.
- */
- getTab: function (filter) {
- let packet = {
- to: this.actor,
- type: "getTab"
- };
-
- if (filter) {
- if (typeof (filter.outerWindowID) == "number") {
- packet.outerWindowID = filter.outerWindowID;
- } else if (typeof (filter.tabId) == "number") {
- packet.tabId = filter.tabId;
- } else if ("tab" in filter) {
- let browser = filter.tab.linkedBrowser;
- if (browser.frameLoader.tabParent) {
- // Tabs in child process
- packet.tabId = browser.frameLoader.tabParent.tabId;
- } else if (browser.outerWindowID) {
- // <xul:browser> tabs in parent process
- packet.outerWindowID = browser.outerWindowID;
- } else {
- // <iframe mozbrowser> tabs in parent process
- let windowUtils = browser.contentWindow
- .QueryInterface(Ci.nsIInterfaceRequestor)
- .getInterface(Ci.nsIDOMWindowUtils);
- packet.outerWindowID = windowUtils.outerWindowID;
- }
- } else {
- // Throw if a filter object have been passed but without
- // any clearly idenfified filter.
- throw new Error("Unsupported argument given to getTab request");
- }
- }
-
- return this.request(packet);
- },
-
- /**
- * Fetch the WindowActor for a specific window, like a browser window in
- * Firefox, but it can be used to reach any window in the process.
- *
- * @param number outerWindowID
- * The outerWindowID of the top level window you are looking for.
- */
- getWindow: function ({ outerWindowID }) {
- if (!outerWindowID) {
- throw new Error("Must specify outerWindowID");
- }
-
- let packet = {
- to: this.actor,
- type: "getWindow",
- outerWindowID,
- };
-
- return this.request(packet);
- },
-
- /**
- * Description of protocol's actors and methods.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- protocolDescription: DebuggerClient.requester({ type: "protocolDescription" }),
-
- /*
- * Methods constructed by DebuggerClient.requester require these forwards
- * on their 'this'.
- */
- get _transport() {
- return this._client._transport;
- },
- get request() {
- return this._client.request;
- }
-};
-
-/**
- * Creates a thread client for the remote debugging protocol server. This client
- * is a front to the thread actor created in the server side, hiding the
- * protocol details in a traditional JavaScript API.
- *
- * @param client DebuggerClient|TabClient
- * The parent of the thread (tab for tab-scoped debuggers, DebuggerClient
- * for chrome debuggers).
- * @param actor string
- * The actor ID for this thread.
- */
-function ThreadClient(client, actor) {
- this._parent = client;
- this.client = client instanceof DebuggerClient ? client : client.client;
- this._actor = actor;
- this._frameCache = [];
- this._scriptCache = {};
- this._pauseGrips = {};
- this._threadGrips = {};
- this.request = this.client.request;
-}
-
-ThreadClient.prototype = {
- _state: "paused",
- get state() {
- return this._state;
- },
- get paused() {
- return this._state === "paused";
- },
-
- _pauseOnExceptions: false,
- _ignoreCaughtExceptions: false,
- _pauseOnDOMEvents: null,
-
- _actor: null,
- get actor() {
- return this._actor;
- },
-
- get _transport() {
- return this.client._transport;
- },
-
- _assertPaused: function (command) {
- if (!this.paused) {
- throw Error(command + " command sent while not paused. Currently " + this._state);
- }
- },
-
- /**
- * Resume a paused thread. If the optional limit parameter is present, then
- * the thread will also pause when that limit is reached.
- *
- * @param [optional] object limit
- * An object with a type property set to the appropriate limit (next,
- * step, or finish) per the remote debugging protocol specification.
- * Use null to specify no limit.
- * @param function onResponse
- * Called with the response packet.
- */
- _doResume: DebuggerClient.requester({
- type: "resume",
- resumeLimit: arg(0)
- }, {
- before: function (packet) {
- this._assertPaused("resume");
-
- // Put the client in a tentative "resuming" state so we can prevent
- // further requests that should only be sent in the paused state.
- this._previousState = this._state;
- this._state = "resuming";
-
- if (this._pauseOnExceptions) {
- packet.pauseOnExceptions = this._pauseOnExceptions;
- }
- if (this._ignoreCaughtExceptions) {
- packet.ignoreCaughtExceptions = this._ignoreCaughtExceptions;
- }
- if (this._pauseOnDOMEvents) {
- packet.pauseOnDOMEvents = this._pauseOnDOMEvents;
- }
- return packet;
- },
- after: function (response) {
- if (response.error && this._state == "resuming") {
- // There was an error resuming, update the state to the new one
- // reported by the server, if given (only on wrongState), otherwise
- // reset back to the previous state.
- if (response.state) {
- this._state = ThreadStateTypes[response.state];
- } else {
- this._state = this._previousState;
- }
- }
- delete this._previousState;
- return response;
- },
- }),
-
- /**
- * Reconfigure the thread actor.
- *
- * @param object options
- * A dictionary object of the new options to use in the thread actor.
- * @param function onResponse
- * Called with the response packet.
- */
- reconfigure: DebuggerClient.requester({
- type: "reconfigure",
- options: arg(0)
- }),
-
- /**
- * Resume a paused thread.
- */
- resume: function (onResponse) {
- return this._doResume(null, onResponse);
- },
-
- /**
- * Resume then pause without stepping.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- resumeThenPause: function (onResponse) {
- return this._doResume({ type: "break" }, onResponse);
- },
-
- /**
- * Step over a function call.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- stepOver: function (onResponse) {
- return this._doResume({ type: "next" }, onResponse);
- },
-
- /**
- * Step into a function call.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- stepIn: function (onResponse) {
- return this._doResume({ type: "step" }, onResponse);
- },
-
- /**
- * Step out of a function call.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- stepOut: function (onResponse) {
- return this._doResume({ type: "finish" }, onResponse);
- },
-
- /**
- * Immediately interrupt a running thread.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- interrupt: function (onResponse) {
- return this._doInterrupt(null, onResponse);
- },
-
- /**
- * Pause execution right before the next JavaScript bytecode is executed.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- breakOnNext: function (onResponse) {
- return this._doInterrupt("onNext", onResponse);
- },
-
- /**
- * Interrupt a running thread.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- _doInterrupt: DebuggerClient.requester({
- type: "interrupt",
- when: arg(0)
- }),
-
- /**
- * Enable or disable pausing when an exception is thrown.
- *
- * @param boolean pauseOnExceptions
- * Enables pausing if true, disables otherwise.
- * @param boolean ignoreCaughtExceptions
- * Whether to ignore caught exceptions
- * @param function onResponse
- * Called with the response packet.
- */
- pauseOnExceptions: function (pauseOnExceptions,
- ignoreCaughtExceptions,
- onResponse = noop) {
- this._pauseOnExceptions = pauseOnExceptions;
- this._ignoreCaughtExceptions = ignoreCaughtExceptions;
-
- // Otherwise send the flag using a standard resume request.
- if (!this.paused) {
- return this.interrupt(response => {
- if (response.error) {
- // Can't continue if pausing failed.
- onResponse(response);
- return response;
- }
- return this.resume(onResponse);
- });
- }
-
- onResponse();
- return promise.resolve();
- },
-
- /**
- * Enable pausing when the specified DOM events are triggered. Disabling
- * pausing on an event can be realized by calling this method with the updated
- * array of events that doesn't contain it.
- *
- * @param array|string events
- * An array of strings, representing the DOM event types to pause on,
- * or "*" to pause on all DOM events. Pass an empty array to
- * completely disable pausing on DOM events.
- * @param function onResponse
- * Called with the response packet in a future turn of the event loop.
- */
- pauseOnDOMEvents: function (events, onResponse = noop) {
- this._pauseOnDOMEvents = events;
- // If the debuggee is paused, the value of the array will be communicated in
- // the next resumption. Otherwise we have to force a pause in order to send
- // the array.
- if (this.paused) {
- DevToolsUtils.executeSoon(() => onResponse({}));
- return {};
- }
- return this.interrupt(response => {
- // Can't continue if pausing failed.
- if (response.error) {
- onResponse(response);
- return response;
- }
- return this.resume(onResponse);
- });
- },
-
- /**
- * Send a clientEvaluate packet to the debuggee. Response
- * will be a resume packet.
- *
- * @param string frame
- * The actor ID of the frame where the evaluation should take place.
- * @param string expression
- * The expression that will be evaluated in the scope of the frame
- * above.
- * @param function onResponse
- * Called with the response packet.
- */
- eval: DebuggerClient.requester({
- type: "clientEvaluate",
- frame: arg(0),
- expression: arg(1)
- }, {
- before: function (packet) {
- this._assertPaused("eval");
- // Put the client in a tentative "resuming" state so we can prevent
- // further requests that should only be sent in the paused state.
- this._state = "resuming";
- return packet;
- },
- after: function (response) {
- if (response.error) {
- // There was an error resuming, back to paused state.
- this._state = "paused";
- }
- return response;
- },
- }),
-
- /**
- * Detach from the thread actor.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- detach: DebuggerClient.requester({
- type: "detach"
- }, {
- after: function (response) {
- this.client.unregisterClient(this);
- this._parent.thread = null;
- return response;
- },
- }),
-
- /**
- * Release multiple thread-lifetime object actors. If any pause-lifetime
- * actors are included in the request, a |notReleasable| error will return,
- * but all the thread-lifetime ones will have been released.
- *
- * @param array actors
- * An array with actor IDs to release.
- */
- releaseMany: DebuggerClient.requester({
- type: "releaseMany",
- actors: arg(0),
- }),
-
- /**
- * Promote multiple pause-lifetime object actors to thread-lifetime ones.
- *
- * @param array actors
- * An array with actor IDs to promote.
- */
- threadGrips: DebuggerClient.requester({
- type: "threadGrips",
- actors: arg(0)
- }),
-
- /**
- * Return the event listeners defined on the page.
- *
- * @param onResponse Function
- * Called with the thread's response.
- */
- eventListeners: DebuggerClient.requester({
- type: "eventListeners"
- }),
-
- /**
- * Request the loaded sources for the current thread.
- *
- * @param onResponse Function
- * Called with the thread's response.
- */
- getSources: DebuggerClient.requester({
- type: "sources"
- }),
-
- /**
- * Clear the thread's source script cache. A scriptscleared event
- * will be sent.
- */
- _clearScripts: function () {
- if (Object.keys(this._scriptCache).length > 0) {
- this._scriptCache = {};
- this.emit("scriptscleared");
- }
- },
-
- /**
- * Request frames from the callstack for the current thread.
- *
- * @param start integer
- * The number of the youngest stack frame to return (the youngest
- * frame is 0).
- * @param count integer
- * The maximum number of frames to return, or null to return all
- * frames.
- * @param onResponse function
- * Called with the thread's response.
- */
- getFrames: DebuggerClient.requester({
- type: "frames",
- start: arg(0),
- count: arg(1)
- }),
-
- /**
- * An array of cached frames. Clients can observe the framesadded and
- * framescleared event to keep up to date on changes to this cache,
- * and can fill it using the fillFrames method.
- */
- get cachedFrames() {
- return this._frameCache;
- },
-
- /**
- * true if there are more stack frames available on the server.
- */
- get moreFrames() {
- return this.paused && (!this._frameCache || this._frameCache.length == 0
- || !this._frameCache[this._frameCache.length - 1].oldest);
- },
-
- /**
- * Request the frame environment.
- *
- * @param frameId string
- */
- getEnvironment: function (frameId) {
- return this.request({ to: frameId, type: "getEnvironment" });
- },
-
- /**
- * Ensure that at least total stack frames have been loaded in the
- * ThreadClient's stack frame cache. A framesadded event will be
- * sent when the stack frame cache is updated.
- *
- * @param total number
- * The minimum number of stack frames to be included.
- * @param callback function
- * Optional callback function called when frames have been loaded
- * @returns true if a framesadded notification should be expected.
- */
- fillFrames: function (total, callback = noop) {
- this._assertPaused("fillFrames");
- if (this._frameCache.length >= total) {
- return false;
- }
-
- let numFrames = this._frameCache.length;
-
- this.getFrames(numFrames, total - numFrames, (response) => {
- if (response.error) {
- callback(response);
- return;
- }
-
- let threadGrips = DevToolsUtils.values(this._threadGrips);
-
- for (let i in response.frames) {
- let frame = response.frames[i];
- if (!frame.where.source) {
- // Older servers use urls instead, so we need to resolve
- // them to source actors
- for (let grip of threadGrips) {
- if (grip instanceof SourceClient && grip.url === frame.url) {
- frame.where.source = grip._form;
- }
- }
- }
-
- this._frameCache[frame.depth] = frame;
- }
-
- // If we got as many frames as we asked for, there might be more
- // frames available.
- this.emit("framesadded");
-
- callback(response);
- });
-
- return true;
- },
-
- /**
- * Clear the thread's stack frame cache. A framescleared event
- * will be sent.
- */
- _clearFrames: function () {
- if (this._frameCache.length > 0) {
- this._frameCache = [];
- this.emit("framescleared");
- }
- },
-
- /**
- * Return a ObjectClient object for the given object grip.
- *
- * @param grip object
- * A pause-lifetime object grip returned by the protocol.
- */
- pauseGrip: function (grip) {
- if (grip.actor in this._pauseGrips) {
- return this._pauseGrips[grip.actor];
- }
-
- let client = new ObjectClient(this.client, grip);
- this._pauseGrips[grip.actor] = client;
- return client;
- },
-
- /**
- * Get or create a long string client, checking the grip client cache if it
- * already exists.
- *
- * @param grip Object
- * The long string grip returned by the protocol.
- * @param gripCacheName String
- * The property name of the grip client cache to check for existing
- * clients in.
- */
- _longString: function (grip, gripCacheName) {
- if (grip.actor in this[gripCacheName]) {
- return this[gripCacheName][grip.actor];
- }
-
- let client = new LongStringClient(this.client, grip);
- this[gripCacheName][grip.actor] = client;
- return client;
- },
-
- /**
- * Return an instance of LongStringClient for the given long string grip that
- * is scoped to the current pause.
- *
- * @param grip Object
- * The long string grip returned by the protocol.
- */
- pauseLongString: function (grip) {
- return this._longString(grip, "_pauseGrips");
- },
-
- /**
- * Return an instance of LongStringClient for the given long string grip that
- * is scoped to the thread lifetime.
- *
- * @param grip Object
- * The long string grip returned by the protocol.
- */
- threadLongString: function (grip) {
- return this._longString(grip, "_threadGrips");
- },
-
- /**
- * Get or create an ArrayBuffer client, checking the grip client cache if it
- * already exists.
- *
- * @param grip Object
- * The ArrayBuffer grip returned by the protocol.
- * @param gripCacheName String
- * The property name of the grip client cache to check for existing
- * clients in.
- */
- _arrayBuffer: function (grip, gripCacheName) {
- if (grip.actor in this[gripCacheName]) {
- return this[gripCacheName][grip.actor];
- }
-
- let client = new ArrayBufferClient(this.client, grip);
- this[gripCacheName][grip.actor] = client;
- return client;
- },
-
- /**
- * Return an instance of ArrayBufferClient for the given ArrayBuffer grip that
- * is scoped to the thread lifetime.
- *
- * @param grip Object
- * The ArrayBuffer grip returned by the protocol.
- */
- threadArrayBuffer: function (grip) {
- return this._arrayBuffer(grip, "_threadGrips");
- },
-
- /**
- * Clear and invalidate all the grip clients from the given cache.
- *
- * @param gripCacheName
- * The property name of the grip cache we want to clear.
- */
- _clearObjectClients: function (gripCacheName) {
- for (let id in this[gripCacheName]) {
- this[gripCacheName][id].valid = false;
- }
- this[gripCacheName] = {};
- },
-
- /**
- * Invalidate pause-lifetime grip clients and clear the list of current grip
- * clients.
- */
- _clearPauseGrips: function () {
- this._clearObjectClients("_pauseGrips");
- },
-
- /**
- * Invalidate thread-lifetime grip clients and clear the list of current grip
- * clients.
- */
- _clearThreadGrips: function () {
- this._clearObjectClients("_threadGrips");
- },
-
- /**
- * Handle thread state change by doing necessary cleanup and notifying all
- * registered listeners.
- */
- _onThreadState: function (packet) {
- this._state = ThreadStateTypes[packet.type];
- // The debugger UI may not be initialized yet so we want to keep
- // the packet around so it knows what to pause state to display
- // when it's initialized
- this._lastPausePacket = packet.type === "resumed" ? null : packet;
- this._clearFrames();
- this._clearPauseGrips();
- packet.type === ThreadStateTypes.detached && this._clearThreadGrips();
- this.client._eventsEnabled && this.emit(packet.type, packet);
- },
-
- getLastPausePacket: function () {
- return this._lastPausePacket;
- },
-
- /**
- * Return an EnvironmentClient instance for the given environment actor form.
- */
- environment: function (form) {
- return new EnvironmentClient(this.client, form);
- },
-
- /**
- * Return an instance of SourceClient for the given source actor form.
- */
- source: function (form) {
- if (form.actor in this._threadGrips) {
- return this._threadGrips[form.actor];
- }
-
- this._threadGrips[form.actor] = new SourceClient(this, form);
- return this._threadGrips[form.actor];
- },
-
- /**
- * Request the prototype and own properties of mutlipleObjects.
- *
- * @param onResponse function
- * Called with the request's response.
- * @param actors [string]
- * List of actor ID of the queried objects.
- */
- getPrototypesAndProperties: DebuggerClient.requester({
- type: "prototypesAndProperties",
- actors: arg(0)
- }),
-
- events: ["newSource"]
-};
-
-eventSource(ThreadClient.prototype);
-
-/**
- * Creates a tracing profiler client for the remote debugging protocol
- * server. This client is a front to the trace actor created on the
- * server side, hiding the protocol details in a traditional
- * JavaScript API.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param actor string
- * The actor ID for this thread.
- */
-function TraceClient(client, actor) {
- this._client = client;
- this._actor = actor;
- this._activeTraces = new Set();
- this._waitingPackets = new Map();
- this._expectedPacket = 0;
- this.request = this._client.request;
- this.events = [];
-}
-
-TraceClient.prototype = {
- get actor() {
- return this._actor;
- },
- get tracing() {
- return this._activeTraces.size > 0;
- },
-
- get _transport() {
- return this._client._transport;
- },
-
- /**
- * Detach from the trace actor.
- */
- detach: DebuggerClient.requester({
- type: "detach"
- }, {
- after: function (response) {
- this._client.unregisterClient(this);
- return response;
- },
- }),
-
- /**
- * Start a new trace.
- *
- * @param trace [string]
- * An array of trace types to be recorded by the new trace.
- *
- * @param name string
- * The name of the new trace.
- *
- * @param onResponse function
- * Called with the request's response.
- */
- startTrace: DebuggerClient.requester({
- type: "startTrace",
- name: arg(1),
- trace: arg(0)
- }, {
- after: function (response) {
- if (response.error) {
- return response;
- }
-
- if (!this.tracing) {
- this._waitingPackets.clear();
- this._expectedPacket = 0;
- }
- this._activeTraces.add(response.name);
-
- return response;
- },
- }),
-
- /**
- * End a trace. If a name is provided, stop the named
- * trace. Otherwise, stop the most recently started trace.
- *
- * @param name string
- * The name of the trace to stop.
- *
- * @param onResponse function
- * Called with the request's response.
- */
- stopTrace: DebuggerClient.requester({
- type: "stopTrace",
- name: arg(0)
- }, {
- after: function (response) {
- if (response.error) {
- return response;
- }
-
- this._activeTraces.delete(response.name);
-
- return response;
- },
- })
-};
-
-/**
- * Grip clients are used to retrieve information about the relevant object.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param grip object
- * A pause-lifetime object grip returned by the protocol.
- */
-function ObjectClient(client, grip) {
- this._grip = grip;
- this._client = client;
- this.request = this._client.request;
-}
-exports.ObjectClient = ObjectClient;
-
-ObjectClient.prototype = {
- get actor() {
- return this._grip.actor;
- },
- get _transport() {
- return this._client._transport;
- },
-
- valid: true,
-
- get isFrozen() {
- return this._grip.frozen;
- },
- get isSealed() {
- return this._grip.sealed;
- },
- get isExtensible() {
- return this._grip.extensible;
- },
-
- getDefinitionSite: DebuggerClient.requester({
- type: "definitionSite"
- }, {
- before: function (packet) {
- if (this._grip.class != "Function") {
- throw new Error("getDefinitionSite is only valid for function grips.");
- }
- return packet;
- }
- }),
-
- /**
- * Request the names of a function's formal parameters.
- *
- * @param onResponse function
- * Called with an object of the form:
- * { parameterNames:[<parameterName>, ...] }
- * where each <parameterName> is the name of a parameter.
- */
- getParameterNames: DebuggerClient.requester({
- type: "parameterNames"
- }, {
- before: function (packet) {
- if (this._grip.class !== "Function") {
- throw new Error("getParameterNames is only valid for function grips.");
- }
- return packet;
- },
- }),
-
- /**
- * Request the names of the properties defined on the object and not its
- * prototype.
- *
- * @param onResponse function Called with the request's response.
- */
- getOwnPropertyNames: DebuggerClient.requester({
- type: "ownPropertyNames"
- }),
-
- /**
- * Request the prototype and own properties of the object.
- *
- * @param onResponse function Called with the request's response.
- */
- getPrototypeAndProperties: DebuggerClient.requester({
- type: "prototypeAndProperties"
- }),
-
- /**
- * Request a PropertyIteratorClient instance to ease listing
- * properties for this object.
- *
- * @param options Object
- * A dictionary object with various boolean attributes:
- * - ignoreIndexedProperties Boolean
- * If true, filters out Array items.
- * e.g. properties names between `0` and `object.length`.
- * - ignoreNonIndexedProperties Boolean
- * If true, filters out items that aren't array items
- * e.g. properties names that are not a number between `0`
- * and `object.length`.
- * - sort Boolean
- * If true, the iterator will sort the properties by name
- * before dispatching them.
- * @param onResponse function Called with the client instance.
- */
- enumProperties: DebuggerClient.requester({
- type: "enumProperties",
- options: arg(0)
- }, {
- after: function (response) {
- if (response.iterator) {
- return { iterator: new PropertyIteratorClient(this._client, response.iterator) };
- }
- return response;
- },
- }),
-
- /**
- * Request a PropertyIteratorClient instance to enumerate entries in a
- * Map/Set-like object.
- *
- * @param onResponse function Called with the request's response.
- */
- enumEntries: DebuggerClient.requester({
- type: "enumEntries"
- }, {
- before: function (packet) {
- if (!["Map", "WeakMap", "Set", "WeakSet"].includes(this._grip.class)) {
- throw new Error("enumEntries is only valid for Map/Set-like grips.");
- }
- return packet;
- },
- after: function (response) {
- if (response.iterator) {
- return {
- iterator: new PropertyIteratorClient(this._client, response.iterator)
- };
- }
- return response;
- }
- }),
-
- /**
- * Request a SymbolIteratorClient instance to enumerate symbols in an object.
- *
- * @param onResponse function Called with the request's response.
- */
- enumSymbols: DebuggerClient.requester({
- type: "enumSymbols"
- }, {
- before: function (packet) {
- if (this._grip.type !== "object") {
- throw new Error("enumSymbols is only valid for objects grips.");
- }
- return packet;
- },
- after: function (response) {
- if (response.iterator) {
- return {
- iterator: new SymbolIteratorClient(this._client, response.iterator)
- };
- }
- return response;
- }
- }),
-
- /**
- * Request the property descriptor of the object's specified property.
- *
- * @param name string The name of the requested property.
- * @param onResponse function Called with the request's response.
- */
- getProperty: DebuggerClient.requester({
- type: "property",
- name: arg(0)
- }),
-
- /**
- * Request the prototype of the object.
- *
- * @param onResponse function Called with the request's response.
- */
- getPrototype: DebuggerClient.requester({
- type: "prototype"
- }),
-
- /**
- * Request the display string of the object.
- *
- * @param onResponse function Called with the request's response.
- */
- getDisplayString: DebuggerClient.requester({
- type: "displayString"
- }),
-
- /**
- * Request the scope of the object.
- *
- * @param onResponse function Called with the request's response.
- */
- getScope: DebuggerClient.requester({
- type: "scope"
- }, {
- before: function (packet) {
- if (this._grip.class !== "Function") {
- throw new Error("scope is only valid for function grips.");
- }
- return packet;
- },
- }),
-
- /**
- * Request the promises directly depending on the current promise.
- */
- getDependentPromises: DebuggerClient.requester({
- type: "dependentPromises"
- }, {
- before: function (packet) {
- if (this._grip.class !== "Promise") {
- throw new Error("getDependentPromises is only valid for promise " +
- "grips.");
- }
- return packet;
- }
- }),
-
- /**
- * Request the stack to the promise's allocation point.
- */
- getPromiseAllocationStack: DebuggerClient.requester({
- type: "allocationStack"
- }, {
- before: function (packet) {
- if (this._grip.class !== "Promise") {
- throw new Error("getAllocationStack is only valid for promise grips.");
- }
- return packet;
- }
- }),
-
- /**
- * Request the stack to the promise's fulfillment point.
- */
- getPromiseFulfillmentStack: DebuggerClient.requester({
- type: "fulfillmentStack"
- }, {
- before: function (packet) {
- if (this._grip.class !== "Promise") {
- throw new Error("getPromiseFulfillmentStack is only valid for " +
- "promise grips.");
- }
- return packet;
- }
- }),
-
- /**
- * Request the stack to the promise's rejection point.
- */
- getPromiseRejectionStack: DebuggerClient.requester({
- type: "rejectionStack"
- }, {
- before: function (packet) {
- if (this._grip.class !== "Promise") {
- throw new Error("getPromiseRejectionStack is only valid for " +
- "promise grips.");
- }
- return packet;
- }
- })
-};
-
-/**
- * A PropertyIteratorClient provides a way to access to property names and
- * values of an object efficiently, slice by slice.
- * Note that the properties can be sorted in the backend,
- * this is controled while creating the PropertyIteratorClient
- * from ObjectClient.enumProperties.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param grip Object
- * A PropertyIteratorActor grip returned by the protocol via
- * TabActor.enumProperties request.
- */
-function PropertyIteratorClient(client, grip) {
- this._grip = grip;
- this._client = client;
- this.request = this._client.request;
-}
-
-PropertyIteratorClient.prototype = {
- get actor() {
- return this._grip.actor;
- },
-
- /**
- * Get the total number of properties available in the iterator.
- */
- get count() {
- return this._grip.count;
- },
-
- /**
- * Get one or more property names that correspond to the positions in the
- * indexes parameter.
- *
- * @param indexes Array
- * An array of property indexes.
- * @param callback Function
- * The function called when we receive the property names.
- */
- names: DebuggerClient.requester({
- type: "names",
- indexes: arg(0)
- }, {}),
-
- /**
- * Get a set of following property value(s).
- *
- * @param start Number
- * The index of the first property to fetch.
- * @param count Number
- * The number of properties to fetch.
- * @param callback Function
- * The function called when we receive the property values.
- */
- slice: DebuggerClient.requester({
- type: "slice",
- start: arg(0),
- count: arg(1)
- }, {}),
-
- /**
- * Get all the property values.
- *
- * @param callback Function
- * The function called when we receive the property values.
- */
- all: DebuggerClient.requester({
- type: "all"
- }, {}),
-};
-
-/**
- * A SymbolIteratorClient provides a way to access to symbols
- * of an object efficiently, slice by slice.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param grip Object
- * A SymbolIteratorActor grip returned by the protocol via
- * TabActor.enumSymbols request.
- */
-function SymbolIteratorClient(client, grip) {
- this._grip = grip;
- this._client = client;
- this.request = this._client.request;
-}
-
-SymbolIteratorClient.prototype = {
- get actor() {
- return this._grip.actor;
- },
-
- /**
- * Get the total number of symbols available in the iterator.
- */
- get count() {
- return this._grip.count;
- },
-
- /**
- * Get a set of following symbols.
- *
- * @param start Number
- * The index of the first symbol to fetch.
- * @param count Number
- * The number of symbols to fetch.
- * @param callback Function
- * The function called when we receive the symbols.
- */
- slice: DebuggerClient.requester({
- type: "slice",
- start: arg(0),
- count: arg(1)
- }, {}),
-
- /**
- * Get all the symbols.
- *
- * @param callback Function
- * The function called when we receive the symbols.
- */
- all: DebuggerClient.requester({
- type: "all"
- }, {}),
-};
-
-/**
- * A ArrayBufferClient provides a way to access ArrayBuffer from the
- * debugger server.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param grip Object
- * A pause-lifetime ArrayBuffer grip returned by the protocol.
- */
-function ArrayBufferClient(client, grip) {
- this._grip = grip;
- this._client = client;
- this.request = this._client.request;
-}
-ArrayBufferClient.prototype = {
- get actor() {
- return this._grip.actor;
- },
- get length() {
- return this._grip.length;
- },
- get _transport() {
- return this._client._transport;
- },
-
- valid: true,
-
- slice: DebuggerClient.requester({
- type: "slice",
- start: arg(0),
- count: arg(1)
- }),
-};
-
-/**
- * A LongStringClient provides a way to access "very long" strings from the
- * debugger server.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param grip Object
- * A pause-lifetime long string grip returned by the protocol.
- */
-function LongStringClient(client, grip) {
- this._grip = grip;
- this._client = client;
- this.request = this._client.request;
-}
-exports.LongStringClient = LongStringClient;
-
-LongStringClient.prototype = {
- get actor() {
- return this._grip.actor;
- },
- get length() {
- return this._grip.length;
- },
- get initial() {
- return this._grip.initial;
- },
- get _transport() {
- return this._client._transport;
- },
-
- valid: true,
-
- /**
- * Get the substring of this LongString from start to end.
- *
- * @param start Number
- * The starting index.
- * @param end Number
- * The ending index.
- * @param callback Function
- * The function called when we receive the substring.
- */
- substring: DebuggerClient.requester({
- type: "substring",
- start: arg(0),
- end: arg(1)
- }),
-};
-
-/**
- * A SourceClient provides a way to access the source text of a script.
- *
- * @param client ThreadClient
- * The thread client parent.
- * @param form Object
- * The form sent across the remote debugging protocol.
- */
-function SourceClient(client, form) {
- this._form = form;
- this._isBlackBoxed = form.isBlackBoxed;
- this._isPrettyPrinted = form.isPrettyPrinted;
- this._activeThread = client;
- this._client = client.client;
-}
-
-SourceClient.prototype = {
- get _transport() {
- return this._client._transport;
- },
- get isBlackBoxed() {
- return this._isBlackBoxed;
- },
- get isPrettyPrinted() {
- return this._isPrettyPrinted;
- },
- get actor() {
- return this._form.actor;
- },
- get request() {
- return this._client.request;
- },
- get url() {
- return this._form.url;
- },
-
- /**
- * Black box this SourceClient's source.
- *
- * @param callback Function
- * The callback function called when we receive the response from the server.
- */
- blackBox: DebuggerClient.requester({
- type: "blackbox"
- }, {
- after: function (response) {
- if (!response.error) {
- this._isBlackBoxed = true;
- if (this._activeThread) {
- this._activeThread.emit("blackboxchange", this);
- }
- }
- return response;
- }
- }),
-
- /**
- * Un-black box this SourceClient's source.
- *
- * @param callback Function
- * The callback function called when we receive the response from the server.
- */
- unblackBox: DebuggerClient.requester({
- type: "unblackbox"
- }, {
- after: function (response) {
- if (!response.error) {
- this._isBlackBoxed = false;
- if (this._activeThread) {
- this._activeThread.emit("blackboxchange", this);
- }
- }
- return response;
- }
- }),
-
- /**
- * Get Executable Lines from a source
- *
- * @param callback Function
- * The callback function called when we receive the response from the server.
- */
- getExecutableLines: function (cb = noop) {
- let packet = {
- to: this._form.actor,
- type: "getExecutableLines"
- };
-
- return this._client.request(packet).then(res => {
- cb(res.lines);
- return res.lines;
- });
- },
-
- /**
- * Get a long string grip for this SourceClient's source.
- */
- source: function (callback = noop) {
- let packet = {
- to: this._form.actor,
- type: "source"
- };
- return this._client.request(packet).then(response => {
- return this._onSourceResponse(response, callback);
- });
- },
-
- /**
- * Pretty print this source's text.
- */
- prettyPrint: function (indent, callback = noop) {
- const packet = {
- to: this._form.actor,
- type: "prettyPrint",
- indent
- };
- return this._client.request(packet).then(response => {
- if (!response.error) {
- this._isPrettyPrinted = true;
- this._activeThread._clearFrames();
- this._activeThread.emit("prettyprintchange", this);
- }
- return this._onSourceResponse(response, callback);
- });
- },
-
- /**
- * Stop pretty printing this source's text.
- */
- disablePrettyPrint: function (callback = noop) {
- const packet = {
- to: this._form.actor,
- type: "disablePrettyPrint"
- };
- return this._client.request(packet).then(response => {
- if (!response.error) {
- this._isPrettyPrinted = false;
- this._activeThread._clearFrames();
- this._activeThread.emit("prettyprintchange", this);
- }
- return this._onSourceResponse(response, callback);
- });
- },
-
- _onSourceResponse: function (response, callback) {
- if (response.error) {
- callback(response);
- return response;
- }
-
- if (typeof response.source === "string") {
- callback(response);
- return response;
- }
-
- let { contentType, source } = response;
- if (source.type === "arrayBuffer") {
- let arrayBuffer = this._activeThread.threadArrayBuffer(source);
- return arrayBuffer.slice(0, arrayBuffer.length).then(function (resp) {
- if (resp.error) {
- callback(resp);
- return resp;
- }
- // Keeping str as a string, ArrayBuffer/Uint8Array will not survive
- // setIn/mergeIn operations.
- const str = atob(resp.encoded);
- let newResponse = {
- source: {
- binary: str,
- toString: () => "[wasm]",
- },
- contentType,
- };
- callback(newResponse);
- return newResponse;
- });
- }
-
- let longString = this._activeThread.threadLongString(source);
- return longString.substring(0, longString.length).then(function (resp) {
- if (resp.error) {
- callback(resp);
- return resp;
- }
-
- let newResponse = {
- source: resp.substring,
- contentType: contentType
- };
- callback(newResponse);
- return newResponse;
- });
- },
-
- /**
- * Request to set a breakpoint in the specified location.
- *
- * @param object location
- * The location and condition of the breakpoint in
- * the form of { line[, column, condition] }.
- * @param function onResponse
- * Called with the thread's response.
- */
- setBreakpoint: function ({ line, column, condition, noSliding }, onResponse = noop) {
- // A helper function that sets the breakpoint.
- let doSetBreakpoint = callback => {
- let root = this._client.mainRoot;
- let location = {
- line,
- column,
- };
-
- let packet = {
- to: this.actor,
- type: "setBreakpoint",
- location,
- condition,
- noSliding,
- };
-
- // Backwards compatibility: send the breakpoint request to the
- // thread if the server doesn't support Debugger.Source actors.
- if (!root.traits.debuggerSourceActors) {
- packet.to = this._activeThread.actor;
- packet.location.url = this.url;
- }
-
- return this._client.request(packet).then(response => {
- // Ignoring errors, since the user may be setting a breakpoint in a
- // dead script that will reappear on a page reload.
- let bpClient;
- if (response.actor) {
- bpClient = new BreakpointClient(
- this._client,
- this,
- response.actor,
- location,
- root.traits.conditionalBreakpoints ? condition : undefined
- );
- }
- onResponse(response, bpClient);
- if (callback) {
- callback();
- }
- return [response, bpClient];
- });
- };
-
- // If the debuggee is paused, just set the breakpoint.
- if (this._activeThread.paused) {
- return doSetBreakpoint();
- }
- // Otherwise, force a pause in order to set the breakpoint.
- return this._activeThread.interrupt().then(response => {
- if (response.error) {
- // Can't set the breakpoint if pausing failed.
- onResponse(response);
- return response;
- }
-
- const { type, why } = response;
- const cleanUp = type == "paused" && why.type == "interrupted"
- ? () => this._activeThread.resume()
- : noop;
-
- return doSetBreakpoint(cleanUp);
- });
- }
-};
-
-/**
- * Breakpoint clients are used to remove breakpoints that are no longer used.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param sourceClient SourceClient
- * The source where this breakpoint exists
- * @param actor string
- * The actor ID for this breakpoint.
- * @param location object
- * The location of the breakpoint. This is an object with two properties:
- * url and line.
- * @param condition string
- * The conditional expression of the breakpoint
- */
-function BreakpointClient(client, sourceClient, actor, location, condition) {
- this._client = client;
- this._actor = actor;
- this.location = location;
- this.location.actor = sourceClient.actor;
- this.location.url = sourceClient.url;
- this.source = sourceClient;
- this.request = this._client.request;
-
- // The condition property should only exist if it's a truthy value
- if (condition) {
- this.condition = condition;
- }
-}
-
-BreakpointClient.prototype = {
-
- _actor: null,
- get actor() {
- return this._actor;
- },
- get _transport() {
- return this._client._transport;
- },
-
- /**
- * Remove the breakpoint from the server.
- */
- remove: DebuggerClient.requester({
- type: "delete"
- }),
-
- /**
- * Determines if this breakpoint has a condition
- */
- hasCondition: function () {
- let root = this._client.mainRoot;
- // XXX bug 990137: We will remove support for client-side handling of
- // conditional breakpoints
- if (root.traits.conditionalBreakpoints) {
- return "condition" in this;
- }
- return "conditionalExpression" in this;
- },
-
- /**
- * Get the condition of this breakpoint. Currently we have to
- * support locally emulated conditional breakpoints until the
- * debugger servers are updated (see bug 990137). We used a
- * different property when moving it server-side to ensure that we
- * are testing the right code.
- */
- getCondition: function () {
- let root = this._client.mainRoot;
- if (root.traits.conditionalBreakpoints) {
- return this.condition;
- }
- return this.conditionalExpression;
- },
-
- /**
- * Set the condition of this breakpoint
- */
- setCondition: function (gThreadClient, condition) {
- let root = this._client.mainRoot;
- let deferred = promise.defer();
-
- if (root.traits.conditionalBreakpoints) {
- let info = {
- line: this.location.line,
- column: this.location.column,
- condition: condition
- };
-
- // Remove the current breakpoint and add a new one with the
- // condition.
- this.remove(response => {
- if (response && response.error) {
- deferred.reject(response);
- return;
- }
-
- this.source.setBreakpoint(info, (resp, newBreakpoint) => {
- if (resp && resp.error) {
- deferred.reject(resp);
- } else {
- deferred.resolve(newBreakpoint);
- }
- });
- });
- } else {
- // The property shouldn't even exist if the condition is blank
- if (condition === "") {
- delete this.conditionalExpression;
- } else {
- this.conditionalExpression = condition;
- }
- deferred.resolve(this);
- }
-
- return deferred.promise;
- }
-};
-
-eventSource(BreakpointClient.prototype);
-
-/**
- * Environment clients are used to manipulate the lexical environment actors.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param form Object
- * The form sent across the remote debugging protocol.
- */
-function EnvironmentClient(client, form) {
- this._client = client;
- this._form = form;
- this.request = this._client.request;
-}
-exports.EnvironmentClient = EnvironmentClient;
-
-EnvironmentClient.prototype = {
-
- get actor() {
- return this._form.actor;
- },
- get _transport() {
- return this._client._transport;
- },
-
- /**
- * Fetches the bindings introduced by this lexical environment.
- */
- getBindings: DebuggerClient.requester({
- type: "bindings"
- }),
-
- /**
- * Changes the value of the identifier whose name is name (a string) to that
- * represented by value (a grip).
- */
- assign: DebuggerClient.requester({
- type: "assign",
- name: arg(0),
- value: arg(1)
- })
-};
-
-eventSource(EnvironmentClient.prototype);
+module.exports = AddonClient;
copy from devtools/shared/client/main.js
copy to devtools/shared/client/array-buffer-client.js
--- a/devtools/shared/client/main.js
+++ b/devtools/shared/client/array-buffer-client.js
@@ -1,2916 +1,15 @@
-/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
-/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
-const { Ci, Cu } = require("chrome");
-const DevToolsUtils = require("devtools/shared/DevToolsUtils");
-const { getStack, callFunctionWithAsyncStack } = require("devtools/shared/platform/stack");
-
-const promise = Cu.import("resource://devtools/shared/deprecated-sync-thenables.js", {}).Promise;
-
-loader.lazyRequireGetter(this, "EventEmitter", "devtools/shared/event-emitter");
-loader.lazyRequireGetter(this, "WebConsoleClient", "devtools/shared/webconsole/client", true);
-loader.lazyRequireGetter(this, "DebuggerSocket", "devtools/shared/security/socket", true);
-loader.lazyRequireGetter(this, "Authentication", "devtools/shared/security/auth");
-
-const noop = () => {};
-
-/**
- * TODO: Get rid of this API in favor of EventTarget (bug 1042642)
- *
- * Add simple event notification to a prototype object. Any object that has
- * some use for event notifications or the observer pattern in general can be
- * augmented with the necessary facilities by passing its prototype to this
- * function.
- *
- * @param proto object
- * The prototype object that will be modified.
- */
-function eventSource(proto) {
- /**
- * Add a listener to the event source for a given event.
- *
- * @param name string
- * The event to listen for.
- * @param listener function
- * Called when the event is fired. If the same listener
- * is added more than once, it will be called once per
- * addListener call.
- */
- proto.addListener = function (name, listener) {
- if (typeof listener != "function") {
- throw TypeError("Listeners must be functions.");
- }
-
- if (!this._listeners) {
- this._listeners = {};
- }
-
- this._getListeners(name).push(listener);
- };
-
- /**
- * Add a listener to the event source for a given event. The
- * listener will be removed after it is called for the first time.
- *
- * @param name string
- * The event to listen for.
- * @param listener function
- * Called when the event is fired.
- */
- proto.addOneTimeListener = function (name, listener) {
- let l = (...args) => {
- this.removeListener(name, l);
- listener.apply(null, args);
- };
- this.addListener(name, l);
- };
-
- /**
- * Remove a listener from the event source previously added with
- * addListener().
- *
- * @param name string
- * The event name used during addListener to add the listener.
- * @param listener function
- * The callback to remove. If addListener was called multiple
- * times, all instances will be removed.
- */
- proto.removeListener = function (name, listener) {
- if (!this._listeners || (listener && !this._listeners[name])) {
- return;
- }
-
- if (!listener) {
- this._listeners[name] = [];
- } else {
- this._listeners[name] =
- this._listeners[name].filter(l => l != listener);
- }
- };
-
- /**
- * Returns the listeners for the specified event name. If none are defined it
- * initializes an empty list and returns that.
- *
- * @param name string
- * The event name.
- */
- proto._getListeners = function (name) {
- if (name in this._listeners) {
- return this._listeners[name];
- }
- this._listeners[name] = [];
- return this._listeners[name];
- };
-
- /**
- * Notify listeners of an event.
- *
- * @param name string
- * The event to fire.
- * @param arguments
- * All arguments will be passed along to the listeners,
- * including the name argument.
- */
- proto.emit = function () {
- if (!this._listeners) {
- return;
- }
-
- let name = arguments[0];
- let listeners = this._getListeners(name).slice(0);
-
- for (let listener of listeners) {
- try {
- listener.apply(null, arguments);
- } catch (e) {
- // Prevent a bad listener from interfering with the others.
- DevToolsUtils.reportException("notify event '" + name + "'", e);
- }
- }
- };
-}
-
-/**
- * Set of protocol messages that affect thread state, and the
- * state the actor is in after each message.
- */
-const ThreadStateTypes = {
- "paused": "paused",
- "resumed": "attached",
- "detached": "detached",
- "running": "attached"
-};
-
-/**
- * Set of protocol messages that are sent by the server without a prior request
- * by the client.
- */
-const UnsolicitedNotifications = {
- "consoleAPICall": "consoleAPICall",
- "eventNotification": "eventNotification",
- "fileActivity": "fileActivity",
- "lastPrivateContextExited": "lastPrivateContextExited",
- "logMessage": "logMessage",
- "networkEvent": "networkEvent",
- "networkEventUpdate": "networkEventUpdate",
- "newGlobal": "newGlobal",
- "newScript": "newScript",
- "tabDetached": "tabDetached",
- "tabListChanged": "tabListChanged",
- "reflowActivity": "reflowActivity",
- "addonListChanged": "addonListChanged",
- "workerListChanged": "workerListChanged",
- "serviceWorkerRegistrationListChanged": "serviceWorkerRegistrationList",
- "tabNavigated": "tabNavigated",
- "frameUpdate": "frameUpdate",
- "pageError": "pageError",
- "documentLoad": "documentLoad",
- "enteredFrame": "enteredFrame",
- "exitedFrame": "exitedFrame",
- "appOpen": "appOpen",
- "appClose": "appClose",
- "appInstall": "appInstall",
- "appUninstall": "appUninstall",
- "evaluationResult": "evaluationResult",
- "newSource": "newSource",
- "updatedSource": "updatedSource",
- "inspectObject": "inspectObject"
-};
-
-/**
- * Set of pause types that are sent by the server and not as an immediate
- * response to a client request.
- */
-const UnsolicitedPauses = {
- "resumeLimit": "resumeLimit",
- "debuggerStatement": "debuggerStatement",
- "breakpoint": "breakpoint",
- "DOMEvent": "DOMEvent",
- "watchpoint": "watchpoint",
- "exception": "exception"
-};
-
-/**
- * Creates a client for the remote debugging protocol server. This client
- * provides the means to communicate with the server and exchange the messages
- * required by the protocol in a traditional JavaScript API.
- */
-const DebuggerClient = exports.DebuggerClient = function (transport) {
- this._transport = transport;
- this._transport.hooks = this;
-
- // Map actor ID to client instance for each actor type.
- this._clients = new Map();
-
- this._pendingRequests = new Map();
- this._activeRequests = new Map();
- this._eventsEnabled = true;
-
- this.traits = {};
-
- this.request = this.request.bind(this);
- this.localTransport = this._transport.onOutputStreamReady === undefined;
-
- /*
- * As the first thing on the connection, expect a greeting packet from
- * the connection's root actor.
- */
- this.mainRoot = null;
- this.expectReply("root", (packet) => {
- this.mainRoot = new RootClient(this, packet);
- this.emit("connected", packet.applicationType, packet.traits);
- });
-};
-
-/**
- * A declarative helper for defining methods that send requests to the server.
- *
- * @param packetSkeleton
- * The form of the packet to send. Can specify fields to be filled from
- * the parameters by using the |arg| function.
- * @param before
- * The function to call before sending the packet. Is passed the packet,
- * and the return value is used as the new packet. The |this| context is
- * the instance of the client object we are defining a method for.
- * @param after
- * The function to call after the response is received. It is passed the
- * response, and the return value is considered the new response that
- * will be passed to the callback. The |this| context is the instance of
- * the client object we are defining a method for.
- * @return Request
- * The `Request` object that is a Promise object and resolves once
- * we receive the response. (See request method for more details)
- */
-DebuggerClient.requester = function (packetSkeleton, config = {}) {
- let { before, after } = config;
- return DevToolsUtils.makeInfallible(function (...args) {
- let outgoingPacket = {
- to: packetSkeleton.to || this.actor
- };
-
- let maxPosition = -1;
- for (let k of Object.keys(packetSkeleton)) {
- if (packetSkeleton[k] instanceof DebuggerClient.Argument) {
- let { position } = packetSkeleton[k];
- outgoingPacket[k] = packetSkeleton[k].getArgument(args);
- maxPosition = Math.max(position, maxPosition);
- } else {
- outgoingPacket[k] = packetSkeleton[k];
- }
- }
-
- if (before) {
- outgoingPacket = before.call(this, outgoingPacket);
- }
-
- return this.request(outgoingPacket, DevToolsUtils.makeInfallible((response) => {
- if (after) {
- let { from } = response;
- response = after.call(this, response);
- if (!response.from) {
- response.from = from;
- }
- }
-
- // The callback is always the last parameter.
- let thisCallback = args[maxPosition + 1];
- if (thisCallback) {
- thisCallback(response);
- }
- return response;
- }, "DebuggerClient.requester request callback"));
- }, "DebuggerClient.requester");
-};
-
-function arg(pos) {
- return new DebuggerClient.Argument(pos);
-}
-
-DebuggerClient.Argument = function (position) {
- this.position = position;
-};
-
-DebuggerClient.Argument.prototype.getArgument = function (params) {
- if (!(this.position in params)) {
- throw new Error("Bad index into params: " + this.position);
- }
- return params[this.position];
-};
-
-// Expose these to save callers the trouble of importing DebuggerSocket
-DebuggerClient.socketConnect = function (options) {
- // Defined here instead of just copying the function to allow lazy-load
- return DebuggerSocket.connect(options);
-};
-DevToolsUtils.defineLazyGetter(DebuggerClient, "Authenticators", () => {
- return Authentication.Authenticators;
-});
-DevToolsUtils.defineLazyGetter(DebuggerClient, "AuthenticationResult", () => {
- return Authentication.AuthenticationResult;
-});
-
-DebuggerClient.prototype = {
- /**
- * Connect to the server and start exchanging protocol messages.
- *
- * @param onConnected function
- * If specified, will be called when the greeting packet is
- * received from the debugging server.
- *
- * @return Promise
- * Resolves once connected with an array whose first element
- * is the application type, by default "browser", and the second
- * element is the traits object (help figure out the features
- * and behaviors of the server we connect to. See RootActor).
- */
- connect: function (onConnected) {
- let deferred = promise.defer();
- this.emit("connect");
-
- // Also emit the event on the |DebuggerClient| object (not on the instance),
- // so it's possible to track all instances.
- EventEmitter.emit(DebuggerClient, "connect", this);
-
- this.addOneTimeListener("connected", (name, applicationType, traits) => {
- this.traits = traits;
- if (onConnected) {
- onConnected(applicationType, traits);
- }
- deferred.resolve([applicationType, traits]);
- });
-
- this._transport.ready();
- return deferred.promise;
- },
-
- /**
- * Shut down communication with the debugging server.
- *
- * @param onClosed function
- * If specified, will be called when the debugging connection
- * has been closed. This parameter is deprecated - please use
- * the returned Promise.
- * @return Promise
- * Resolves after the underlying transport is closed.
- */
- close: function (onClosed) {
- let deferred = promise.defer();
- if (onClosed) {
- deferred.promise.then(onClosed);
- }
-
- // Disable detach event notifications, because event handlers will be in a
- // cleared scope by the time they run.
- this._eventsEnabled = false;
-
- let cleanup = () => {
- this._transport.close();
- this._transport = null;
- };
-
- // If the connection is already closed,
- // there is no need to detach client
- // as we won't be able to send any message.
- if (this._closed) {
- cleanup();
- deferred.resolve();
- return deferred.promise;
- }
-
- this.addOneTimeListener("closed", deferred.resolve);
-
- // Call each client's `detach` method by calling
- // lastly registered ones first to give a chance
- // to detach child clients first.
- let clients = [...this._clients.values()];
- this._clients.clear();
- const detachClients = () => {
- let client = clients.pop();
- if (!client) {
- // All clients detached.
- cleanup();
- return;
- }
- if (client.detach) {
- client.detach(detachClients);
- return;
- }
- detachClients();
- };
- detachClients();
-
- return deferred.promise;
- },
-
- /*
- * This function exists only to preserve DebuggerClient's interface;
- * new code should say 'client.mainRoot.listTabs()'.
- */
- listTabs: function (onResponse) {
- return this.mainRoot.listTabs(onResponse);
- },
-
- /*
- * This function exists only to preserve DebuggerClient's interface;
- * new code should say 'client.mainRoot.listAddons()'.
- */
- listAddons: function (onResponse) {
- return this.mainRoot.listAddons(onResponse);
- },
-
- getTab: function (filter) {
- return this.mainRoot.getTab(filter);
- },
-
- /**
- * Attach to a tab actor.
- *
- * @param string tabActor
- * The actor ID for the tab to attach.
- * @param function onResponse
- * Called with the response packet and a TabClient
- * (which will be undefined on error).
- */
- attachTab: function (tabActor, onResponse = noop) {
- if (this._clients.has(tabActor)) {
- let cachedTab = this._clients.get(tabActor);
- let cachedResponse = {
- cacheDisabled: cachedTab.cacheDisabled,
- javascriptEnabled: cachedTab.javascriptEnabled,
- traits: cachedTab.traits,
- };
- DevToolsUtils.executeSoon(() => onResponse(cachedResponse, cachedTab));
- return promise.resolve([cachedResponse, cachedTab]);
- }
-
- let packet = {
- to: tabActor,
- type: "attach"
- };
- return this.request(packet).then(response => {
- let tabClient;
- if (!response.error) {
- tabClient = new TabClient(this, response);
- this.registerClient(tabClient);
- }
- onResponse(response, tabClient);
- return [response, tabClient];
- });
- },
-
- attachWorker: function (workerActor, onResponse = noop) {
- let workerClient = this._clients.get(workerActor);
- if (workerClient !== undefined) {
- let response = {
- from: workerClient.actor,
- type: "attached",
- url: workerClient.url
- };
- DevToolsUtils.executeSoon(() => onResponse(response, workerClient));
- return promise.resolve([response, workerClient]);
- }
-
- return this.request({ to: workerActor, type: "attach" }).then(response => {
- if (response.error) {
- onResponse(response, null);
- return [response, null];
- }
-
- workerClient = new WorkerClient(this, response);
- this.registerClient(workerClient);
- onResponse(response, workerClient);
- return [response, workerClient];
- });
- },
-
- /**
- * Attach to an addon actor.
- *
- * @param string addonActor
- * The actor ID for the addon to attach.
- * @param function onResponse
- * Called with the response packet and a AddonClient
- * (which will be undefined on error).
- */
- attachAddon: function (addonActor, onResponse = noop) {
- let packet = {
- to: addonActor,
- type: "attach"
- };
- return this.request(packet).then(response => {
- let addonClient;
- if (!response.error) {
- addonClient = new AddonClient(this, addonActor);
- this.registerClient(addonClient);
- this.activeAddon = addonClient;
- }
- onResponse(response, addonClient);
- return [response, addonClient];
- });
- },
-
- /**
- * Attach to a Web Console actor.
- *
- * @param string consoleActor
- * The ID for the console actor to attach to.
- * @param array listeners
- * The console listeners you want to start.
- * @param function onResponse
- * Called with the response packet and a WebConsoleClient
- * instance (which will be undefined on error).
- */
- attachConsole:
- function (consoleActor, listeners, onResponse = noop) {
- let packet = {
- to: consoleActor,
- type: "startListeners",
- listeners: listeners,
- };
-
- return this.request(packet).then(response => {
- let consoleClient;
- if (!response.error) {
- if (this._clients.has(consoleActor)) {
- consoleClient = this._clients.get(consoleActor);
- } else {
- consoleClient = new WebConsoleClient(this, response);
- this.registerClient(consoleClient);
- }
- }
- onResponse(response, consoleClient);
- return [response, consoleClient];
- });
- },
-
- /**
- * Attach to a global-scoped thread actor for chrome debugging.
- *
- * @param string threadActor
- * The actor ID for the thread to attach.
- * @param function onResponse
- * Called with the response packet and a ThreadClient
- * (which will be undefined on error).
- * @param object options
- * Configuration options.
- * - useSourceMaps: whether to use source maps or not.
- */
- attachThread: function (threadActor, onResponse = noop, options = {}) {
- if (this._clients.has(threadActor)) {
- let client = this._clients.get(threadActor);
- DevToolsUtils.executeSoon(() => onResponse({}, client));
- return promise.resolve([{}, client]);
- }
-
- let packet = {
- to: threadActor,
- type: "attach",
- options,
- };
- return this.request(packet).then(response => {
- let threadClient;
- if (!response.error) {
- threadClient = new ThreadClient(this, threadActor);
- this.registerClient(threadClient);
- }
- onResponse(response, threadClient);
- return [response, threadClient];
- });
- },
-
- /**
- * Attach to a trace actor.
- *
- * @param string traceActor
- * The actor ID for the tracer to attach.
- * @param function onResponse
- * Called with the response packet and a TraceClient
- * (which will be undefined on error).
- */
- attachTracer: function (traceActor, onResponse = noop) {
- if (this._clients.has(traceActor)) {
- let client = this._clients.get(traceActor);
- DevToolsUtils.executeSoon(() => onResponse({}, client));
- return promise.resolve([{}, client]);
- }
-
- let packet = {
- to: traceActor,
- type: "attach"
- };
- return this.request(packet).then(response => {
- let traceClient;
- if (!response.error) {
- traceClient = new TraceClient(this, traceActor);
- this.registerClient(traceClient);
- }
- onResponse(response, traceClient);
- return [response, traceClient];
- });
- },
-
- /**
- * Fetch the ChromeActor for the main process or ChildProcessActor for a
- * a given child process ID.
- *
- * @param number id
- * The ID for the process to attach (returned by `listProcesses`).
- * Connected to the main process if omitted, or is 0.
- */
- getProcess: function (id) {
- let packet = {
- to: "root",
- type: "getProcess"
- };
- if (typeof (id) == "number") {
- packet.id = id;
- }
- return this.request(packet);
- },
-
- /**
- * Release an object actor.
- *
- * @param string actor
- * The actor ID to send the request to.
- * @param onResponse function
- * If specified, will be called with the response packet when
- * debugging server responds.
- */
- release: DebuggerClient.requester({
- to: arg(0),
- type: "release"
- }),
-
- /**
- * Send a request to the debugging server.
- *
- * @param packet object
- * A JSON packet to send to the debugging server.
- * @param onResponse function
- * If specified, will be called with the JSON response packet when
- * debugging server responds.
- * @return Request
- * This object emits a number of events to allow you to respond to
- * different parts of the request lifecycle.
- * It is also a Promise object, with a `then` method, that is resolved
- * whenever a JSON or a Bulk response is received; and is rejected
- * if the response is an error.
- * Note: This return value can be ignored if you are using JSON alone,
- * because the callback provided in |onResponse| will be bound to the
- * "json-reply" event automatically.
- *
- * Events emitted:
- * * json-reply: The server replied with a JSON packet, which is
- * passed as event data.
- * * bulk-reply: The server replied with bulk data, which you can read
- * using the event data object containing:
- * * actor: Name of actor that received the packet
- * * type: Name of actor's method that was called on receipt
- * * length: Size of the data to be read
- * * stream: This input stream should only be used directly if you
- * can ensure that you will read exactly |length| bytes
- * and will not close the stream when reading is complete
- * * done: If you use the stream directly (instead of |copyTo|
- * below), you must signal completion by resolving /
- * rejecting this deferred. If it's rejected, the
- * transport will be closed. If an Error is supplied as a
- * rejection value, it will be logged via |dumpn|. If you
- * do use |copyTo|, resolving is taken care of for you
- * when copying completes.
- * * copyTo: A helper function for getting your data out of the
- * stream that meets the stream handling requirements
- * above, and has the following signature:
- * @param output nsIAsyncOutputStream
- * The stream to copy to.
- * @return Promise
- * The promise is resolved when copying completes or
- * rejected if any (unexpected) errors occur.
- * This object also emits "progress" events for each chunk
- * that is copied. See stream-utils.js.
- */
- request: function (packet, onResponse) {
- if (!this.mainRoot) {
- throw Error("Have not yet received a hello packet from the server.");
- }
- let type = packet.type || "";
- if (!packet.to) {
- throw Error("'" + type + "' request packet has no destination.");
- }
-
- // The onResponse callback might modify the response, so we need to call
- // it and resolve the promise with its result if it's truthy.
- const safeOnResponse = response => {
- if (!onResponse) {
- return response;
- }
- return onResponse(response) || response;
- };
-
- if (this._closed) {
- let msg = "'" + type + "' request packet to " +
- "'" + packet.to + "' " +
- "can't be sent as the connection is closed.";
- let resp = { error: "connectionClosed", message: msg };
- return promise.reject(safeOnResponse(resp));
- }
-
- let request = new Request(packet);
- request.format = "json";
- request.stack = getStack();
-
- // Implement a Promise like API on the returned object
- // that resolves/rejects on request response
- let deferred = promise.defer();
- function listenerJson(resp) {
- removeRequestListeners();
- if (resp.error) {
- deferred.reject(safeOnResponse(resp));
- } else {
- deferred.resolve(safeOnResponse(resp));
- }
- }
- function listenerBulk(resp) {
- removeRequestListeners();
- deferred.resolve(safeOnResponse(resp));
- }
-
- const removeRequestListeners = () => {
- request.off("json-reply", listenerJson);
- request.off("bulk-reply", listenerBulk);
- };
-
- request.on("json-reply", listenerJson);
- request.on("bulk-reply", listenerBulk);
-
- this._sendOrQueueRequest(request);
- request.then = deferred.promise.then.bind(deferred.promise);
-
- return request;
- },
-
- /**
- * Transmit streaming data via a bulk request.
- *
- * This method initiates the bulk send process by queuing up the header data.
- * The caller receives eventual access to a stream for writing.
- *
- * Since this opens up more options for how the server might respond (it could
- * send back either JSON or bulk data), and the returned Request object emits
- * events for different stages of the request process that you may want to
- * react to.
- *
- * @param request Object
- * This is modeled after the format of JSON packets above, but does not
- * actually contain the data, but is instead just a routing header:
- * * actor: Name of actor that will receive the packet
- * * type: Name of actor's method that should be called on receipt
- * * length: Size of the data to be sent
- * @return Request
- * This object emits a number of events to allow you to respond to
- * different parts of the request lifecycle.
- *
- * Events emitted:
- * * bulk-send-ready: Ready to send bulk data to the server, using the
- * event data object containing:
- * * stream: This output stream should only be used directly if
- * you can ensure that you will write exactly |length|
- * bytes and will not close the stream when writing is
- * complete
- * * done: If you use the stream directly (instead of |copyFrom|
- * below), you must signal completion by resolving /
- * rejecting this deferred. If it's rejected, the
- * transport will be closed. If an Error is supplied as
- * a rejection value, it will be logged via |dumpn|. If
- * you do use |copyFrom|, resolving is taken care of for
- * you when copying completes.
- * * copyFrom: A helper function for getting your data onto the
- * stream that meets the stream handling requirements
- * above, and has the following signature:
- * @param input nsIAsyncInputStream
- * The stream to copy from.
- * @return Promise
- * The promise is resolved when copying completes or
- * rejected if any (unexpected) errors occur.
- * This object also emits "progress" events for each chunk
- * that is copied. See stream-utils.js.
- * * json-reply: The server replied with a JSON packet, which is
- * passed as event data.
- * * bulk-reply: The server replied with bulk data, which you can read
- * using the event data object containing:
- * * actor: Name of actor that received the packet
- * * type: Name of actor's method that was called on receipt
- * * length: Size of the data to be read
- * * stream: This input stream should only be used directly if you
- * can ensure that you will read exactly |length| bytes
- * and will not close the stream when reading is complete
- * * done: If you use the stream directly (instead of |copyTo|
- * below), you must signal completion by resolving /
- * rejecting this deferred. If it's rejected, the
- * transport will be closed. If an Error is supplied as a
- * rejection value, it will be logged via |dumpn|. If you
- * do use |copyTo|, resolving is taken care of for you
- * when copying completes.
- * * copyTo: A helper function for getting your data out of the
- * stream that meets the stream handling requirements
- * above, and has the following signature:
- * @param output nsIAsyncOutputStream
- * The stream to copy to.
- * @return Promise
- * The promise is resolved when copying completes or
- * rejected if any (unexpected) errors occur.
- * This object also emits "progress" events for each chunk
- * that is copied. See stream-utils.js.
- */
- startBulkRequest: function (request) {
- if (!this.traits.bulk) {
- throw Error("Server doesn't support bulk transfers");
- }
- if (!this.mainRoot) {
- throw Error("Have not yet received a hello packet from the server.");
- }
- if (!request.type) {
- throw Error("Bulk packet is missing the required 'type' field.");
- }
- if (!request.actor) {
- throw Error("'" + request.type + "' bulk packet has no destination.");
- }
- if (!request.length) {
- throw Error("'" + request.type + "' bulk packet has no length.");
- }
-
- request = new Request(request);
- request.format = "bulk";
-
- this._sendOrQueueRequest(request);
-
- return request;
- },
-
- /**
- * If a new request can be sent immediately, do so. Otherwise, queue it.
- */
- _sendOrQueueRequest(request) {
- let actor = request.actor;
- if (!this._activeRequests.has(actor)) {
- this._sendRequest(request);
- } else {
- this._queueRequest(request);
- }
- },
-
- /**
- * Send a request.
- * @throws Error if there is already an active request in flight for the same
- * actor.
- */
- _sendRequest(request) {
- let actor = request.actor;
- this.expectReply(actor, request);
-
- if (request.format === "json") {
- this._transport.send(request.request);
- return;
- }
-
- this._transport.startBulkSend(request.request).then((...args) => {
- request.emit("bulk-send-ready", ...args);
- });
- },
-
- /**
- * Queue a request to be sent later. Queues are only drained when an in
- * flight request to a given actor completes.
- */
- _queueRequest(request) {
- let actor = request.actor;
- let queue = this._pendingRequests.get(actor) || [];
- queue.push(request);
- this._pendingRequests.set(actor, queue);
- },
-
- /**
- * Attempt the next request to a given actor (if any).
- */
- _attemptNextRequest(actor) {
- if (this._activeRequests.has(actor)) {
- return;
- }
- let queue = this._pendingRequests.get(actor);
- if (!queue) {
- return;
- }
- let request = queue.shift();
- if (queue.length === 0) {
- this._pendingRequests.delete(actor);
- }
- this._sendRequest(request);
- },
-
- /**
- * Arrange to hand the next reply from |actor| to the handler bound to
- * |request|.
- *
- * DebuggerClient.prototype.request / startBulkRequest usually takes care of
- * establishing the handler for a given request, but in rare cases (well,
- * greetings from new root actors, is the only case at the moment) we must be
- * prepared for a "reply" that doesn't correspond to any request we sent.
- */
- expectReply: function (actor, request) {
- if (this._activeRequests.has(actor)) {
- throw Error("clashing handlers for next reply from " + actor);
- }
-
- // If a handler is passed directly (as it is with the handler for the root
- // actor greeting), create a dummy request to bind this to.
- if (typeof request === "function") {
- let handler = request;
- request = new Request();
- request.on("json-reply", handler);
- }
-
- this._activeRequests.set(actor, request);
- },
-
- // Transport hooks.
-
- /**
- * Called by DebuggerTransport to dispatch incoming packets as appropriate.
- *
- * @param packet object
- * The incoming packet.
- */
- onPacket: function (packet) {
- if (!packet.from) {
- DevToolsUtils.reportException(
- "onPacket",
- new Error("Server did not specify an actor, dropping packet: " +
- JSON.stringify(packet)));
- return;
- }
-
- // If we have a registered Front for this actor, let it handle the packet
- // and skip all the rest of this unpleasantness.
- let front = this.getActor(packet.from);
- if (front) {
- front.onPacket(packet);
- return;
- }
-
- // Check for "forwardingCancelled" here instead of using a client to handle it.
- // This is necessary because we might receive this event while the client is closing,
- // and the clients have already been removed by that point.
- if (this.mainRoot &&
- packet.from == this.mainRoot.actor &&
- packet.type == "forwardingCancelled") {
- this.purgeRequests(packet.prefix);
- return;
- }
-
- if (this._clients.has(packet.from) && packet.type) {
- let client = this._clients.get(packet.from);
- let type = packet.type;
- if (client.events.indexOf(type) != -1) {
- client.emit(type, packet);
- // we ignore the rest, as the client is expected to handle this packet.
- return;
- }
- }
-
- let activeRequest;
- // See if we have a handler function waiting for a reply from this
- // actor. (Don't count unsolicited notifications or pauses as
- // replies.)
- if (this._activeRequests.has(packet.from) &&
- !(packet.type in UnsolicitedNotifications) &&
- !(packet.type == ThreadStateTypes.paused &&
- packet.why.type in UnsolicitedPauses)) {
- activeRequest = this._activeRequests.get(packet.from);
- this._activeRequests.delete(packet.from);
- }
-
- // If there is a subsequent request for the same actor, hand it off to the
- // transport. Delivery of packets on the other end is always async, even
- // in the local transport case.
- this._attemptNextRequest(packet.from);
-
- // Packets that indicate thread state changes get special treatment.
- if (packet.type in ThreadStateTypes &&
- this._clients.has(packet.from) &&
- typeof this._clients.get(packet.from)._onThreadState == "function") {
- this._clients.get(packet.from)._onThreadState(packet);
- }
-
- // TODO: Bug 1151156 - Remove once Gecko 40 is on b2g-stable.
- if (!this.traits.noNeedToFakeResumptionOnNavigation) {
- // On navigation the server resumes, so the client must resume as well.
- // We achieve that by generating a fake resumption packet that triggers
- // the client's thread state change listeners.
- if (packet.type == UnsolicitedNotifications.tabNavigated &&
- this._clients.has(packet.from) &&
- this._clients.get(packet.from).thread) {
- let thread = this._clients.get(packet.from).thread;
- let resumption = { from: thread._actor, type: "resumed" };
- thread._onThreadState(resumption);
- }
- }
-
- // Only try to notify listeners on events, not responses to requests
- // that lack a packet type.
- if (packet.type) {
- this.emit(packet.type, packet);
- }
-
- if (activeRequest) {
- let emitReply = () => activeRequest.emit("json-reply", packet);
- if (activeRequest.stack) {
- callFunctionWithAsyncStack(emitReply, activeRequest.stack,
- "DevTools RDP");
- } else {
- emitReply();
- }
- }
- },
-
- /**
- * Called by the DebuggerTransport to dispatch incoming bulk packets as
- * appropriate.
- *
- * @param packet object
- * The incoming packet, which contains:
- * * actor: Name of actor that will receive the packet
- * * type: Name of actor's method that should be called on receipt
- * * length: Size of the data to be read
- * * stream: This input stream should only be used directly if you can
- * ensure that you will read exactly |length| bytes and will
- * not close the stream when reading is complete
- * * done: If you use the stream directly (instead of |copyTo|
- * below), you must signal completion by resolving /
- * rejecting this deferred. If it's rejected, the transport
- * will be closed. If an Error is supplied as a rejection
- * value, it will be logged via |dumpn|. If you do use
- * |copyTo|, resolving is taken care of for you when copying
- * completes.
- * * copyTo: A helper function for getting your data out of the stream
- * that meets the stream handling requirements above, and has
- * the following signature:
- * @param output nsIAsyncOutputStream
- * The stream to copy to.
- * @return Promise
- * The promise is resolved when copying completes or rejected
- * if any (unexpected) errors occur.
- * This object also emits "progress" events for each chunk
- * that is copied. See stream-utils.js.
- */
- onBulkPacket: function (packet) {
- let { actor } = packet;
-
- if (!actor) {
- DevToolsUtils.reportException(
- "onBulkPacket",
- new Error("Server did not specify an actor, dropping bulk packet: " +
- JSON.stringify(packet)));
- return;
- }
-
- // See if we have a handler function waiting for a reply from this
- // actor.
- if (!this._activeRequests.has(actor)) {
- return;
- }
-
- let activeRequest = this._activeRequests.get(actor);
- this._activeRequests.delete(actor);
-
- // If there is a subsequent request for the same actor, hand it off to the
- // transport. Delivery of packets on the other end is always async, even
- // in the local transport case.
- this._attemptNextRequest(actor);
-
- activeRequest.emit("bulk-reply", packet);
- },
-
- /**
- * Called by DebuggerTransport when the underlying stream is closed.
- *
- * @param status nsresult
- * The status code that corresponds to the reason for closing
- * the stream.
- */
- onClosed: function () {
- this._closed = true;
- this.emit("closed");
-
- this.purgeRequests();
-
- // The |_pools| array on the client-side currently is used only by
- // protocol.js to store active fronts, mirroring the actor pools found in
- // the server. So, read all usages of "pool" as "protocol.js front".
- //
- // In the normal case where we shutdown cleanly, the toolbox tells each tool
- // to close, and they each call |destroy| on any fronts they were using.
- // When |destroy| or |cleanup| is called on a protocol.js front, it also
- // removes itself from the |_pools| array. Once the toolbox has shutdown,
- // the connection is closed, and we reach here. All fronts (should have
- // been) |destroy|ed, so |_pools| should empty.
- //
- // If the connection instead aborts unexpectedly, we may end up here with
- // all fronts used during the life of the connection. So, we call |cleanup|
- // on them clear their state, reject pending requests, and remove themselves
- // from |_pools|. This saves the toolbox from hanging indefinitely, in case
- // it waits for some server response before shutdown that will now never
- // arrive.
- for (let pool of this._pools) {
- pool.cleanup();
- }
- },
-
- /**
- * Purge pending and active requests in this client.
- *
- * @param prefix string (optional)
- * If a prefix is given, only requests for actor IDs that start with the prefix
- * will be cleaned up. This is useful when forwarding of a portion of requests
- * is cancelled on the server.
- */
- purgeRequests(prefix = "") {
- let reject = function (type, request) {
- // Server can send packets on its own and client only pass a callback
- // to expectReply, so that there is no request object.
- let msg;
- if (request.request) {
- msg = "'" + request.request.type + "' " + type + " request packet" +
- " to '" + request.actor + "' " +
- "can't be sent as the connection just closed.";
- } else {
- msg = "server side packet can't be received as the connection just closed.";
- }
- let packet = { error: "connectionClosed", message: msg };
- request.emit("json-reply", packet);
- };
-
- let pendingRequestsToReject = [];
- this._pendingRequests.forEach((requests, actor) => {
- if (!actor.startsWith(prefix)) {
- return;
- }
- this._pendingRequests.delete(actor);
- pendingRequestsToReject = pendingRequestsToReject.concat(requests);
- });
- pendingRequestsToReject.forEach(request => reject("pending", request));
-
- let activeRequestsToReject = [];
- this._activeRequests.forEach((request, actor) => {
- if (!actor.startsWith(prefix)) {
- return;
- }
- this._activeRequests.delete(actor);
- activeRequestsToReject = activeRequestsToReject.concat(request);
- });
- activeRequestsToReject.forEach(request => reject("active", request));
- },
-
- /**
- * Search for all requests in process for this client, including those made via
- * protocol.js and wait all of them to complete. Since the requests seen when this is
- * first called may in turn trigger more requests, we keep recursing through this
- * function until there is no more activity.
- *
- * This is a fairly heavy weight process, so it's only meant to be used in tests.
- *
- * @return Promise
- * Resolved when all requests have settled.
- */
- waitForRequestsToSettle() {
- let requests = [];
-
- // Gather all pending and active requests in this client
- // The request object supports a Promise API for completion (it has .then())
- this._pendingRequests.forEach(requestsForActor => {
- // Each value is an array of pending requests
- requests = requests.concat(requestsForActor);
- });
- this._activeRequests.forEach(requestForActor => {
- // Each value is a single active request
- requests = requests.concat(requestForActor);
- });
-
- // protocol.js
- // Use a Set because some fronts (like domwalker) seem to have multiple parents.
- let fronts = new Set();
- let poolsToVisit = [...this._pools];
-
- // With protocol.js, each front can potentially have it's own pools containing child
- // fronts, forming a tree. Descend through all the pools to locate all child fronts.
- while (poolsToVisit.length) {
- let pool = poolsToVisit.shift();
- fronts.add(pool);
- for (let child of pool.poolChildren()) {
- poolsToVisit.push(child);
- }
- }
-
- // For each front, wait for its requests to settle
- for (let front of fronts) {
- if (front.hasRequests()) {
- requests.push(front.waitForRequestsToSettle());
- }
- }
-
- // Abort early if there are no requests
- if (!requests.length) {
- return Promise.resolve();
- }
-
- return DevToolsUtils.settleAll(requests).catch(() => {
- // One of the requests might have failed, but ignore that situation here and pipe
- // both success and failure through the same path. The important part is just that
- // we waited.
- }).then(() => {
- // Repeat, more requests may have started in response to those we just waited for
- return this.waitForRequestsToSettle();
- });
- },
-
- registerClient: function (client) {
- let actorID = client.actor;
- if (!actorID) {
- throw new Error("DebuggerServer.registerClient expects " +
- "a client instance with an `actor` attribute.");
- }
- if (!Array.isArray(client.events)) {
- throw new Error("DebuggerServer.registerClient expects " +
- "a client instance with an `events` attribute " +
- "that is an array.");
- }
- if (client.events.length > 0 && typeof (client.emit) != "function") {
- throw new Error("DebuggerServer.registerClient expects " +
- "a client instance with non-empty `events` array to" +
- "have an `emit` function.");
- }
- if (this._clients.has(actorID)) {
- throw new Error("DebuggerServer.registerClient already registered " +
- "a client for this actor.");
- }
- this._clients.set(actorID, client);
- },
-
- unregisterClient: function (client) {
- let actorID = client.actor;
- if (!actorID) {
- throw new Error("DebuggerServer.unregisterClient expects " +
- "a Client instance with a `actor` attribute.");
- }
- this._clients.delete(actorID);
- },
-
- /**
- * Actor lifetime management, echos the server's actor pools.
- */
- __pools: null,
- get _pools() {
- if (this.__pools) {
- return this.__pools;
- }
- this.__pools = new Set();
- return this.__pools;
- },
-
- addActorPool: function (pool) {
- this._pools.add(pool);
- },
- removeActorPool: function (pool) {
- this._pools.delete(pool);
- },
- getActor: function (actorID) {
- let pool = this.poolFor(actorID);
- return pool ? pool.get(actorID) : null;
- },
-
- poolFor: function (actorID) {
- for (let pool of this._pools) {
- if (pool.has(actorID)) {
- return pool;
- }
- }
- return null;
- },
-
- /**
- * Currently attached addon.
- */
- activeAddon: null
-};
-
-eventSource(DebuggerClient.prototype);
-
-class Request extends EventEmitter {
- constructor(request) {
- super();
- this.request = request;
- }
-
- get actor() {
- return this.request.to || this.request.actor;
- }
-}
-
-/**
- * Creates a tab client for the remote debugging protocol server. This client
- * is a front to the tab actor created in the server side, hiding the protocol
- * details in a traditional JavaScript API.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param form object
- * The protocol form for this tab.
- */
-function TabClient(client, form) {
- this.client = client;
- this._actor = form.from;
- this._threadActor = form.threadActor;
- this.javascriptEnabled = form.javascriptEnabled;
- this.cacheDisabled = form.cacheDisabled;
- this.thread = null;
- this.request = this.client.request;
- this.traits = form.traits || {};
- this.events = ["workerListChanged"];
-}
-
-TabClient.prototype = {
- get actor() {
- return this._actor;
- },
- get _transport() {
- return this.client._transport;
- },
-
- /**
- * Attach to a thread actor.
- *
- * @param object options
- * Configuration options.
- * - useSourceMaps: whether to use source maps or not.
- * @param function onResponse
- * Called with the response packet and a ThreadClient
- * (which will be undefined on error).
- */
- attachThread: function (options = {}, onResponse = noop) {
- if (this.thread) {
- DevToolsUtils.executeSoon(() => onResponse({}, this.thread));
- return promise.resolve([{}, this.thread]);
- }
-
- let packet = {
- to: this._threadActor,
- type: "attach",
- options,
- };
- return this.request(packet).then(response => {
- if (!response.error) {
- this.thread = new ThreadClient(this, this._threadActor);
- this.client.registerClient(this.thread);
- }
- onResponse(response, this.thread);
- return [response, this.thread];
- });
- },
-
- /**
- * Detach the client from the tab actor.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- detach: DebuggerClient.requester({
- type: "detach"
- }, {
- before: function (packet) {
- if (this.thread) {
- this.thread.detach();
- }
- return packet;
- },
- after: function (response) {
- this.client.unregisterClient(this);
- return response;
- },
- }),
-
- /**
- * Bring the window to the front.
- */
- focus: DebuggerClient.requester({
- type: "focus"
- }, {}),
-
- /**
- * Reload the page in this tab.
- *
- * @param [optional] object options
- * An object with a `force` property indicating whether or not
- * this reload should skip the cache
- */
- reload: function (options = { force: false }) {
- return this._reload(options);
- },
- _reload: DebuggerClient.requester({
- type: "reload",
- options: arg(0)
- }),
-
- /**
- * Navigate to another URL.
- *
- * @param string url
- * The URL to navigate to.
- */
- navigateTo: DebuggerClient.requester({
- type: "navigateTo",
- url: arg(0)
- }),
-
- /**
- * Reconfigure the tab actor.
- *
- * @param object options
- * A dictionary object of the new options to use in the tab actor.
- * @param function onResponse
- * Called with the response packet.
- */
- reconfigure: DebuggerClient.requester({
- type: "reconfigure",
- options: arg(0)
- }),
-
- listWorkers: DebuggerClient.requester({
- type: "listWorkers"
- }),
-
- attachWorker: function (workerActor, onResponse) {
- return this.client.attachWorker(workerActor, onResponse);
- },
-};
-
-eventSource(TabClient.prototype);
-
-function WorkerClient(client, form) {
- this.client = client;
- this._actor = form.from;
- this._isClosed = false;
- this._url = form.url;
-
- this._onClose = this._onClose.bind(this);
-
- this.addListener("close", this._onClose);
-
- this.traits = {};
-}
-
-WorkerClient.prototype = {
- get _transport() {
- return this.client._transport;
- },
-
- get request() {
- return this.client.request;
- },
-
- get actor() {
- return this._actor;
- },
-
- get url() {
- return this._url;
- },
-
- get isClosed() {
- return this._isClosed;
- },
-
- detach: DebuggerClient.requester({ type: "detach" }, {
- after: function (response) {
- if (this.thread) {
- this.client.unregisterClient(this.thread);
- }
- this.client.unregisterClient(this);
- return response;
- },
- }),
-
- attachThread: function (options = {}, onResponse = noop) {
- if (this.thread) {
- let response = [{
- type: "connected",
- threadActor: this.thread._actor,
- consoleActor: this.consoleActor,
- }, this.thread];
- DevToolsUtils.executeSoon(() => onResponse(response));
- return response;
- }
-
- // The connect call on server doesn't attach the thread as of version 44.
- return this.request({
- to: this._actor,
- type: "connect",
- options,
- }).then(connectResponse => {
- if (connectResponse.error) {
- onResponse(connectResponse, null);
- return [connectResponse, null];
- }
-
- return this.request({
- to: connectResponse.threadActor,
- type: "attach",
- options,
- }).then(attachResponse => {
- if (attachResponse.error) {
- onResponse(attachResponse, null);
- }
-
- this.thread = new ThreadClient(this, connectResponse.threadActor);
- this.consoleActor = connectResponse.consoleActor;
- this.client.registerClient(this.thread);
-
- onResponse(connectResponse, this.thread);
- return [connectResponse, this.thread];
- });
- }, error => {
- onResponse(error, null);
- });
- },
-
- _onClose: function () {
- this.removeListener("close", this._onClose);
-
- if (this.thread) {
- this.client.unregisterClient(this.thread);
- }
- this.client.unregisterClient(this);
- this._isClosed = true;
- },
-
- reconfigure: function () {
- return Promise.resolve();
- },
-
- events: ["close"]
-};
-
-eventSource(WorkerClient.prototype);
-
-function AddonClient(client, actor) {
- this._client = client;
- this._actor = actor;
- this.request = this._client.request;
- this.events = [];
-}
-
-AddonClient.prototype = {
- get actor() {
- return this._actor;
- },
- get _transport() {
- return this._client._transport;
- },
-
- /**
- * Detach the client from the addon actor.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- detach: DebuggerClient.requester({
- type: "detach"
- }, {
- after: function (response) {
- if (this._client.activeAddon === this) {
- this._client.activeAddon = null;
- }
- this._client.unregisterClient(this);
- return response;
- },
- })
-};
-
-/**
- * A RootClient object represents a root actor on the server. Each
- * DebuggerClient keeps a RootClient instance representing the root actor
- * for the initial connection; DebuggerClient's 'listTabs' and
- * 'listChildProcesses' methods forward to that root actor.
- *
- * @param client object
- * The client connection to which this actor belongs.
- * @param greeting string
- * The greeting packet from the root actor we're to represent.
- *
- * Properties of a RootClient instance:
- *
- * @property actor string
- * The name of this child's root actor.
- * @property applicationType string
- * The application type, as given in the root actor's greeting packet.
- * @property traits object
- * The traits object, as given in the root actor's greeting packet.
- */
-function RootClient(client, greeting) {
- this._client = client;
- this.actor = greeting.from;
- this.applicationType = greeting.applicationType;
- this.traits = greeting.traits;
-}
-exports.RootClient = RootClient;
-
-RootClient.prototype = {
- constructor: RootClient,
-
- /**
- * Gets the "root" form, which lists all the global actors that affect the entire
- * browser. This can replace usages of `listTabs` that only wanted the global actors
- * and didn't actually care about tabs.
- */
- getRoot: DebuggerClient.requester({ type: "getRoot" }),
-
- /**
- * List the open tabs.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- listTabs: DebuggerClient.requester({ type: "listTabs" }),
-
- /**
- * List the installed addons.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- listAddons: DebuggerClient.requester({ type: "listAddons" }),
-
- /**
- * List the registered workers.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- listWorkers: DebuggerClient.requester({ type: "listWorkers" }),
-
- /**
- * List the registered service workers.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- listServiceWorkerRegistrations: DebuggerClient.requester({
- type: "listServiceWorkerRegistrations"
- }),
-
- /**
- * List the running processes.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- listProcesses: DebuggerClient.requester({ type: "listProcesses" }),
-
- /**
- * Fetch the TabActor for the currently selected tab, or for a specific
- * tab given as first parameter.
- *
- * @param [optional] object filter
- * A dictionary object with following optional attributes:
- * - outerWindowID: used to match tabs in parent process
- * - tabId: used to match tabs in child processes
- * - tab: a reference to xul:tab element
- * If nothing is specified, returns the actor for the currently
- * selected tab.
- */
- getTab: function (filter) {
- let packet = {
- to: this.actor,
- type: "getTab"
- };
-
- if (filter) {
- if (typeof (filter.outerWindowID) == "number") {
- packet.outerWindowID = filter.outerWindowID;
- } else if (typeof (filter.tabId) == "number") {
- packet.tabId = filter.tabId;
- } else if ("tab" in filter) {
- let browser = filter.tab.linkedBrowser;
- if (browser.frameLoader.tabParent) {
- // Tabs in child process
- packet.tabId = browser.frameLoader.tabParent.tabId;
- } else if (browser.outerWindowID) {
- // <xul:browser> tabs in parent process
- packet.outerWindowID = browser.outerWindowID;
- } else {
- // <iframe mozbrowser> tabs in parent process
- let windowUtils = browser.contentWindow
- .QueryInterface(Ci.nsIInterfaceRequestor)
- .getInterface(Ci.nsIDOMWindowUtils);
- packet.outerWindowID = windowUtils.outerWindowID;
- }
- } else {
- // Throw if a filter object have been passed but without
- // any clearly idenfified filter.
- throw new Error("Unsupported argument given to getTab request");
- }
- }
-
- return this.request(packet);
- },
-
- /**
- * Fetch the WindowActor for a specific window, like a browser window in
- * Firefox, but it can be used to reach any window in the process.
- *
- * @param number outerWindowID
- * The outerWindowID of the top level window you are looking for.
- */
- getWindow: function ({ outerWindowID }) {
- if (!outerWindowID) {
- throw new Error("Must specify outerWindowID");
- }
-
- let packet = {
- to: this.actor,
- type: "getWindow",
- outerWindowID,
- };
-
- return this.request(packet);
- },
-
- /**
- * Description of protocol's actors and methods.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- protocolDescription: DebuggerClient.requester({ type: "protocolDescription" }),
-
- /*
- * Methods constructed by DebuggerClient.requester require these forwards
- * on their 'this'.
- */
- get _transport() {
- return this._client._transport;
- },
- get request() {
- return this._client.request;
- }
-};
-
-/**
- * Creates a thread client for the remote debugging protocol server. This client
- * is a front to the thread actor created in the server side, hiding the
- * protocol details in a traditional JavaScript API.
- *
- * @param client DebuggerClient|TabClient
- * The parent of the thread (tab for tab-scoped debuggers, DebuggerClient
- * for chrome debuggers).
- * @param actor string
- * The actor ID for this thread.
- */
-function ThreadClient(client, actor) {
- this._parent = client;
- this.client = client instanceof DebuggerClient ? client : client.client;
- this._actor = actor;
- this._frameCache = [];
- this._scriptCache = {};
- this._pauseGrips = {};
- this._threadGrips = {};
- this.request = this.client.request;
-}
-
-ThreadClient.prototype = {
- _state: "paused",
- get state() {
- return this._state;
- },
- get paused() {
- return this._state === "paused";
- },
-
- _pauseOnExceptions: false,
- _ignoreCaughtExceptions: false,
- _pauseOnDOMEvents: null,
-
- _actor: null,
- get actor() {
- return this._actor;
- },
-
- get _transport() {
- return this.client._transport;
- },
-
- _assertPaused: function (command) {
- if (!this.paused) {
- throw Error(command + " command sent while not paused. Currently " + this._state);
- }
- },
-
- /**
- * Resume a paused thread. If the optional limit parameter is present, then
- * the thread will also pause when that limit is reached.
- *
- * @param [optional] object limit
- * An object with a type property set to the appropriate limit (next,
- * step, or finish) per the remote debugging protocol specification.
- * Use null to specify no limit.
- * @param function onResponse
- * Called with the response packet.
- */
- _doResume: DebuggerClient.requester({
- type: "resume",
- resumeLimit: arg(0)
- }, {
- before: function (packet) {
- this._assertPaused("resume");
-
- // Put the client in a tentative "resuming" state so we can prevent
- // further requests that should only be sent in the paused state.
- this._previousState = this._state;
- this._state = "resuming";
-
- if (this._pauseOnExceptions) {
- packet.pauseOnExceptions = this._pauseOnExceptions;
- }
- if (this._ignoreCaughtExceptions) {
- packet.ignoreCaughtExceptions = this._ignoreCaughtExceptions;
- }
- if (this._pauseOnDOMEvents) {
- packet.pauseOnDOMEvents = this._pauseOnDOMEvents;
- }
- return packet;
- },
- after: function (response) {
- if (response.error && this._state == "resuming") {
- // There was an error resuming, update the state to the new one
- // reported by the server, if given (only on wrongState), otherwise
- // reset back to the previous state.
- if (response.state) {
- this._state = ThreadStateTypes[response.state];
- } else {
- this._state = this._previousState;
- }
- }
- delete this._previousState;
- return response;
- },
- }),
-
- /**
- * Reconfigure the thread actor.
- *
- * @param object options
- * A dictionary object of the new options to use in the thread actor.
- * @param function onResponse
- * Called with the response packet.
- */
- reconfigure: DebuggerClient.requester({
- type: "reconfigure",
- options: arg(0)
- }),
-
- /**
- * Resume a paused thread.
- */
- resume: function (onResponse) {
- return this._doResume(null, onResponse);
- },
-
- /**
- * Resume then pause without stepping.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- resumeThenPause: function (onResponse) {
- return this._doResume({ type: "break" }, onResponse);
- },
-
- /**
- * Step over a function call.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- stepOver: function (onResponse) {
- return this._doResume({ type: "next" }, onResponse);
- },
-
- /**
- * Step into a function call.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- stepIn: function (onResponse) {
- return this._doResume({ type: "step" }, onResponse);
- },
-
- /**
- * Step out of a function call.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- stepOut: function (onResponse) {
- return this._doResume({ type: "finish" }, onResponse);
- },
-
- /**
- * Immediately interrupt a running thread.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- interrupt: function (onResponse) {
- return this._doInterrupt(null, onResponse);
- },
-
- /**
- * Pause execution right before the next JavaScript bytecode is executed.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- breakOnNext: function (onResponse) {
- return this._doInterrupt("onNext", onResponse);
- },
-
- /**
- * Interrupt a running thread.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- _doInterrupt: DebuggerClient.requester({
- type: "interrupt",
- when: arg(0)
- }),
-
- /**
- * Enable or disable pausing when an exception is thrown.
- *
- * @param boolean pauseOnExceptions
- * Enables pausing if true, disables otherwise.
- * @param boolean ignoreCaughtExceptions
- * Whether to ignore caught exceptions
- * @param function onResponse
- * Called with the response packet.
- */
- pauseOnExceptions: function (pauseOnExceptions,
- ignoreCaughtExceptions,
- onResponse = noop) {
- this._pauseOnExceptions = pauseOnExceptions;
- this._ignoreCaughtExceptions = ignoreCaughtExceptions;
-
- // Otherwise send the flag using a standard resume request.
- if (!this.paused) {
- return this.interrupt(response => {
- if (response.error) {
- // Can't continue if pausing failed.
- onResponse(response);
- return response;
- }
- return this.resume(onResponse);
- });
- }
-
- onResponse();
- return promise.resolve();
- },
-
- /**
- * Enable pausing when the specified DOM events are triggered. Disabling
- * pausing on an event can be realized by calling this method with the updated
- * array of events that doesn't contain it.
- *
- * @param array|string events
- * An array of strings, representing the DOM event types to pause on,
- * or "*" to pause on all DOM events. Pass an empty array to
- * completely disable pausing on DOM events.
- * @param function onResponse
- * Called with the response packet in a future turn of the event loop.
- */
- pauseOnDOMEvents: function (events, onResponse = noop) {
- this._pauseOnDOMEvents = events;
- // If the debuggee is paused, the value of the array will be communicated in
- // the next resumption. Otherwise we have to force a pause in order to send
- // the array.
- if (this.paused) {
- DevToolsUtils.executeSoon(() => onResponse({}));
- return {};
- }
- return this.interrupt(response => {
- // Can't continue if pausing failed.
- if (response.error) {
- onResponse(response);
- return response;
- }
- return this.resume(onResponse);
- });
- },
-
- /**
- * Send a clientEvaluate packet to the debuggee. Response
- * will be a resume packet.
- *
- * @param string frame
- * The actor ID of the frame where the evaluation should take place.
- * @param string expression
- * The expression that will be evaluated in the scope of the frame
- * above.
- * @param function onResponse
- * Called with the response packet.
- */
- eval: DebuggerClient.requester({
- type: "clientEvaluate",
- frame: arg(0),
- expression: arg(1)
- }, {
- before: function (packet) {
- this._assertPaused("eval");
- // Put the client in a tentative "resuming" state so we can prevent
- // further requests that should only be sent in the paused state.
- this._state = "resuming";
- return packet;
- },
- after: function (response) {
- if (response.error) {
- // There was an error resuming, back to paused state.
- this._state = "paused";
- }
- return response;
- },
- }),
-
- /**
- * Detach from the thread actor.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- detach: DebuggerClient.requester({
- type: "detach"
- }, {
- after: function (response) {
- this.client.unregisterClient(this);
- this._parent.thread = null;
- return response;
- },
- }),
-
- /**
- * Release multiple thread-lifetime object actors. If any pause-lifetime
- * actors are included in the request, a |notReleasable| error will return,
- * but all the thread-lifetime ones will have been released.
- *
- * @param array actors
- * An array with actor IDs to release.
- */
- releaseMany: DebuggerClient.requester({
- type: "releaseMany",
- actors: arg(0),
- }),
-
- /**
- * Promote multiple pause-lifetime object actors to thread-lifetime ones.
- *
- * @param array actors
- * An array with actor IDs to promote.
- */
- threadGrips: DebuggerClient.requester({
- type: "threadGrips",
- actors: arg(0)
- }),
-
- /**
- * Return the event listeners defined on the page.
- *
- * @param onResponse Function
- * Called with the thread's response.
- */
- eventListeners: DebuggerClient.requester({
- type: "eventListeners"
- }),
-
- /**
- * Request the loaded sources for the current thread.
- *
- * @param onResponse Function
- * Called with the thread's response.
- */
- getSources: DebuggerClient.requester({
- type: "sources"
- }),
-
- /**
- * Clear the thread's source script cache. A scriptscleared event
- * will be sent.
- */
- _clearScripts: function () {
- if (Object.keys(this._scriptCache).length > 0) {
- this._scriptCache = {};
- this.emit("scriptscleared");
- }
- },
-
- /**
- * Request frames from the callstack for the current thread.
- *
- * @param start integer
- * The number of the youngest stack frame to return (the youngest
- * frame is 0).
- * @param count integer
- * The maximum number of frames to return, or null to return all
- * frames.
- * @param onResponse function
- * Called with the thread's response.
- */
- getFrames: DebuggerClient.requester({
- type: "frames",
- start: arg(0),
- count: arg(1)
- }),
-
- /**
- * An array of cached frames. Clients can observe the framesadded and
- * framescleared event to keep up to date on changes to this cache,
- * and can fill it using the fillFrames method.
- */
- get cachedFrames() {
- return this._frameCache;
- },
-
- /**
- * true if there are more stack frames available on the server.
- */
- get moreFrames() {
- return this.paused && (!this._frameCache || this._frameCache.length == 0
- || !this._frameCache[this._frameCache.length - 1].oldest);
- },
-
- /**
- * Request the frame environment.
- *
- * @param frameId string
- */
- getEnvironment: function (frameId) {
- return this.request({ to: frameId, type: "getEnvironment" });
- },
-
- /**
- * Ensure that at least total stack frames have been loaded in the
- * ThreadClient's stack frame cache. A framesadded event will be
- * sent when the stack frame cache is updated.
- *
- * @param total number
- * The minimum number of stack frames to be included.
- * @param callback function
- * Optional callback function called when frames have been loaded
- * @returns true if a framesadded notification should be expected.
- */
- fillFrames: function (total, callback = noop) {
- this._assertPaused("fillFrames");
- if (this._frameCache.length >= total) {
- return false;
- }
-
- let numFrames = this._frameCache.length;
-
- this.getFrames(numFrames, total - numFrames, (response) => {
- if (response.error) {
- callback(response);
- return;
- }
-
- let threadGrips = DevToolsUtils.values(this._threadGrips);
-
- for (let i in response.frames) {
- let frame = response.frames[i];
- if (!frame.where.source) {
- // Older servers use urls instead, so we need to resolve
- // them to source actors
- for (let grip of threadGrips) {
- if (grip instanceof SourceClient && grip.url === frame.url) {
- frame.where.source = grip._form;
- }
- }
- }
-
- this._frameCache[frame.depth] = frame;
- }
-
- // If we got as many frames as we asked for, there might be more
- // frames available.
- this.emit("framesadded");
-
- callback(response);
- });
-
- return true;
- },
-
- /**
- * Clear the thread's stack frame cache. A framescleared event
- * will be sent.
- */
- _clearFrames: function () {
- if (this._frameCache.length > 0) {
- this._frameCache = [];
- this.emit("framescleared");
- }
- },
-
- /**
- * Return a ObjectClient object for the given object grip.
- *
- * @param grip object
- * A pause-lifetime object grip returned by the protocol.
- */
- pauseGrip: function (grip) {
- if (grip.actor in this._pauseGrips) {
- return this._pauseGrips[grip.actor];
- }
-
- let client = new ObjectClient(this.client, grip);
- this._pauseGrips[grip.actor] = client;
- return client;
- },
-
- /**
- * Get or create a long string client, checking the grip client cache if it
- * already exists.
- *
- * @param grip Object
- * The long string grip returned by the protocol.
- * @param gripCacheName String
- * The property name of the grip client cache to check for existing
- * clients in.
- */
- _longString: function (grip, gripCacheName) {
- if (grip.actor in this[gripCacheName]) {
- return this[gripCacheName][grip.actor];
- }
-
- let client = new LongStringClient(this.client, grip);
- this[gripCacheName][grip.actor] = client;
- return client;
- },
-
- /**
- * Return an instance of LongStringClient for the given long string grip that
- * is scoped to the current pause.
- *
- * @param grip Object
- * The long string grip returned by the protocol.
- */
- pauseLongString: function (grip) {
- return this._longString(grip, "_pauseGrips");
- },
-
- /**
- * Return an instance of LongStringClient for the given long string grip that
- * is scoped to the thread lifetime.
- *
- * @param grip Object
- * The long string grip returned by the protocol.
- */
- threadLongString: function (grip) {
- return this._longString(grip, "_threadGrips");
- },
-
- /**
- * Get or create an ArrayBuffer client, checking the grip client cache if it
- * already exists.
- *
- * @param grip Object
- * The ArrayBuffer grip returned by the protocol.
- * @param gripCacheName String
- * The property name of the grip client cache to check for existing
- * clients in.
- */
- _arrayBuffer: function (grip, gripCacheName) {
- if (grip.actor in this[gripCacheName]) {
- return this[gripCacheName][grip.actor];
- }
-
- let client = new ArrayBufferClient(this.client, grip);
- this[gripCacheName][grip.actor] = client;
- return client;
- },
-
- /**
- * Return an instance of ArrayBufferClient for the given ArrayBuffer grip that
- * is scoped to the thread lifetime.
- *
- * @param grip Object
- * The ArrayBuffer grip returned by the protocol.
- */
- threadArrayBuffer: function (grip) {
- return this._arrayBuffer(grip, "_threadGrips");
- },
-
- /**
- * Clear and invalidate all the grip clients from the given cache.
- *
- * @param gripCacheName
- * The property name of the grip cache we want to clear.
- */
- _clearObjectClients: function (gripCacheName) {
- for (let id in this[gripCacheName]) {
- this[gripCacheName][id].valid = false;
- }
- this[gripCacheName] = {};
- },
-
- /**
- * Invalidate pause-lifetime grip clients and clear the list of current grip
- * clients.
- */
- _clearPauseGrips: function () {
- this._clearObjectClients("_pauseGrips");
- },
-
- /**
- * Invalidate thread-lifetime grip clients and clear the list of current grip
- * clients.
- */
- _clearThreadGrips: function () {
- this._clearObjectClients("_threadGrips");
- },
-
- /**
- * Handle thread state change by doing necessary cleanup and notifying all
- * registered listeners.
- */
- _onThreadState: function (packet) {
- this._state = ThreadStateTypes[packet.type];
- // The debugger UI may not be initialized yet so we want to keep
- // the packet around so it knows what to pause state to display
- // when it's initialized
- this._lastPausePacket = packet.type === "resumed" ? null : packet;
- this._clearFrames();
- this._clearPauseGrips();
- packet.type === ThreadStateTypes.detached && this._clearThreadGrips();
- this.client._eventsEnabled && this.emit(packet.type, packet);
- },
-
- getLastPausePacket: function () {
- return this._lastPausePacket;
- },
-
- /**
- * Return an EnvironmentClient instance for the given environment actor form.
- */
- environment: function (form) {
- return new EnvironmentClient(this.client, form);
- },
-
- /**
- * Return an instance of SourceClient for the given source actor form.
- */
- source: function (form) {
- if (form.actor in this._threadGrips) {
- return this._threadGrips[form.actor];
- }
-
- this._threadGrips[form.actor] = new SourceClient(this, form);
- return this._threadGrips[form.actor];
- },
-
- /**
- * Request the prototype and own properties of mutlipleObjects.
- *
- * @param onResponse function
- * Called with the request's response.
- * @param actors [string]
- * List of actor ID of the queried objects.
- */
- getPrototypesAndProperties: DebuggerClient.requester({
- type: "prototypesAndProperties",
- actors: arg(0)
- }),
-
- events: ["newSource"]
-};
-
-eventSource(ThreadClient.prototype);
-
-/**
- * Creates a tracing profiler client for the remote debugging protocol
- * server. This client is a front to the trace actor created on the
- * server side, hiding the protocol details in a traditional
- * JavaScript API.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param actor string
- * The actor ID for this thread.
- */
-function TraceClient(client, actor) {
- this._client = client;
- this._actor = actor;
- this._activeTraces = new Set();
- this._waitingPackets = new Map();
- this._expectedPacket = 0;
- this.request = this._client.request;
- this.events = [];
-}
-
-TraceClient.prototype = {
- get actor() {
- return this._actor;
- },
- get tracing() {
- return this._activeTraces.size > 0;
- },
-
- get _transport() {
- return this._client._transport;
- },
-
- /**
- * Detach from the trace actor.
- */
- detach: DebuggerClient.requester({
- type: "detach"
- }, {
- after: function (response) {
- this._client.unregisterClient(this);
- return response;
- },
- }),
-
- /**
- * Start a new trace.
- *
- * @param trace [string]
- * An array of trace types to be recorded by the new trace.
- *
- * @param name string
- * The name of the new trace.
- *
- * @param onResponse function
- * Called with the request's response.
- */
- startTrace: DebuggerClient.requester({
- type: "startTrace",
- name: arg(1),
- trace: arg(0)
- }, {
- after: function (response) {
- if (response.error) {
- return response;
- }
-
- if (!this.tracing) {
- this._waitingPackets.clear();
- this._expectedPacket = 0;
- }
- this._activeTraces.add(response.name);
-
- return response;
- },
- }),
-
- /**
- * End a trace. If a name is provided, stop the named
- * trace. Otherwise, stop the most recently started trace.
- *
- * @param name string
- * The name of the trace to stop.
- *
- * @param onResponse function
- * Called with the request's response.
- */
- stopTrace: DebuggerClient.requester({
- type: "stopTrace",
- name: arg(0)
- }, {
- after: function (response) {
- if (response.error) {
- return response;
- }
-
- this._activeTraces.delete(response.name);
-
- return response;
- },
- })
-};
-
-/**
- * Grip clients are used to retrieve information about the relevant object.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param grip object
- * A pause-lifetime object grip returned by the protocol.
- */
-function ObjectClient(client, grip) {
- this._grip = grip;
- this._client = client;
- this.request = this._client.request;
-}
-exports.ObjectClient = ObjectClient;
-
-ObjectClient.prototype = {
- get actor() {
- return this._grip.actor;
- },
- get _transport() {
- return this._client._transport;
- },
-
- valid: true,
-
- get isFrozen() {
- return this._grip.frozen;
- },
- get isSealed() {
- return this._grip.sealed;
- },
- get isExtensible() {
- return this._grip.extensible;
- },
-
- getDefinitionSite: DebuggerClient.requester({
- type: "definitionSite"
- }, {
- before: function (packet) {
- if (this._grip.class != "Function") {
- throw new Error("getDefinitionSite is only valid for function grips.");
- }
- return packet;
- }
- }),
-
- /**
- * Request the names of a function's formal parameters.
- *
- * @param onResponse function
- * Called with an object of the form:
- * { parameterNames:[<parameterName>, ...] }
- * where each <parameterName> is the name of a parameter.
- */
- getParameterNames: DebuggerClient.requester({
- type: "parameterNames"
- }, {
- before: function (packet) {
- if (this._grip.class !== "Function") {
- throw new Error("getParameterNames is only valid for function grips.");
- }
- return packet;
- },
- }),
-
- /**
- * Request the names of the properties defined on the object and not its
- * prototype.
- *
- * @param onResponse function Called with the request's response.
- */
- getOwnPropertyNames: DebuggerClient.requester({
- type: "ownPropertyNames"
- }),
-
- /**
- * Request the prototype and own properties of the object.
- *
- * @param onResponse function Called with the request's response.
- */
- getPrototypeAndProperties: DebuggerClient.requester({
- type: "prototypeAndProperties"
- }),
-
- /**
- * Request a PropertyIteratorClient instance to ease listing
- * properties for this object.
- *
- * @param options Object
- * A dictionary object with various boolean attributes:
- * - ignoreIndexedProperties Boolean
- * If true, filters out Array items.
- * e.g. properties names between `0` and `object.length`.
- * - ignoreNonIndexedProperties Boolean
- * If true, filters out items that aren't array items
- * e.g. properties names that are not a number between `0`
- * and `object.length`.
- * - sort Boolean
- * If true, the iterator will sort the properties by name
- * before dispatching them.
- * @param onResponse function Called with the client instance.
- */
- enumProperties: DebuggerClient.requester({
- type: "enumProperties",
- options: arg(0)
- }, {
- after: function (response) {
- if (response.iterator) {
- return { iterator: new PropertyIteratorClient(this._client, response.iterator) };
- }
- return response;
- },
- }),
-
- /**
- * Request a PropertyIteratorClient instance to enumerate entries in a
- * Map/Set-like object.
- *
- * @param onResponse function Called with the request's response.
- */
- enumEntries: DebuggerClient.requester({
- type: "enumEntries"
- }, {
- before: function (packet) {
- if (!["Map", "WeakMap", "Set", "WeakSet"].includes(this._grip.class)) {
- throw new Error("enumEntries is only valid for Map/Set-like grips.");
- }
- return packet;
- },
- after: function (response) {
- if (response.iterator) {
- return {
- iterator: new PropertyIteratorClient(this._client, response.iterator)
- };
- }
- return response;
- }
- }),
-
- /**
- * Request a SymbolIteratorClient instance to enumerate symbols in an object.
- *
- * @param onResponse function Called with the request's response.
- */
- enumSymbols: DebuggerClient.requester({
- type: "enumSymbols"
- }, {
- before: function (packet) {
- if (this._grip.type !== "object") {
- throw new Error("enumSymbols is only valid for objects grips.");
- }
- return packet;
- },
- after: function (response) {
- if (response.iterator) {
- return {
- iterator: new SymbolIteratorClient(this._client, response.iterator)
- };
- }
- return response;
- }
- }),
-
- /**
- * Request the property descriptor of the object's specified property.
- *
- * @param name string The name of the requested property.
- * @param onResponse function Called with the request's response.
- */
- getProperty: DebuggerClient.requester({
- type: "property",
- name: arg(0)
- }),
-
- /**
- * Request the prototype of the object.
- *
- * @param onResponse function Called with the request's response.
- */
- getPrototype: DebuggerClient.requester({
- type: "prototype"
- }),
-
- /**
- * Request the display string of the object.
- *
- * @param onResponse function Called with the request's response.
- */
- getDisplayString: DebuggerClient.requester({
- type: "displayString"
- }),
-
- /**
- * Request the scope of the object.
- *
- * @param onResponse function Called with the request's response.
- */
- getScope: DebuggerClient.requester({
- type: "scope"
- }, {
- before: function (packet) {
- if (this._grip.class !== "Function") {
- throw new Error("scope is only valid for function grips.");
- }
- return packet;
- },
- }),
-
- /**
- * Request the promises directly depending on the current promise.
- */
- getDependentPromises: DebuggerClient.requester({
- type: "dependentPromises"
- }, {
- before: function (packet) {
- if (this._grip.class !== "Promise") {
- throw new Error("getDependentPromises is only valid for promise " +
- "grips.");
- }
- return packet;
- }
- }),
-
- /**
- * Request the stack to the promise's allocation point.
- */
- getPromiseAllocationStack: DebuggerClient.requester({
- type: "allocationStack"
- }, {
- before: function (packet) {
- if (this._grip.class !== "Promise") {
- throw new Error("getAllocationStack is only valid for promise grips.");
- }
- return packet;
- }
- }),
-
- /**
- * Request the stack to the promise's fulfillment point.
- */
- getPromiseFulfillmentStack: DebuggerClient.requester({
- type: "fulfillmentStack"
- }, {
- before: function (packet) {
- if (this._grip.class !== "Promise") {
- throw new Error("getPromiseFulfillmentStack is only valid for " +
- "promise grips.");
- }
- return packet;
- }
- }),
-
- /**
- * Request the stack to the promise's rejection point.
- */
- getPromiseRejectionStack: DebuggerClient.requester({
- type: "rejectionStack"
- }, {
- before: function (packet) {
- if (this._grip.class !== "Promise") {
- throw new Error("getPromiseRejectionStack is only valid for " +
- "promise grips.");
- }
- return packet;
- }
- })
-};
-
-/**
- * A PropertyIteratorClient provides a way to access to property names and
- * values of an object efficiently, slice by slice.
- * Note that the properties can be sorted in the backend,
- * this is controled while creating the PropertyIteratorClient
- * from ObjectClient.enumProperties.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param grip Object
- * A PropertyIteratorActor grip returned by the protocol via
- * TabActor.enumProperties request.
- */
-function PropertyIteratorClient(client, grip) {
- this._grip = grip;
- this._client = client;
- this.request = this._client.request;
-}
-
-PropertyIteratorClient.prototype = {
- get actor() {
- return this._grip.actor;
- },
-
- /**
- * Get the total number of properties available in the iterator.
- */
- get count() {
- return this._grip.count;
- },
-
- /**
- * Get one or more property names that correspond to the positions in the
- * indexes parameter.
- *
- * @param indexes Array
- * An array of property indexes.
- * @param callback Function
- * The function called when we receive the property names.
- */
- names: DebuggerClient.requester({
- type: "names",
- indexes: arg(0)
- }, {}),
-
- /**
- * Get a set of following property value(s).
- *
- * @param start Number
- * The index of the first property to fetch.
- * @param count Number
- * The number of properties to fetch.
- * @param callback Function
- * The function called when we receive the property values.
- */
- slice: DebuggerClient.requester({
- type: "slice",
- start: arg(0),
- count: arg(1)
- }, {}),
-
- /**
- * Get all the property values.
- *
- * @param callback Function
- * The function called when we receive the property values.
- */
- all: DebuggerClient.requester({
- type: "all"
- }, {}),
-};
-
-/**
- * A SymbolIteratorClient provides a way to access to symbols
- * of an object efficiently, slice by slice.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param grip Object
- * A SymbolIteratorActor grip returned by the protocol via
- * TabActor.enumSymbols request.
- */
-function SymbolIteratorClient(client, grip) {
- this._grip = grip;
- this._client = client;
- this.request = this._client.request;
-}
-
-SymbolIteratorClient.prototype = {
- get actor() {
- return this._grip.actor;
- },
-
- /**
- * Get the total number of symbols available in the iterator.
- */
- get count() {
- return this._grip.count;
- },
-
- /**
- * Get a set of following symbols.
- *
- * @param start Number
- * The index of the first symbol to fetch.
- * @param count Number
- * The number of symbols to fetch.
- * @param callback Function
- * The function called when we receive the symbols.
- */
- slice: DebuggerClient.requester({
- type: "slice",
- start: arg(0),
- count: arg(1)
- }, {}),
-
- /**
- * Get all the symbols.
- *
- * @param callback Function
- * The function called when we receive the symbols.
- */
- all: DebuggerClient.requester({
- type: "all"
- }, {}),
-};
+const {arg, DebuggerClient} = require("./debugger-client");
/**
* A ArrayBufferClient provides a way to access ArrayBuffer from the
* debugger server.
*
* @param client DebuggerClient
* The debugger client parent.
* @param grip Object
@@ -2936,491 +35,9 @@ ArrayBufferClient.prototype = {
slice: DebuggerClient.requester({
type: "slice",
start: arg(0),
count: arg(1)
}),
};
-/**
- * A LongStringClient provides a way to access "very long" strings from the
- * debugger server.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param grip Object
- * A pause-lifetime long string grip returned by the protocol.
- */
-function LongStringClient(client, grip) {
- this._grip = grip;
- this._client = client;
- this.request = this._client.request;
-}
-exports.LongStringClient = LongStringClient;
-
-LongStringClient.prototype = {
- get actor() {
- return this._grip.actor;
- },
- get length() {
- return this._grip.length;
- },
- get initial() {
- return this._grip.initial;
- },
- get _transport() {
- return this._client._transport;
- },
-
- valid: true,
-
- /**
- * Get the substring of this LongString from start to end.
- *
- * @param start Number
- * The starting index.
- * @param end Number
- * The ending index.
- * @param callback Function
- * The function called when we receive the substring.
- */
- substring: DebuggerClient.requester({
- type: "substring",
- start: arg(0),
- end: arg(1)
- }),
-};
-
-/**
- * A SourceClient provides a way to access the source text of a script.
- *
- * @param client ThreadClient
- * The thread client parent.
- * @param form Object
- * The form sent across the remote debugging protocol.
- */
-function SourceClient(client, form) {
- this._form = form;
- this._isBlackBoxed = form.isBlackBoxed;
- this._isPrettyPrinted = form.isPrettyPrinted;
- this._activeThread = client;
- this._client = client.client;
-}
-
-SourceClient.prototype = {
- get _transport() {
- return this._client._transport;
- },
- get isBlackBoxed() {
- return this._isBlackBoxed;
- },
- get isPrettyPrinted() {
- return this._isPrettyPrinted;
- },
- get actor() {
- return this._form.actor;
- },
- get request() {
- return this._client.request;
- },
- get url() {
- return this._form.url;
- },
-
- /**
- * Black box this SourceClient's source.
- *
- * @param callback Function
- * The callback function called when we receive the response from the server.
- */
- blackBox: DebuggerClient.requester({
- type: "blackbox"
- }, {
- after: function (response) {
- if (!response.error) {
- this._isBlackBoxed = true;
- if (this._activeThread) {
- this._activeThread.emit("blackboxchange", this);
- }
- }
- return response;
- }
- }),
-
- /**
- * Un-black box this SourceClient's source.
- *
- * @param callback Function
- * The callback function called when we receive the response from the server.
- */
- unblackBox: DebuggerClient.requester({
- type: "unblackbox"
- }, {
- after: function (response) {
- if (!response.error) {
- this._isBlackBoxed = false;
- if (this._activeThread) {
- this._activeThread.emit("blackboxchange", this);
- }
- }
- return response;
- }
- }),
-
- /**
- * Get Executable Lines from a source
- *
- * @param callback Function
- * The callback function called when we receive the response from the server.
- */
- getExecutableLines: function (cb = noop) {
- let packet = {
- to: this._form.actor,
- type: "getExecutableLines"
- };
-
- return this._client.request(packet).then(res => {
- cb(res.lines);
- return res.lines;
- });
- },
-
- /**
- * Get a long string grip for this SourceClient's source.
- */
- source: function (callback = noop) {
- let packet = {
- to: this._form.actor,
- type: "source"
- };
- return this._client.request(packet).then(response => {
- return this._onSourceResponse(response, callback);
- });
- },
-
- /**
- * Pretty print this source's text.
- */
- prettyPrint: function (indent, callback = noop) {
- const packet = {
- to: this._form.actor,
- type: "prettyPrint",
- indent
- };
- return this._client.request(packet).then(response => {
- if (!response.error) {
- this._isPrettyPrinted = true;
- this._activeThread._clearFrames();
- this._activeThread.emit("prettyprintchange", this);
- }
- return this._onSourceResponse(response, callback);
- });
- },
-
- /**
- * Stop pretty printing this source's text.
- */
- disablePrettyPrint: function (callback = noop) {
- const packet = {
- to: this._form.actor,
- type: "disablePrettyPrint"
- };
- return this._client.request(packet).then(response => {
- if (!response.error) {
- this._isPrettyPrinted = false;
- this._activeThread._clearFrames();
- this._activeThread.emit("prettyprintchange", this);
- }
- return this._onSourceResponse(response, callback);
- });
- },
-
- _onSourceResponse: function (response, callback) {
- if (response.error) {
- callback(response);
- return response;
- }
-
- if (typeof response.source === "string") {
- callback(response);
- return response;
- }
-
- let { contentType, source } = response;
- if (source.type === "arrayBuffer") {
- let arrayBuffer = this._activeThread.threadArrayBuffer(source);
- return arrayBuffer.slice(0, arrayBuffer.length).then(function (resp) {
- if (resp.error) {
- callback(resp);
- return resp;
- }
- // Keeping str as a string, ArrayBuffer/Uint8Array will not survive
- // setIn/mergeIn operations.
- const str = atob(resp.encoded);
- let newResponse = {
- source: {
- binary: str,
- toString: () => "[wasm]",
- },
- contentType,
- };
- callback(newResponse);
- return newResponse;
- });
- }
-
- let longString = this._activeThread.threadLongString(source);
- return longString.substring(0, longString.length).then(function (resp) {
- if (resp.error) {
- callback(resp);
- return resp;
- }
-
- let newResponse = {
- source: resp.substring,
- contentType: contentType
- };
- callback(newResponse);
- return newResponse;
- });
- },
-
- /**
- * Request to set a breakpoint in the specified location.
- *
- * @param object location
- * The location and condition of the breakpoint in
- * the form of { line[, column, condition] }.
- * @param function onResponse
- * Called with the thread's response.
- */
- setBreakpoint: function ({ line, column, condition, noSliding }, onResponse = noop) {
- // A helper function that sets the breakpoint.
- let doSetBreakpoint = callback => {
- let root = this._client.mainRoot;
- let location = {
- line,
- column,
- };
-
- let packet = {
- to: this.actor,
- type: "setBreakpoint",
- location,
- condition,
- noSliding,
- };
-
- // Backwards compatibility: send the breakpoint request to the
- // thread if the server doesn't support Debugger.Source actors.
- if (!root.traits.debuggerSourceActors) {
- packet.to = this._activeThread.actor;
- packet.location.url = this.url;
- }
-
- return this._client.request(packet).then(response => {
- // Ignoring errors, since the user may be setting a breakpoint in a
- // dead script that will reappear on a page reload.
- let bpClient;
- if (response.actor) {
- bpClient = new BreakpointClient(
- this._client,
- this,
- response.actor,
- location,
- root.traits.conditionalBreakpoints ? condition : undefined
- );
- }
- onResponse(response, bpClient);
- if (callback) {
- callback();
- }
- return [response, bpClient];
- });
- };
-
- // If the debuggee is paused, just set the breakpoint.
- if (this._activeThread.paused) {
- return doSetBreakpoint();
- }
- // Otherwise, force a pause in order to set the breakpoint.
- return this._activeThread.interrupt().then(response => {
- if (response.error) {
- // Can't set the breakpoint if pausing failed.
- onResponse(response);
- return response;
- }
-
- const { type, why } = response;
- const cleanUp = type == "paused" && why.type == "interrupted"
- ? () => this._activeThread.resume()
- : noop;
-
- return doSetBreakpoint(cleanUp);
- });
- }
-};
-
-/**
- * Breakpoint clients are used to remove breakpoints that are no longer used.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param sourceClient SourceClient
- * The source where this breakpoint exists
- * @param actor string
- * The actor ID for this breakpoint.
- * @param location object
- * The location of the breakpoint. This is an object with two properties:
- * url and line.
- * @param condition string
- * The conditional expression of the breakpoint
- */
-function BreakpointClient(client, sourceClient, actor, location, condition) {
- this._client = client;
- this._actor = actor;
- this.location = location;
- this.location.actor = sourceClient.actor;
- this.location.url = sourceClient.url;
- this.source = sourceClient;
- this.request = this._client.request;
-
- // The condition property should only exist if it's a truthy value
- if (condition) {
- this.condition = condition;
- }
-}
-
-BreakpointClient.prototype = {
-
- _actor: null,
- get actor() {
- return this._actor;
- },
- get _transport() {
- return this._client._transport;
- },
-
- /**
- * Remove the breakpoint from the server.
- */
- remove: DebuggerClient.requester({
- type: "delete"
- }),
-
- /**
- * Determines if this breakpoint has a condition
- */
- hasCondition: function () {
- let root = this._client.mainRoot;
- // XXX bug 990137: We will remove support for client-side handling of
- // conditional breakpoints
- if (root.traits.conditionalBreakpoints) {
- return "condition" in this;
- }
- return "conditionalExpression" in this;
- },
-
- /**
- * Get the condition of this breakpoint. Currently we have to
- * support locally emulated conditional breakpoints until the
- * debugger servers are updated (see bug 990137). We used a
- * different property when moving it server-side to ensure that we
- * are testing the right code.
- */
- getCondition: function () {
- let root = this._client.mainRoot;
- if (root.traits.conditionalBreakpoints) {
- return this.condition;
- }
- return this.conditionalExpression;
- },
-
- /**
- * Set the condition of this breakpoint
- */
- setCondition: function (gThreadClient, condition) {
- let root = this._client.mainRoot;
- let deferred = promise.defer();
-
- if (root.traits.conditionalBreakpoints) {
- let info = {
- line: this.location.line,
- column: this.location.column,
- condition: condition
- };
-
- // Remove the current breakpoint and add a new one with the
- // condition.
- this.remove(response => {
- if (response && response.error) {
- deferred.reject(response);
- return;
- }
-
- this.source.setBreakpoint(info, (resp, newBreakpoint) => {
- if (resp && resp.error) {
- deferred.reject(resp);
- } else {
- deferred.resolve(newBreakpoint);
- }
- });
- });
- } else {
- // The property shouldn't even exist if the condition is blank
- if (condition === "") {
- delete this.conditionalExpression;
- } else {
- this.conditionalExpression = condition;
- }
- deferred.resolve(this);
- }
-
- return deferred.promise;
- }
-};
-
-eventSource(BreakpointClient.prototype);
-
-/**
- * Environment clients are used to manipulate the lexical environment actors.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param form Object
- * The form sent across the remote debugging protocol.
- */
-function EnvironmentClient(client, form) {
- this._client = client;
- this._form = form;
- this.request = this._client.request;
-}
-exports.EnvironmentClient = EnvironmentClient;
-
-EnvironmentClient.prototype = {
-
- get actor() {
- return this._form.actor;
- },
- get _transport() {
- return this._client._transport;
- },
-
- /**
- * Fetches the bindings introduced by this lexical environment.
- */
- getBindings: DebuggerClient.requester({
- type: "bindings"
- }),
-
- /**
- * Changes the value of the identifier whose name is name (a string) to that
- * represented by value (a grip).
- */
- assign: DebuggerClient.requester({
- type: "assign",
- name: arg(0),
- value: arg(1)
- })
-};
-
-eventSource(EnvironmentClient.prototype);
+module.exports = ArrayBufferClient;
copy from devtools/shared/client/main.js
copy to devtools/shared/client/breakpoint-client.js
--- a/devtools/shared/client/main.js
+++ b/devtools/shared/client/breakpoint-client.js
@@ -1,3269 +1,19 @@
-/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
-/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
-const { Ci, Cu } = require("chrome");
-const DevToolsUtils = require("devtools/shared/DevToolsUtils");
-const { getStack, callFunctionWithAsyncStack } = require("devtools/shared/platform/stack");
-
+const { Cu } = require("chrome");
const promise = Cu.import("resource://devtools/shared/deprecated-sync-thenables.js", {}).Promise;
-loader.lazyRequireGetter(this, "EventEmitter", "devtools/shared/event-emitter");
-loader.lazyRequireGetter(this, "WebConsoleClient", "devtools/shared/webconsole/client", true);
-loader.lazyRequireGetter(this, "DebuggerSocket", "devtools/shared/security/socket", true);
-loader.lazyRequireGetter(this, "Authentication", "devtools/shared/security/auth");
-
-const noop = () => {};
-
-/**
- * TODO: Get rid of this API in favor of EventTarget (bug 1042642)
- *
- * Add simple event notification to a prototype object. Any object that has
- * some use for event notifications or the observer pattern in general can be
- * augmented with the necessary facilities by passing its prototype to this
- * function.
- *
- * @param proto object
- * The prototype object that will be modified.
- */
-function eventSource(proto) {
- /**
- * Add a listener to the event source for a given event.
- *
- * @param name string
- * The event to listen for.
- * @param listener function
- * Called when the event is fired. If the same listener
- * is added more than once, it will be called once per
- * addListener call.
- */
- proto.addListener = function (name, listener) {
- if (typeof listener != "function") {
- throw TypeError("Listeners must be functions.");
- }
-
- if (!this._listeners) {
- this._listeners = {};
- }
-
- this._getListeners(name).push(listener);
- };
-
- /**
- * Add a listener to the event source for a given event. The
- * listener will be removed after it is called for the first time.
- *
- * @param name string
- * The event to listen for.
- * @param listener function
- * Called when the event is fired.
- */
- proto.addOneTimeListener = function (name, listener) {
- let l = (...args) => {
- this.removeListener(name, l);
- listener.apply(null, args);
- };
- this.addListener(name, l);
- };
-
- /**
- * Remove a listener from the event source previously added with
- * addListener().
- *
- * @param name string
- * The event name used during addListener to add the listener.
- * @param listener function
- * The callback to remove. If addListener was called multiple
- * times, all instances will be removed.
- */
- proto.removeListener = function (name, listener) {
- if (!this._listeners || (listener && !this._listeners[name])) {
- return;
- }
-
- if (!listener) {
- this._listeners[name] = [];
- } else {
- this._listeners[name] =
- this._listeners[name].filter(l => l != listener);
- }
- };
-
- /**
- * Returns the listeners for the specified event name. If none are defined it
- * initializes an empty list and returns that.
- *
- * @param name string
- * The event name.
- */
- proto._getListeners = function (name) {
- if (name in this._listeners) {
- return this._listeners[name];
- }
- this._listeners[name] = [];
- return this._listeners[name];
- };
-
- /**
- * Notify listeners of an event.
- *
- * @param name string
- * The event to fire.
- * @param arguments
- * All arguments will be passed along to the listeners,
- * including the name argument.
- */
- proto.emit = function () {
- if (!this._listeners) {
- return;
- }
-
- let name = arguments[0];
- let listeners = this._getListeners(name).slice(0);
-
- for (let listener of listeners) {
- try {
- listener.apply(null, arguments);
- } catch (e) {
- // Prevent a bad listener from interfering with the others.
- DevToolsUtils.reportException("notify event '" + name + "'", e);
- }
- }
- };
-}
-
-/**
- * Set of protocol messages that affect thread state, and the
- * state the actor is in after each message.
- */
-const ThreadStateTypes = {
- "paused": "paused",
- "resumed": "attached",
- "detached": "detached",
- "running": "attached"
-};
-
-/**
- * Set of protocol messages that are sent by the server without a prior request
- * by the client.
- */
-const UnsolicitedNotifications = {
- "consoleAPICall": "consoleAPICall",
- "eventNotification": "eventNotification",
- "fileActivity": "fileActivity",
- "lastPrivateContextExited": "lastPrivateContextExited",
- "logMessage": "logMessage",
- "networkEvent": "networkEvent",
- "networkEventUpdate": "networkEventUpdate",
- "newGlobal": "newGlobal",
- "newScript": "newScript",
- "tabDetached": "tabDetached",
- "tabListChanged": "tabListChanged",
- "reflowActivity": "reflowActivity",
- "addonListChanged": "addonListChanged",
- "workerListChanged": "workerListChanged",
- "serviceWorkerRegistrationListChanged": "serviceWorkerRegistrationList",
- "tabNavigated": "tabNavigated",
- "frameUpdate": "frameUpdate",
- "pageError": "pageError",
- "documentLoad": "documentLoad",
- "enteredFrame": "enteredFrame",
- "exitedFrame": "exitedFrame",
- "appOpen": "appOpen",
- "appClose": "appClose",
- "appInstall": "appInstall",
- "appUninstall": "appUninstall",
- "evaluationResult": "evaluationResult",
- "newSource": "newSource",
- "updatedSource": "updatedSource",
- "inspectObject": "inspectObject"
-};
-
-/**
- * Set of pause types that are sent by the server and not as an immediate
- * response to a client request.
- */
-const UnsolicitedPauses = {
- "resumeLimit": "resumeLimit",
- "debuggerStatement": "debuggerStatement",
- "breakpoint": "breakpoint",
- "DOMEvent": "DOMEvent",
- "watchpoint": "watchpoint",
- "exception": "exception"
-};
-
-/**
- * Creates a client for the remote debugging protocol server. This client
- * provides the means to communicate with the server and exchange the messages
- * required by the protocol in a traditional JavaScript API.
- */
-const DebuggerClient = exports.DebuggerClient = function (transport) {
- this._transport = transport;
- this._transport.hooks = this;
-
- // Map actor ID to client instance for each actor type.
- this._clients = new Map();
-
- this._pendingRequests = new Map();
- this._activeRequests = new Map();
- this._eventsEnabled = true;
-
- this.traits = {};
-
- this.request = this.request.bind(this);
- this.localTransport = this._transport.onOutputStreamReady === undefined;
-
- /*
- * As the first thing on the connection, expect a greeting packet from
- * the connection's root actor.
- */
- this.mainRoot = null;
- this.expectReply("root", (packet) => {
- this.mainRoot = new RootClient(this, packet);
- this.emit("connected", packet.applicationType, packet.traits);
- });
-};
-
-/**
- * A declarative helper for defining methods that send requests to the server.
- *
- * @param packetSkeleton
- * The form of the packet to send. Can specify fields to be filled from
- * the parameters by using the |arg| function.
- * @param before
- * The function to call before sending the packet. Is passed the packet,
- * and the return value is used as the new packet. The |this| context is
- * the instance of the client object we are defining a method for.
- * @param after
- * The function to call after the response is received. It is passed the
- * response, and the return value is considered the new response that
- * will be passed to the callback. The |this| context is the instance of
- * the client object we are defining a method for.
- * @return Request
- * The `Request` object that is a Promise object and resolves once
- * we receive the response. (See request method for more details)
- */
-DebuggerClient.requester = function (packetSkeleton, config = {}) {
- let { before, after } = config;
- return DevToolsUtils.makeInfallible(function (...args) {
- let outgoingPacket = {
- to: packetSkeleton.to || this.actor
- };
-
- let maxPosition = -1;
- for (let k of Object.keys(packetSkeleton)) {
- if (packetSkeleton[k] instanceof DebuggerClient.Argument) {
- let { position } = packetSkeleton[k];
- outgoingPacket[k] = packetSkeleton[k].getArgument(args);
- maxPosition = Math.max(position, maxPosition);
- } else {
- outgoingPacket[k] = packetSkeleton[k];
- }
- }
-
- if (before) {
- outgoingPacket = before.call(this, outgoingPacket);
- }
-
- return this.request(outgoingPacket, DevToolsUtils.makeInfallible((response) => {
- if (after) {
- let { from } = response;
- response = after.call(this, response);
- if (!response.from) {
- response.from = from;
- }
- }
-
- // The callback is always the last parameter.
- let thisCallback = args[maxPosition + 1];
- if (thisCallback) {
- thisCallback(response);
- }
- return response;
- }, "DebuggerClient.requester request callback"));
- }, "DebuggerClient.requester");
-};
-
-function arg(pos) {
- return new DebuggerClient.Argument(pos);
-}
-
-DebuggerClient.Argument = function (position) {
- this.position = position;
-};
-
-DebuggerClient.Argument.prototype.getArgument = function (params) {
- if (!(this.position in params)) {
- throw new Error("Bad index into params: " + this.position);
- }
- return params[this.position];
-};
-
-// Expose these to save callers the trouble of importing DebuggerSocket
-DebuggerClient.socketConnect = function (options) {
- // Defined here instead of just copying the function to allow lazy-load
- return DebuggerSocket.connect(options);
-};
-DevToolsUtils.defineLazyGetter(DebuggerClient, "Authenticators", () => {
- return Authentication.Authenticators;
-});
-DevToolsUtils.defineLazyGetter(DebuggerClient, "AuthenticationResult", () => {
- return Authentication.AuthenticationResult;
-});
-
-DebuggerClient.prototype = {
- /**
- * Connect to the server and start exchanging protocol messages.
- *
- * @param onConnected function
- * If specified, will be called when the greeting packet is
- * received from the debugging server.
- *
- * @return Promise
- * Resolves once connected with an array whose first element
- * is the application type, by default "browser", and the second
- * element is the traits object (help figure out the features
- * and behaviors of the server we connect to. See RootActor).
- */
- connect: function (onConnected) {
- let deferred = promise.defer();
- this.emit("connect");
-
- // Also emit the event on the |DebuggerClient| object (not on the instance),
- // so it's possible to track all instances.
- EventEmitter.emit(DebuggerClient, "connect", this);
-
- this.addOneTimeListener("connected", (name, applicationType, traits) => {
- this.traits = traits;
- if (onConnected) {
- onConnected(applicationType, traits);
- }
- deferred.resolve([applicationType, traits]);
- });
-
- this._transport.ready();
- return deferred.promise;
- },
-
- /**
- * Shut down communication with the debugging server.
- *
- * @param onClosed function
- * If specified, will be called when the debugging connection
- * has been closed. This parameter is deprecated - please use
- * the returned Promise.
- * @return Promise
- * Resolves after the underlying transport is closed.
- */
- close: function (onClosed) {
- let deferred = promise.defer();
- if (onClosed) {
- deferred.promise.then(onClosed);
- }
-
- // Disable detach event notifications, because event handlers will be in a
- // cleared scope by the time they run.
- this._eventsEnabled = false;
-
- let cleanup = () => {
- this._transport.close();
- this._transport = null;
- };
-
- // If the connection is already closed,
- // there is no need to detach client
- // as we won't be able to send any message.
- if (this._closed) {
- cleanup();
- deferred.resolve();
- return deferred.promise;
- }
-
- this.addOneTimeListener("closed", deferred.resolve);
-
- // Call each client's `detach` method by calling
- // lastly registered ones first to give a chance
- // to detach child clients first.
- let clients = [...this._clients.values()];
- this._clients.clear();
- const detachClients = () => {
- let client = clients.pop();
- if (!client) {
- // All clients detached.
- cleanup();
- return;
- }
- if (client.detach) {
- client.detach(detachClients);
- return;
- }
- detachClients();
- };
- detachClients();
-
- return deferred.promise;
- },
-
- /*
- * This function exists only to preserve DebuggerClient's interface;
- * new code should say 'client.mainRoot.listTabs()'.
- */
- listTabs: function (onResponse) {
- return this.mainRoot.listTabs(onResponse);
- },
-
- /*
- * This function exists only to preserve DebuggerClient's interface;
- * new code should say 'client.mainRoot.listAddons()'.
- */
- listAddons: function (onResponse) {
- return this.mainRoot.listAddons(onResponse);
- },
-
- getTab: function (filter) {
- return this.mainRoot.getTab(filter);
- },
-
- /**
- * Attach to a tab actor.
- *
- * @param string tabActor
- * The actor ID for the tab to attach.
- * @param function onResponse
- * Called with the response packet and a TabClient
- * (which will be undefined on error).
- */
- attachTab: function (tabActor, onResponse = noop) {
- if (this._clients.has(tabActor)) {
- let cachedTab = this._clients.get(tabActor);
- let cachedResponse = {
- cacheDisabled: cachedTab.cacheDisabled,
- javascriptEnabled: cachedTab.javascriptEnabled,
- traits: cachedTab.traits,
- };
- DevToolsUtils.executeSoon(() => onResponse(cachedResponse, cachedTab));
- return promise.resolve([cachedResponse, cachedTab]);
- }
-
- let packet = {
- to: tabActor,
- type: "attach"
- };
- return this.request(packet).then(response => {
- let tabClient;
- if (!response.error) {
- tabClient = new TabClient(this, response);
- this.registerClient(tabClient);
- }
- onResponse(response, tabClient);
- return [response, tabClient];
- });
- },
-
- attachWorker: function (workerActor, onResponse = noop) {
- let workerClient = this._clients.get(workerActor);
- if (workerClient !== undefined) {
- let response = {
- from: workerClient.actor,
- type: "attached",
- url: workerClient.url
- };
- DevToolsUtils.executeSoon(() => onResponse(response, workerClient));
- return promise.resolve([response, workerClient]);
- }
-
- return this.request({ to: workerActor, type: "attach" }).then(response => {
- if (response.error) {
- onResponse(response, null);
- return [response, null];
- }
-
- workerClient = new WorkerClient(this, response);
- this.registerClient(workerClient);
- onResponse(response, workerClient);
- return [response, workerClient];
- });
- },
-
- /**
- * Attach to an addon actor.
- *
- * @param string addonActor
- * The actor ID for the addon to attach.
- * @param function onResponse
- * Called with the response packet and a AddonClient
- * (which will be undefined on error).
- */
- attachAddon: function (addonActor, onResponse = noop) {
- let packet = {
- to: addonActor,
- type: "attach"
- };
- return this.request(packet).then(response => {
- let addonClient;
- if (!response.error) {
- addonClient = new AddonClient(this, addonActor);
- this.registerClient(addonClient);
- this.activeAddon = addonClient;
- }
- onResponse(response, addonClient);
- return [response, addonClient];
- });
- },
-
- /**
- * Attach to a Web Console actor.
- *
- * @param string consoleActor
- * The ID for the console actor to attach to.
- * @param array listeners
- * The console listeners you want to start.
- * @param function onResponse
- * Called with the response packet and a WebConsoleClient
- * instance (which will be undefined on error).
- */
- attachConsole:
- function (consoleActor, listeners, onResponse = noop) {
- let packet = {
- to: consoleActor,
- type: "startListeners",
- listeners: listeners,
- };
-
- return this.request(packet).then(response => {
- let consoleClient;
- if (!response.error) {
- if (this._clients.has(consoleActor)) {
- consoleClient = this._clients.get(consoleActor);
- } else {
- consoleClient = new WebConsoleClient(this, response);
- this.registerClient(consoleClient);
- }
- }
- onResponse(response, consoleClient);
- return [response, consoleClient];
- });
- },
-
- /**
- * Attach to a global-scoped thread actor for chrome debugging.
- *
- * @param string threadActor
- * The actor ID for the thread to attach.
- * @param function onResponse
- * Called with the response packet and a ThreadClient
- * (which will be undefined on error).
- * @param object options
- * Configuration options.
- * - useSourceMaps: whether to use source maps or not.
- */
- attachThread: function (threadActor, onResponse = noop, options = {}) {
- if (this._clients.has(threadActor)) {
- let client = this._clients.get(threadActor);
- DevToolsUtils.executeSoon(() => onResponse({}, client));
- return promise.resolve([{}, client]);
- }
-
- let packet = {
- to: threadActor,
- type: "attach",
- options,
- };
- return this.request(packet).then(response => {
- let threadClient;
- if (!response.error) {
- threadClient = new ThreadClient(this, threadActor);
- this.registerClient(threadClient);
- }
- onResponse(response, threadClient);
- return [response, threadClient];
- });
- },
-
- /**
- * Attach to a trace actor.
- *
- * @param string traceActor
- * The actor ID for the tracer to attach.
- * @param function onResponse
- * Called with the response packet and a TraceClient
- * (which will be undefined on error).
- */
- attachTracer: function (traceActor, onResponse = noop) {
- if (this._clients.has(traceActor)) {
- let client = this._clients.get(traceActor);
- DevToolsUtils.executeSoon(() => onResponse({}, client));
- return promise.resolve([{}, client]);
- }
-
- let packet = {
- to: traceActor,
- type: "attach"
- };
- return this.request(packet).then(response => {
- let traceClient;
- if (!response.error) {
- traceClient = new TraceClient(this, traceActor);
- this.registerClient(traceClient);
- }
- onResponse(response, traceClient);
- return [response, traceClient];
- });
- },
-
- /**
- * Fetch the ChromeActor for the main process or ChildProcessActor for a
- * a given child process ID.
- *
- * @param number id
- * The ID for the process to attach (returned by `listProcesses`).
- * Connected to the main process if omitted, or is 0.
- */
- getProcess: function (id) {
- let packet = {
- to: "root",
- type: "getProcess"
- };
- if (typeof (id) == "number") {
- packet.id = id;
- }
- return this.request(packet);
- },
-
- /**
- * Release an object actor.
- *
- * @param string actor
- * The actor ID to send the request to.
- * @param onResponse function
- * If specified, will be called with the response packet when
- * debugging server responds.
- */
- release: DebuggerClient.requester({
- to: arg(0),
- type: "release"
- }),
-
- /**
- * Send a request to the debugging server.
- *
- * @param packet object
- * A JSON packet to send to the debugging server.
- * @param onResponse function
- * If specified, will be called with the JSON response packet when
- * debugging server responds.
- * @return Request
- * This object emits a number of events to allow you to respond to
- * different parts of the request lifecycle.
- * It is also a Promise object, with a `then` method, that is resolved
- * whenever a JSON or a Bulk response is received; and is rejected
- * if the response is an error.
- * Note: This return value can be ignored if you are using JSON alone,
- * because the callback provided in |onResponse| will be bound to the
- * "json-reply" event automatically.
- *
- * Events emitted:
- * * json-reply: The server replied with a JSON packet, which is
- * passed as event data.
- * * bulk-reply: The server replied with bulk data, which you can read
- * using the event data object containing:
- * * actor: Name of actor that received the packet
- * * type: Name of actor's method that was called on receipt
- * * length: Size of the data to be read
- * * stream: This input stream should only be used directly if you
- * can ensure that you will read exactly |length| bytes
- * and will not close the stream when reading is complete
- * * done: If you use the stream directly (instead of |copyTo|
- * below), you must signal completion by resolving /
- * rejecting this deferred. If it's rejected, the
- * transport will be closed. If an Error is supplied as a
- * rejection value, it will be logged via |dumpn|. If you
- * do use |copyTo|, resolving is taken care of for you
- * when copying completes.
- * * copyTo: A helper function for getting your data out of the
- * stream that meets the stream handling requirements
- * above, and has the following signature:
- * @param output nsIAsyncOutputStream
- * The stream to copy to.
- * @return Promise
- * The promise is resolved when copying completes or
- * rejected if any (unexpected) errors occur.
- * This object also emits "progress" events for each chunk
- * that is copied. See stream-utils.js.
- */
- request: function (packet, onResponse) {
- if (!this.mainRoot) {
- throw Error("Have not yet received a hello packet from the server.");
- }
- let type = packet.type || "";
- if (!packet.to) {
- throw Error("'" + type + "' request packet has no destination.");
- }
-
- // The onResponse callback might modify the response, so we need to call
- // it and resolve the promise with its result if it's truthy.
- const safeOnResponse = response => {
- if (!onResponse) {
- return response;
- }
- return onResponse(response) || response;
- };
-
- if (this._closed) {
- let msg = "'" + type + "' request packet to " +
- "'" + packet.to + "' " +
- "can't be sent as the connection is closed.";
- let resp = { error: "connectionClosed", message: msg };
- return promise.reject(safeOnResponse(resp));
- }
-
- let request = new Request(packet);
- request.format = "json";
- request.stack = getStack();
-
- // Implement a Promise like API on the returned object
- // that resolves/rejects on request response
- let deferred = promise.defer();
- function listenerJson(resp) {
- removeRequestListeners();
- if (resp.error) {
- deferred.reject(safeOnResponse(resp));
- } else {
- deferred.resolve(safeOnResponse(resp));
- }
- }
- function listenerBulk(resp) {
- removeRequestListeners();
- deferred.resolve(safeOnResponse(resp));
- }
-
- const removeRequestListeners = () => {
- request.off("json-reply", listenerJson);
- request.off("bulk-reply", listenerBulk);
- };
-
- request.on("json-reply", listenerJson);
- request.on("bulk-reply", listenerBulk);
-
- this._sendOrQueueRequest(request);
- request.then = deferred.promise.then.bind(deferred.promise);
-
- return request;
- },
-
- /**
- * Transmit streaming data via a bulk request.
- *
- * This method initiates the bulk send process by queuing up the header data.
- * The caller receives eventual access to a stream for writing.
- *
- * Since this opens up more options for how the server might respond (it could
- * send back either JSON or bulk data), and the returned Request object emits
- * events for different stages of the request process that you may want to
- * react to.
- *
- * @param request Object
- * This is modeled after the format of JSON packets above, but does not
- * actually contain the data, but is instead just a routing header:
- * * actor: Name of actor that will receive the packet
- * * type: Name of actor's method that should be called on receipt
- * * length: Size of the data to be sent
- * @return Request
- * This object emits a number of events to allow you to respond to
- * different parts of the request lifecycle.
- *
- * Events emitted:
- * * bulk-send-ready: Ready to send bulk data to the server, using the
- * event data object containing:
- * * stream: This output stream should only be used directly if
- * you can ensure that you will write exactly |length|
- * bytes and will not close the stream when writing is
- * complete
- * * done: If you use the stream directly (instead of |copyFrom|
- * below), you must signal completion by resolving /
- * rejecting this deferred. If it's rejected, the
- * transport will be closed. If an Error is supplied as
- * a rejection value, it will be logged via |dumpn|. If
- * you do use |copyFrom|, resolving is taken care of for
- * you when copying completes.
- * * copyFrom: A helper function for getting your data onto the
- * stream that meets the stream handling requirements
- * above, and has the following signature:
- * @param input nsIAsyncInputStream
- * The stream to copy from.
- * @return Promise
- * The promise is resolved when copying completes or
- * rejected if any (unexpected) errors occur.
- * This object also emits "progress" events for each chunk
- * that is copied. See stream-utils.js.
- * * json-reply: The server replied with a JSON packet, which is
- * passed as event data.
- * * bulk-reply: The server replied with bulk data, which you can read
- * using the event data object containing:
- * * actor: Name of actor that received the packet
- * * type: Name of actor's method that was called on receipt
- * * length: Size of the data to be read
- * * stream: This input stream should only be used directly if you
- * can ensure that you will read exactly |length| bytes
- * and will not close the stream when reading is complete
- * * done: If you use the stream directly (instead of |copyTo|
- * below), you must signal completion by resolving /
- * rejecting this deferred. If it's rejected, the
- * transport will be closed. If an Error is supplied as a
- * rejection value, it will be logged via |dumpn|. If you
- * do use |copyTo|, resolving is taken care of for you
- * when copying completes.
- * * copyTo: A helper function for getting your data out of the
- * stream that meets the stream handling requirements
- * above, and has the following signature:
- * @param output nsIAsyncOutputStream
- * The stream to copy to.
- * @return Promise
- * The promise is resolved when copying completes or
- * rejected if any (unexpected) errors occur.
- * This object also emits "progress" events for each chunk
- * that is copied. See stream-utils.js.
- */
- startBulkRequest: function (request) {
- if (!this.traits.bulk) {
- throw Error("Server doesn't support bulk transfers");
- }
- if (!this.mainRoot) {
- throw Error("Have not yet received a hello packet from the server.");
- }
- if (!request.type) {
- throw Error("Bulk packet is missing the required 'type' field.");
- }
- if (!request.actor) {
- throw Error("'" + request.type + "' bulk packet has no destination.");
- }
- if (!request.length) {
- throw Error("'" + request.type + "' bulk packet has no length.");
- }
-
- request = new Request(request);
- request.format = "bulk";
-
- this._sendOrQueueRequest(request);
-
- return request;
- },
-
- /**
- * If a new request can be sent immediately, do so. Otherwise, queue it.
- */
- _sendOrQueueRequest(request) {
- let actor = request.actor;
- if (!this._activeRequests.has(actor)) {
- this._sendRequest(request);
- } else {
- this._queueRequest(request);
- }
- },
-
- /**
- * Send a request.
- * @throws Error if there is already an active request in flight for the same
- * actor.
- */
- _sendRequest(request) {
- let actor = request.actor;
- this.expectReply(actor, request);
-
- if (request.format === "json") {
- this._transport.send(request.request);
- return;
- }
-
- this._transport.startBulkSend(request.request).then((...args) => {
- request.emit("bulk-send-ready", ...args);
- });
- },
-
- /**
- * Queue a request to be sent later. Queues are only drained when an in
- * flight request to a given actor completes.
- */
- _queueRequest(request) {
- let actor = request.actor;
- let queue = this._pendingRequests.get(actor) || [];
- queue.push(request);
- this._pendingRequests.set(actor, queue);
- },
-
- /**
- * Attempt the next request to a given actor (if any).
- */
- _attemptNextRequest(actor) {
- if (this._activeRequests.has(actor)) {
- return;
- }
- let queue = this._pendingRequests.get(actor);
- if (!queue) {
- return;
- }
- let request = queue.shift();
- if (queue.length === 0) {
- this._pendingRequests.delete(actor);
- }
- this._sendRequest(request);
- },
-
- /**
- * Arrange to hand the next reply from |actor| to the handler bound to
- * |request|.
- *
- * DebuggerClient.prototype.request / startBulkRequest usually takes care of
- * establishing the handler for a given request, but in rare cases (well,
- * greetings from new root actors, is the only case at the moment) we must be
- * prepared for a "reply" that doesn't correspond to any request we sent.
- */
- expectReply: function (actor, request) {
- if (this._activeRequests.has(actor)) {
- throw Error("clashing handlers for next reply from " + actor);
- }
-
- // If a handler is passed directly (as it is with the handler for the root
- // actor greeting), create a dummy request to bind this to.
- if (typeof request === "function") {
- let handler = request;
- request = new Request();
- request.on("json-reply", handler);
- }
-
- this._activeRequests.set(actor, request);
- },
-
- // Transport hooks.
-
- /**
- * Called by DebuggerTransport to dispatch incoming packets as appropriate.
- *
- * @param packet object
- * The incoming packet.
- */
- onPacket: function (packet) {
- if (!packet.from) {
- DevToolsUtils.reportException(
- "onPacket",
- new Error("Server did not specify an actor, dropping packet: " +
- JSON.stringify(packet)));
- return;
- }
-
- // If we have a registered Front for this actor, let it handle the packet
- // and skip all the rest of this unpleasantness.
- let front = this.getActor(packet.from);
- if (front) {
- front.onPacket(packet);
- return;
- }
-
- // Check for "forwardingCancelled" here instead of using a client to handle it.
- // This is necessary because we might receive this event while the client is closing,
- // and the clients have already been removed by that point.
- if (this.mainRoot &&
- packet.from == this.mainRoot.actor &&
- packet.type == "forwardingCancelled") {
- this.purgeRequests(packet.prefix);
- return;
- }
-
- if (this._clients.has(packet.from) && packet.type) {
- let client = this._clients.get(packet.from);
- let type = packet.type;
- if (client.events.indexOf(type) != -1) {
- client.emit(type, packet);
- // we ignore the rest, as the client is expected to handle this packet.
- return;
- }
- }
-
- let activeRequest;
- // See if we have a handler function waiting for a reply from this
- // actor. (Don't count unsolicited notifications or pauses as
- // replies.)
- if (this._activeRequests.has(packet.from) &&
- !(packet.type in UnsolicitedNotifications) &&
- !(packet.type == ThreadStateTypes.paused &&
- packet.why.type in UnsolicitedPauses)) {
- activeRequest = this._activeRequests.get(packet.from);
- this._activeRequests.delete(packet.from);
- }
-
- // If there is a subsequent request for the same actor, hand it off to the
- // transport. Delivery of packets on the other end is always async, even
- // in the local transport case.
- this._attemptNextRequest(packet.from);
-
- // Packets that indicate thread state changes get special treatment.
- if (packet.type in ThreadStateTypes &&
- this._clients.has(packet.from) &&
- typeof this._clients.get(packet.from)._onThreadState == "function") {
- this._clients.get(packet.from)._onThreadState(packet);
- }
-
- // TODO: Bug 1151156 - Remove once Gecko 40 is on b2g-stable.
- if (!this.traits.noNeedToFakeResumptionOnNavigation) {
- // On navigation the server resumes, so the client must resume as well.
- // We achieve that by generating a fake resumption packet that triggers
- // the client's thread state change listeners.
- if (packet.type == UnsolicitedNotifications.tabNavigated &&
- this._clients.has(packet.from) &&
- this._clients.get(packet.from).thread) {
- let thread = this._clients.get(packet.from).thread;
- let resumption = { from: thread._actor, type: "resumed" };
- thread._onThreadState(resumption);
- }
- }
-
- // Only try to notify listeners on events, not responses to requests
- // that lack a packet type.
- if (packet.type) {
- this.emit(packet.type, packet);
- }
-
- if (activeRequest) {
- let emitReply = () => activeRequest.emit("json-reply", packet);
- if (activeRequest.stack) {
- callFunctionWithAsyncStack(emitReply, activeRequest.stack,
- "DevTools RDP");
- } else {
- emitReply();
- }
- }
- },
-
- /**
- * Called by the DebuggerTransport to dispatch incoming bulk packets as
- * appropriate.
- *
- * @param packet object
- * The incoming packet, which contains:
- * * actor: Name of actor that will receive the packet
- * * type: Name of actor's method that should be called on receipt
- * * length: Size of the data to be read
- * * stream: This input stream should only be used directly if you can
- * ensure that you will read exactly |length| bytes and will
- * not close the stream when reading is complete
- * * done: If you use the stream directly (instead of |copyTo|
- * below), you must signal completion by resolving /
- * rejecting this deferred. If it's rejected, the transport
- * will be closed. If an Error is supplied as a rejection
- * value, it will be logged via |dumpn|. If you do use
- * |copyTo|, resolving is taken care of for you when copying
- * completes.
- * * copyTo: A helper function for getting your data out of the stream
- * that meets the stream handling requirements above, and has
- * the following signature:
- * @param output nsIAsyncOutputStream
- * The stream to copy to.
- * @return Promise
- * The promise is resolved when copying completes or rejected
- * if any (unexpected) errors occur.
- * This object also emits "progress" events for each chunk
- * that is copied. See stream-utils.js.
- */
- onBulkPacket: function (packet) {
- let { actor } = packet;
-
- if (!actor) {
- DevToolsUtils.reportException(
- "onBulkPacket",
- new Error("Server did not specify an actor, dropping bulk packet: " +
- JSON.stringify(packet)));
- return;
- }
-
- // See if we have a handler function waiting for a reply from this
- // actor.
- if (!this._activeRequests.has(actor)) {
- return;
- }
-
- let activeRequest = this._activeRequests.get(actor);
- this._activeRequests.delete(actor);
-
- // If there is a subsequent request for the same actor, hand it off to the
- // transport. Delivery of packets on the other end is always async, even
- // in the local transport case.
- this._attemptNextRequest(actor);
-
- activeRequest.emit("bulk-reply", packet);
- },
-
- /**
- * Called by DebuggerTransport when the underlying stream is closed.
- *
- * @param status nsresult
- * The status code that corresponds to the reason for closing
- * the stream.
- */
- onClosed: function () {
- this._closed = true;
- this.emit("closed");
-
- this.purgeRequests();
-
- // The |_pools| array on the client-side currently is used only by
- // protocol.js to store active fronts, mirroring the actor pools found in
- // the server. So, read all usages of "pool" as "protocol.js front".
- //
- // In the normal case where we shutdown cleanly, the toolbox tells each tool
- // to close, and they each call |destroy| on any fronts they were using.
- // When |destroy| or |cleanup| is called on a protocol.js front, it also
- // removes itself from the |_pools| array. Once the toolbox has shutdown,
- // the connection is closed, and we reach here. All fronts (should have
- // been) |destroy|ed, so |_pools| should empty.
- //
- // If the connection instead aborts unexpectedly, we may end up here with
- // all fronts used during the life of the connection. So, we call |cleanup|
- // on them clear their state, reject pending requests, and remove themselves
- // from |_pools|. This saves the toolbox from hanging indefinitely, in case
- // it waits for some server response before shutdown that will now never
- // arrive.
- for (let pool of this._pools) {
- pool.cleanup();
- }
- },
-
- /**
- * Purge pending and active requests in this client.
- *
- * @param prefix string (optional)
- * If a prefix is given, only requests for actor IDs that start with the prefix
- * will be cleaned up. This is useful when forwarding of a portion of requests
- * is cancelled on the server.
- */
- purgeRequests(prefix = "") {
- let reject = function (type, request) {
- // Server can send packets on its own and client only pass a callback
- // to expectReply, so that there is no request object.
- let msg;
- if (request.request) {
- msg = "'" + request.request.type + "' " + type + " request packet" +
- " to '" + request.actor + "' " +
- "can't be sent as the connection just closed.";
- } else {
- msg = "server side packet can't be received as the connection just closed.";
- }
- let packet = { error: "connectionClosed", message: msg };
- request.emit("json-reply", packet);
- };
-
- let pendingRequestsToReject = [];
- this._pendingRequests.forEach((requests, actor) => {
- if (!actor.startsWith(prefix)) {
- return;
- }
- this._pendingRequests.delete(actor);
- pendingRequestsToReject = pendingRequestsToReject.concat(requests);
- });
- pendingRequestsToReject.forEach(request => reject("pending", request));
-
- let activeRequestsToReject = [];
- this._activeRequests.forEach((request, actor) => {
- if (!actor.startsWith(prefix)) {
- return;
- }
- this._activeRequests.delete(actor);
- activeRequestsToReject = activeRequestsToReject.concat(request);
- });
- activeRequestsToReject.forEach(request => reject("active", request));
- },
-
- /**
- * Search for all requests in process for this client, including those made via
- * protocol.js and wait all of them to complete. Since the requests seen when this is
- * first called may in turn trigger more requests, we keep recursing through this
- * function until there is no more activity.
- *
- * This is a fairly heavy weight process, so it's only meant to be used in tests.
- *
- * @return Promise
- * Resolved when all requests have settled.
- */
- waitForRequestsToSettle() {
- let requests = [];
-
- // Gather all pending and active requests in this client
- // The request object supports a Promise API for completion (it has .then())
- this._pendingRequests.forEach(requestsForActor => {
- // Each value is an array of pending requests
- requests = requests.concat(requestsForActor);
- });
- this._activeRequests.forEach(requestForActor => {
- // Each value is a single active request
- requests = requests.concat(requestForActor);
- });
-
- // protocol.js
- // Use a Set because some fronts (like domwalker) seem to have multiple parents.
- let fronts = new Set();
- let poolsToVisit = [...this._pools];
-
- // With protocol.js, each front can potentially have it's own pools containing child
- // fronts, forming a tree. Descend through all the pools to locate all child fronts.
- while (poolsToVisit.length) {
- let pool = poolsToVisit.shift();
- fronts.add(pool);
- for (let child of pool.poolChildren()) {
- poolsToVisit.push(child);
- }
- }
-
- // For each front, wait for its requests to settle
- for (let front of fronts) {
- if (front.hasRequests()) {
- requests.push(front.waitForRequestsToSettle());
- }
- }
-
- // Abort early if there are no requests
- if (!requests.length) {
- return Promise.resolve();
- }
-
- return DevToolsUtils.settleAll(requests).catch(() => {
- // One of the requests might have failed, but ignore that situation here and pipe
- // both success and failure through the same path. The important part is just that
- // we waited.
- }).then(() => {
- // Repeat, more requests may have started in response to those we just waited for
- return this.waitForRequestsToSettle();
- });
- },
-
- registerClient: function (client) {
- let actorID = client.actor;
- if (!actorID) {
- throw new Error("DebuggerServer.registerClient expects " +
- "a client instance with an `actor` attribute.");
- }
- if (!Array.isArray(client.events)) {
- throw new Error("DebuggerServer.registerClient expects " +
- "a client instance with an `events` attribute " +
- "that is an array.");
- }
- if (client.events.length > 0 && typeof (client.emit) != "function") {
- throw new Error("DebuggerServer.registerClient expects " +
- "a client instance with non-empty `events` array to" +
- "have an `emit` function.");
- }
- if (this._clients.has(actorID)) {
- throw new Error("DebuggerServer.registerClient already registered " +
- "a client for this actor.");
- }
- this._clients.set(actorID, client);
- },
-
- unregisterClient: function (client) {
- let actorID = client.actor;
- if (!actorID) {
- throw new Error("DebuggerServer.unregisterClient expects " +
- "a Client instance with a `actor` attribute.");
- }
- this._clients.delete(actorID);
- },
-
- /**
- * Actor lifetime management, echos the server's actor pools.
- */
- __pools: null,
- get _pools() {
- if (this.__pools) {
- return this.__pools;
- }
- this.__pools = new Set();
- return this.__pools;
- },
-
- addActorPool: function (pool) {
- this._pools.add(pool);
- },
- removeActorPool: function (pool) {
- this._pools.delete(pool);
- },
- getActor: function (actorID) {
- let pool = this.poolFor(actorID);
- return pool ? pool.get(actorID) : null;
- },
-
- poolFor: function (actorID) {
- for (let pool of this._pools) {
- if (pool.has(actorID)) {
- return pool;
- }
- }
- return null;
- },
-
- /**
- * Currently attached addon.
- */
- activeAddon: null
-};
-
-eventSource(DebuggerClient.prototype);
-
-class Request extends EventEmitter {
- constructor(request) {
- super();
- this.request = request;
- }
-
- get actor() {
- return this.request.to || this.request.actor;
- }
-}
-
-/**
- * Creates a tab client for the remote debugging protocol server. This client
- * is a front to the tab actor created in the server side, hiding the protocol
- * details in a traditional JavaScript API.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param form object
- * The protocol form for this tab.
- */
-function TabClient(client, form) {
- this.client = client;
- this._actor = form.from;
- this._threadActor = form.threadActor;
- this.javascriptEnabled = form.javascriptEnabled;
- this.cacheDisabled = form.cacheDisabled;
- this.thread = null;
- this.request = this.client.request;
- this.traits = form.traits || {};
- this.events = ["workerListChanged"];
-}
-
-TabClient.prototype = {
- get actor() {
- return this._actor;
- },
- get _transport() {
- return this.client._transport;
- },
-
- /**
- * Attach to a thread actor.
- *
- * @param object options
- * Configuration options.
- * - useSourceMaps: whether to use source maps or not.
- * @param function onResponse
- * Called with the response packet and a ThreadClient
- * (which will be undefined on error).
- */
- attachThread: function (options = {}, onResponse = noop) {
- if (this.thread) {
- DevToolsUtils.executeSoon(() => onResponse({}, this.thread));
- return promise.resolve([{}, this.thread]);
- }
-
- let packet = {
- to: this._threadActor,
- type: "attach",
- options,
- };
- return this.request(packet).then(response => {
- if (!response.error) {
- this.thread = new ThreadClient(this, this._threadActor);
- this.client.registerClient(this.thread);
- }
- onResponse(response, this.thread);
- return [response, this.thread];
- });
- },
-
- /**
- * Detach the client from the tab actor.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- detach: DebuggerClient.requester({
- type: "detach"
- }, {
- before: function (packet) {
- if (this.thread) {
- this.thread.detach();
- }
- return packet;
- },
- after: function (response) {
- this.client.unregisterClient(this);
- return response;
- },
- }),
-
- /**
- * Bring the window to the front.
- */
- focus: DebuggerClient.requester({
- type: "focus"
- }, {}),
-
- /**
- * Reload the page in this tab.
- *
- * @param [optional] object options
- * An object with a `force` property indicating whether or not
- * this reload should skip the cache
- */
- reload: function (options = { force: false }) {
- return this._reload(options);
- },
- _reload: DebuggerClient.requester({
- type: "reload",
- options: arg(0)
- }),
-
- /**
- * Navigate to another URL.
- *
- * @param string url
- * The URL to navigate to.
- */
- navigateTo: DebuggerClient.requester({
- type: "navigateTo",
- url: arg(0)
- }),
-
- /**
- * Reconfigure the tab actor.
- *
- * @param object options
- * A dictionary object of the new options to use in the tab actor.
- * @param function onResponse
- * Called with the response packet.
- */
- reconfigure: DebuggerClient.requester({
- type: "reconfigure",
- options: arg(0)
- }),
-
- listWorkers: DebuggerClient.requester({
- type: "listWorkers"
- }),
-
- attachWorker: function (workerActor, onResponse) {
- return this.client.attachWorker(workerActor, onResponse);
- },
-};
-
-eventSource(TabClient.prototype);
-
-function WorkerClient(client, form) {
- this.client = client;
- this._actor = form.from;
- this._isClosed = false;
- this._url = form.url;
-
- this._onClose = this._onClose.bind(this);
-
- this.addListener("close", this._onClose);
-
- this.traits = {};
-}
-
-WorkerClient.prototype = {
- get _transport() {
- return this.client._transport;
- },
-
- get request() {
- return this.client.request;
- },
-
- get actor() {
- return this._actor;
- },
-
- get url() {
- return this._url;
- },
-
- get isClosed() {
- return this._isClosed;
- },
-
- detach: DebuggerClient.requester({ type: "detach" }, {
- after: function (response) {
- if (this.thread) {
- this.client.unregisterClient(this.thread);
- }
- this.client.unregisterClient(this);
- return response;
- },
- }),
-
- attachThread: function (options = {}, onResponse = noop) {
- if (this.thread) {
- let response = [{
- type: "connected",
- threadActor: this.thread._actor,
- consoleActor: this.consoleActor,
- }, this.thread];
- DevToolsUtils.executeSoon(() => onResponse(response));
- return response;
- }
-
- // The connect call on server doesn't attach the thread as of version 44.
- return this.request({
- to: this._actor,
- type: "connect",
- options,
- }).then(connectResponse => {
- if (connectResponse.error) {
- onResponse(connectResponse, null);
- return [connectResponse, null];
- }
-
- return this.request({
- to: connectResponse.threadActor,
- type: "attach",
- options,
- }).then(attachResponse => {
- if (attachResponse.error) {
- onResponse(attachResponse, null);
- }
-
- this.thread = new ThreadClient(this, connectResponse.threadActor);
- this.consoleActor = connectResponse.consoleActor;
- this.client.registerClient(this.thread);
-
- onResponse(connectResponse, this.thread);
- return [connectResponse, this.thread];
- });
- }, error => {
- onResponse(error, null);
- });
- },
-
- _onClose: function () {
- this.removeListener("close", this._onClose);
-
- if (this.thread) {
- this.client.unregisterClient(this.thread);
- }
- this.client.unregisterClient(this);
- this._isClosed = true;
- },
-
- reconfigure: function () {
- return Promise.resolve();
- },
-
- events: ["close"]
-};
-
-eventSource(WorkerClient.prototype);
-
-function AddonClient(client, actor) {
- this._client = client;
- this._actor = actor;
- this.request = this._client.request;
- this.events = [];
-}
-
-AddonClient.prototype = {
- get actor() {
- return this._actor;
- },
- get _transport() {
- return this._client._transport;
- },
-
- /**
- * Detach the client from the addon actor.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- detach: DebuggerClient.requester({
- type: "detach"
- }, {
- after: function (response) {
- if (this._client.activeAddon === this) {
- this._client.activeAddon = null;
- }
- this._client.unregisterClient(this);
- return response;
- },
- })
-};
-
-/**
- * A RootClient object represents a root actor on the server. Each
- * DebuggerClient keeps a RootClient instance representing the root actor
- * for the initial connection; DebuggerClient's 'listTabs' and
- * 'listChildProcesses' methods forward to that root actor.
- *
- * @param client object
- * The client connection to which this actor belongs.
- * @param greeting string
- * The greeting packet from the root actor we're to represent.
- *
- * Properties of a RootClient instance:
- *
- * @property actor string
- * The name of this child's root actor.
- * @property applicationType string
- * The application type, as given in the root actor's greeting packet.
- * @property traits object
- * The traits object, as given in the root actor's greeting packet.
- */
-function RootClient(client, greeting) {
- this._client = client;
- this.actor = greeting.from;
- this.applicationType = greeting.applicationType;
- this.traits = greeting.traits;
-}
-exports.RootClient = RootClient;
-
-RootClient.prototype = {
- constructor: RootClient,
-
- /**
- * Gets the "root" form, which lists all the global actors that affect the entire
- * browser. This can replace usages of `listTabs` that only wanted the global actors
- * and didn't actually care about tabs.
- */
- getRoot: DebuggerClient.requester({ type: "getRoot" }),
-
- /**
- * List the open tabs.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- listTabs: DebuggerClient.requester({ type: "listTabs" }),
-
- /**
- * List the installed addons.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- listAddons: DebuggerClient.requester({ type: "listAddons" }),
-
- /**
- * List the registered workers.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- listWorkers: DebuggerClient.requester({ type: "listWorkers" }),
-
- /**
- * List the registered service workers.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- listServiceWorkerRegistrations: DebuggerClient.requester({
- type: "listServiceWorkerRegistrations"
- }),
-
- /**
- * List the running processes.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- listProcesses: DebuggerClient.requester({ type: "listProcesses" }),
-
- /**
- * Fetch the TabActor for the currently selected tab, or for a specific
- * tab given as first parameter.
- *
- * @param [optional] object filter
- * A dictionary object with following optional attributes:
- * - outerWindowID: used to match tabs in parent process
- * - tabId: used to match tabs in child processes
- * - tab: a reference to xul:tab element
- * If nothing is specified, returns the actor for the currently
- * selected tab.
- */
- getTab: function (filter) {
- let packet = {
- to: this.actor,
- type: "getTab"
- };
-
- if (filter) {
- if (typeof (filter.outerWindowID) == "number") {
- packet.outerWindowID = filter.outerWindowID;
- } else if (typeof (filter.tabId) == "number") {
- packet.tabId = filter.tabId;
- } else if ("tab" in filter) {
- let browser = filter.tab.linkedBrowser;
- if (browser.frameLoader.tabParent) {
- // Tabs in child process
- packet.tabId = browser.frameLoader.tabParent.tabId;
- } else if (browser.outerWindowID) {
- // <xul:browser> tabs in parent process
- packet.outerWindowID = browser.outerWindowID;
- } else {
- // <iframe mozbrowser> tabs in parent process
- let windowUtils = browser.contentWindow
- .QueryInterface(Ci.nsIInterfaceRequestor)
- .getInterface(Ci.nsIDOMWindowUtils);
- packet.outerWindowID = windowUtils.outerWindowID;
- }
- } else {
- // Throw if a filter object have been passed but without
- // any clearly idenfified filter.
- throw new Error("Unsupported argument given to getTab request");
- }
- }
-
- return this.request(packet);
- },
-
- /**
- * Fetch the WindowActor for a specific window, like a browser window in
- * Firefox, but it can be used to reach any window in the process.
- *
- * @param number outerWindowID
- * The outerWindowID of the top level window you are looking for.
- */
- getWindow: function ({ outerWindowID }) {
- if (!outerWindowID) {
- throw new Error("Must specify outerWindowID");
- }
-
- let packet = {
- to: this.actor,
- type: "getWindow",
- outerWindowID,
- };
-
- return this.request(packet);
- },
-
- /**
- * Description of protocol's actors and methods.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- protocolDescription: DebuggerClient.requester({ type: "protocolDescription" }),
-
- /*
- * Methods constructed by DebuggerClient.requester require these forwards
- * on their 'this'.
- */
- get _transport() {
- return this._client._transport;
- },
- get request() {
- return this._client.request;
- }
-};
-
-/**
- * Creates a thread client for the remote debugging protocol server. This client
- * is a front to the thread actor created in the server side, hiding the
- * protocol details in a traditional JavaScript API.
- *
- * @param client DebuggerClient|TabClient
- * The parent of the thread (tab for tab-scoped debuggers, DebuggerClient
- * for chrome debuggers).
- * @param actor string
- * The actor ID for this thread.
- */
-function ThreadClient(client, actor) {
- this._parent = client;
- this.client = client instanceof DebuggerClient ? client : client.client;
- this._actor = actor;
- this._frameCache = [];
- this._scriptCache = {};
- this._pauseGrips = {};
- this._threadGrips = {};
- this.request = this.client.request;
-}
-
-ThreadClient.prototype = {
- _state: "paused",
- get state() {
- return this._state;
- },
- get paused() {
- return this._state === "paused";
- },
-
- _pauseOnExceptions: false,
- _ignoreCaughtExceptions: false,
- _pauseOnDOMEvents: null,
-
- _actor: null,
- get actor() {
- return this._actor;
- },
-
- get _transport() {
- return this.client._transport;
- },
-
- _assertPaused: function (command) {
- if (!this.paused) {
- throw Error(command + " command sent while not paused. Currently " + this._state);
- }
- },
-
- /**
- * Resume a paused thread. If the optional limit parameter is present, then
- * the thread will also pause when that limit is reached.
- *
- * @param [optional] object limit
- * An object with a type property set to the appropriate limit (next,
- * step, or finish) per the remote debugging protocol specification.
- * Use null to specify no limit.
- * @param function onResponse
- * Called with the response packet.
- */
- _doResume: DebuggerClient.requester({
- type: "resume",
- resumeLimit: arg(0)
- }, {
- before: function (packet) {
- this._assertPaused("resume");
-
- // Put the client in a tentative "resuming" state so we can prevent
- // further requests that should only be sent in the paused state.
- this._previousState = this._state;
- this._state = "resuming";
-
- if (this._pauseOnExceptions) {
- packet.pauseOnExceptions = this._pauseOnExceptions;
- }
- if (this._ignoreCaughtExceptions) {
- packet.ignoreCaughtExceptions = this._ignoreCaughtExceptions;
- }
- if (this._pauseOnDOMEvents) {
- packet.pauseOnDOMEvents = this._pauseOnDOMEvents;
- }
- return packet;
- },
- after: function (response) {
- if (response.error && this._state == "resuming") {
- // There was an error resuming, update the state to the new one
- // reported by the server, if given (only on wrongState), otherwise
- // reset back to the previous state.
- if (response.state) {
- this._state = ThreadStateTypes[response.state];
- } else {
- this._state = this._previousState;
- }
- }
- delete this._previousState;
- return response;
- },
- }),
-
- /**
- * Reconfigure the thread actor.
- *
- * @param object options
- * A dictionary object of the new options to use in the thread actor.
- * @param function onResponse
- * Called with the response packet.
- */
- reconfigure: DebuggerClient.requester({
- type: "reconfigure",
- options: arg(0)
- }),
-
- /**
- * Resume a paused thread.
- */
- resume: function (onResponse) {
- return this._doResume(null, onResponse);
- },
-
- /**
- * Resume then pause without stepping.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- resumeThenPause: function (onResponse) {
- return this._doResume({ type: "break" }, onResponse);
- },
-
- /**
- * Step over a function call.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- stepOver: function (onResponse) {
- return this._doResume({ type: "next" }, onResponse);
- },
-
- /**
- * Step into a function call.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- stepIn: function (onResponse) {
- return this._doResume({ type: "step" }, onResponse);
- },
-
- /**
- * Step out of a function call.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- stepOut: function (onResponse) {
- return this._doResume({ type: "finish" }, onResponse);
- },
-
- /**
- * Immediately interrupt a running thread.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- interrupt: function (onResponse) {
- return this._doInterrupt(null, onResponse);
- },
-
- /**
- * Pause execution right before the next JavaScript bytecode is executed.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- breakOnNext: function (onResponse) {
- return this._doInterrupt("onNext", onResponse);
- },
-
- /**
- * Interrupt a running thread.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- _doInterrupt: DebuggerClient.requester({
- type: "interrupt",
- when: arg(0)
- }),
-
- /**
- * Enable or disable pausing when an exception is thrown.
- *
- * @param boolean pauseOnExceptions
- * Enables pausing if true, disables otherwise.
- * @param boolean ignoreCaughtExceptions
- * Whether to ignore caught exceptions
- * @param function onResponse
- * Called with the response packet.
- */
- pauseOnExceptions: function (pauseOnExceptions,
- ignoreCaughtExceptions,
- onResponse = noop) {
- this._pauseOnExceptions = pauseOnExceptions;
- this._ignoreCaughtExceptions = ignoreCaughtExceptions;
-
- // Otherwise send the flag using a standard resume request.
- if (!this.paused) {
- return this.interrupt(response => {
- if (response.error) {
- // Can't continue if pausing failed.
- onResponse(response);
- return response;
- }
- return this.resume(onResponse);
- });
- }
-
- onResponse();
- return promise.resolve();
- },
-
- /**
- * Enable pausing when the specified DOM events are triggered. Disabling
- * pausing on an event can be realized by calling this method with the updated
- * array of events that doesn't contain it.
- *
- * @param array|string events
- * An array of strings, representing the DOM event types to pause on,
- * or "*" to pause on all DOM events. Pass an empty array to
- * completely disable pausing on DOM events.
- * @param function onResponse
- * Called with the response packet in a future turn of the event loop.
- */
- pauseOnDOMEvents: function (events, onResponse = noop) {
- this._pauseOnDOMEvents = events;
- // If the debuggee is paused, the value of the array will be communicated in
- // the next resumption. Otherwise we have to force a pause in order to send
- // the array.
- if (this.paused) {
- DevToolsUtils.executeSoon(() => onResponse({}));
- return {};
- }
- return this.interrupt(response => {
- // Can't continue if pausing failed.
- if (response.error) {
- onResponse(response);
- return response;
- }
- return this.resume(onResponse);
- });
- },
-
- /**
- * Send a clientEvaluate packet to the debuggee. Response
- * will be a resume packet.
- *
- * @param string frame
- * The actor ID of the frame where the evaluation should take place.
- * @param string expression
- * The expression that will be evaluated in the scope of the frame
- * above.
- * @param function onResponse
- * Called with the response packet.
- */
- eval: DebuggerClient.requester({
- type: "clientEvaluate",
- frame: arg(0),
- expression: arg(1)
- }, {
- before: function (packet) {
- this._assertPaused("eval");
- // Put the client in a tentative "resuming" state so we can prevent
- // further requests that should only be sent in the paused state.
- this._state = "resuming";
- return packet;
- },
- after: function (response) {
- if (response.error) {
- // There was an error resuming, back to paused state.
- this._state = "paused";
- }
- return response;
- },
- }),
-
- /**
- * Detach from the thread actor.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- detach: DebuggerClient.requester({
- type: "detach"
- }, {
- after: function (response) {
- this.client.unregisterClient(this);
- this._parent.thread = null;
- return response;
- },
- }),
-
- /**
- * Release multiple thread-lifetime object actors. If any pause-lifetime
- * actors are included in the request, a |notReleasable| error will return,
- * but all the thread-lifetime ones will have been released.
- *
- * @param array actors
- * An array with actor IDs to release.
- */
- releaseMany: DebuggerClient.requester({
- type: "releaseMany",
- actors: arg(0),
- }),
-
- /**
- * Promote multiple pause-lifetime object actors to thread-lifetime ones.
- *
- * @param array actors
- * An array with actor IDs to promote.
- */
- threadGrips: DebuggerClient.requester({
- type: "threadGrips",
- actors: arg(0)
- }),
-
- /**
- * Return the event listeners defined on the page.
- *
- * @param onResponse Function
- * Called with the thread's response.
- */
- eventListeners: DebuggerClient.requester({
- type: "eventListeners"
- }),
-
- /**
- * Request the loaded sources for the current thread.
- *
- * @param onResponse Function
- * Called with the thread's response.
- */
- getSources: DebuggerClient.requester({
- type: "sources"
- }),
-
- /**
- * Clear the thread's source script cache. A scriptscleared event
- * will be sent.
- */
- _clearScripts: function () {
- if (Object.keys(this._scriptCache).length > 0) {
- this._scriptCache = {};
- this.emit("scriptscleared");
- }
- },
-
- /**
- * Request frames from the callstack for the current thread.
- *
- * @param start integer
- * The number of the youngest stack frame to return (the youngest
- * frame is 0).
- * @param count integer
- * The maximum number of frames to return, or null to return all
- * frames.
- * @param onResponse function
- * Called with the thread's response.
- */
- getFrames: DebuggerClient.requester({
- type: "frames",
- start: arg(0),
- count: arg(1)
- }),
-
- /**
- * An array of cached frames. Clients can observe the framesadded and
- * framescleared event to keep up to date on changes to this cache,
- * and can fill it using the fillFrames method.
- */
- get cachedFrames() {
- return this._frameCache;
- },
-
- /**
- * true if there are more stack frames available on the server.
- */
- get moreFrames() {
- return this.paused && (!this._frameCache || this._frameCache.length == 0
- || !this._frameCache[this._frameCache.length - 1].oldest);
- },
-
- /**
- * Request the frame environment.
- *
- * @param frameId string
- */
- getEnvironment: function (frameId) {
- return this.request({ to: frameId, type: "getEnvironment" });
- },
-
- /**
- * Ensure that at least total stack frames have been loaded in the
- * ThreadClient's stack frame cache. A framesadded event will be
- * sent when the stack frame cache is updated.
- *
- * @param total number
- * The minimum number of stack frames to be included.
- * @param callback function
- * Optional callback function called when frames have been loaded
- * @returns true if a framesadded notification should be expected.
- */
- fillFrames: function (total, callback = noop) {
- this._assertPaused("fillFrames");
- if (this._frameCache.length >= total) {
- return false;
- }
-
- let numFrames = this._frameCache.length;
-
- this.getFrames(numFrames, total - numFrames, (response) => {
- if (response.error) {
- callback(response);
- return;
- }
-
- let threadGrips = DevToolsUtils.values(this._threadGrips);
-
- for (let i in response.frames) {
- let frame = response.frames[i];
- if (!frame.where.source) {
- // Older servers use urls instead, so we need to resolve
- // them to source actors
- for (let grip of threadGrips) {
- if (grip instanceof SourceClient && grip.url === frame.url) {
- frame.where.source = grip._form;
- }
- }
- }
-
- this._frameCache[frame.depth] = frame;
- }
-
- // If we got as many frames as we asked for, there might be more
- // frames available.
- this.emit("framesadded");
-
- callback(response);
- });
-
- return true;
- },
-
- /**
- * Clear the thread's stack frame cache. A framescleared event
- * will be sent.
- */
- _clearFrames: function () {
- if (this._frameCache.length > 0) {
- this._frameCache = [];
- this.emit("framescleared");
- }
- },
-
- /**
- * Return a ObjectClient object for the given object grip.
- *
- * @param grip object
- * A pause-lifetime object grip returned by the protocol.
- */
- pauseGrip: function (grip) {
- if (grip.actor in this._pauseGrips) {
- return this._pauseGrips[grip.actor];
- }
-
- let client = new ObjectClient(this.client, grip);
- this._pauseGrips[grip.actor] = client;
- return client;
- },
-
- /**
- * Get or create a long string client, checking the grip client cache if it
- * already exists.
- *
- * @param grip Object
- * The long string grip returned by the protocol.
- * @param gripCacheName String
- * The property name of the grip client cache to check for existing
- * clients in.
- */
- _longString: function (grip, gripCacheName) {
- if (grip.actor in this[gripCacheName]) {
- return this[gripCacheName][grip.actor];
- }
-
- let client = new LongStringClient(this.client, grip);
- this[gripCacheName][grip.actor] = client;
- return client;
- },
-
- /**
- * Return an instance of LongStringClient for the given long string grip that
- * is scoped to the current pause.
- *
- * @param grip Object
- * The long string grip returned by the protocol.
- */
- pauseLongString: function (grip) {
- return this._longString(grip, "_pauseGrips");
- },
-
- /**
- * Return an instance of LongStringClient for the given long string grip that
- * is scoped to the thread lifetime.
- *
- * @param grip Object
- * The long string grip returned by the protocol.
- */
- threadLongString: function (grip) {
- return this._longString(grip, "_threadGrips");
- },
-
- /**
- * Get or create an ArrayBuffer client, checking the grip client cache if it
- * already exists.
- *
- * @param grip Object
- * The ArrayBuffer grip returned by the protocol.
- * @param gripCacheName String
- * The property name of the grip client cache to check for existing
- * clients in.
- */
- _arrayBuffer: function (grip, gripCacheName) {
- if (grip.actor in this[gripCacheName]) {
- return this[gripCacheName][grip.actor];
- }
-
- let client = new ArrayBufferClient(this.client, grip);
- this[gripCacheName][grip.actor] = client;
- return client;
- },
-
- /**
- * Return an instance of ArrayBufferClient for the given ArrayBuffer grip that
- * is scoped to the thread lifetime.
- *
- * @param grip Object
- * The ArrayBuffer grip returned by the protocol.
- */
- threadArrayBuffer: function (grip) {
- return this._arrayBuffer(grip, "_threadGrips");
- },
-
- /**
- * Clear and invalidate all the grip clients from the given cache.
- *
- * @param gripCacheName
- * The property name of the grip cache we want to clear.
- */
- _clearObjectClients: function (gripCacheName) {
- for (let id in this[gripCacheName]) {
- this[gripCacheName][id].valid = false;
- }
- this[gripCacheName] = {};
- },
-
- /**
- * Invalidate pause-lifetime grip clients and clear the list of current grip
- * clients.
- */
- _clearPauseGrips: function () {
- this._clearObjectClients("_pauseGrips");
- },
-
- /**
- * Invalidate thread-lifetime grip clients and clear the list of current grip
- * clients.
- */
- _clearThreadGrips: function () {
- this._clearObjectClients("_threadGrips");
- },
-
- /**
- * Handle thread state change by doing necessary cleanup and notifying all
- * registered listeners.
- */
- _onThreadState: function (packet) {
- this._state = ThreadStateTypes[packet.type];
- // The debugger UI may not be initialized yet so we want to keep
- // the packet around so it knows what to pause state to display
- // when it's initialized
- this._lastPausePacket = packet.type === "resumed" ? null : packet;
- this._clearFrames();
- this._clearPauseGrips();
- packet.type === ThreadStateTypes.detached && this._clearThreadGrips();
- this.client._eventsEnabled && this.emit(packet.type, packet);
- },
-
- getLastPausePacket: function () {
- return this._lastPausePacket;
- },
-
- /**
- * Return an EnvironmentClient instance for the given environment actor form.
- */
- environment: function (form) {
- return new EnvironmentClient(this.client, form);
- },
-
- /**
- * Return an instance of SourceClient for the given source actor form.
- */
- source: function (form) {
- if (form.actor in this._threadGrips) {
- return this._threadGrips[form.actor];
- }
-
- this._threadGrips[form.actor] = new SourceClient(this, form);
- return this._threadGrips[form.actor];
- },
-
- /**
- * Request the prototype and own properties of mutlipleObjects.
- *
- * @param onResponse function
- * Called with the request's response.
- * @param actors [string]
- * List of actor ID of the queried objects.
- */
- getPrototypesAndProperties: DebuggerClient.requester({
- type: "prototypesAndProperties",
- actors: arg(0)
- }),
-
- events: ["newSource"]
-};
-
-eventSource(ThreadClient.prototype);
-
-/**
- * Creates a tracing profiler client for the remote debugging protocol
- * server. This client is a front to the trace actor created on the
- * server side, hiding the protocol details in a traditional
- * JavaScript API.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param actor string
- * The actor ID for this thread.
- */
-function TraceClient(client, actor) {
- this._client = client;
- this._actor = actor;
- this._activeTraces = new Set();
- this._waitingPackets = new Map();
- this._expectedPacket = 0;
- this.request = this._client.request;
- this.events = [];
-}
-
-TraceClient.prototype = {
- get actor() {
- return this._actor;
- },
- get tracing() {
- return this._activeTraces.size > 0;
- },
-
- get _transport() {
- return this._client._transport;
- },
-
- /**
- * Detach from the trace actor.
- */
- detach: DebuggerClient.requester({
- type: "detach"
- }, {
- after: function (response) {
- this._client.unregisterClient(this);
- return response;
- },
- }),
-
- /**
- * Start a new trace.
- *
- * @param trace [string]
- * An array of trace types to be recorded by the new trace.
- *
- * @param name string
- * The name of the new trace.
- *
- * @param onResponse function
- * Called with the request's response.
- */
- startTrace: DebuggerClient.requester({
- type: "startTrace",
- name: arg(1),
- trace: arg(0)
- }, {
- after: function (response) {
- if (response.error) {
- return response;
- }
-
- if (!this.tracing) {
- this._waitingPackets.clear();
- this._expectedPacket = 0;
- }
- this._activeTraces.add(response.name);
-
- return response;
- },
- }),
-
- /**
- * End a trace. If a name is provided, stop the named
- * trace. Otherwise, stop the most recently started trace.
- *
- * @param name string
- * The name of the trace to stop.
- *
- * @param onResponse function
- * Called with the request's response.
- */
- stopTrace: DebuggerClient.requester({
- type: "stopTrace",
- name: arg(0)
- }, {
- after: function (response) {
- if (response.error) {
- return response;
- }
-
- this._activeTraces.delete(response.name);
-
- return response;
- },
- })
-};
-
-/**
- * Grip clients are used to retrieve information about the relevant object.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param grip object
- * A pause-lifetime object grip returned by the protocol.
- */
-function ObjectClient(client, grip) {
- this._grip = grip;
- this._client = client;
- this.request = this._client.request;
-}
-exports.ObjectClient = ObjectClient;
-
-ObjectClient.prototype = {
- get actor() {
- return this._grip.actor;
- },
- get _transport() {
- return this._client._transport;
- },
-
- valid: true,
-
- get isFrozen() {
- return this._grip.frozen;
- },
- get isSealed() {
- return this._grip.sealed;
- },
- get isExtensible() {
- return this._grip.extensible;
- },
-
- getDefinitionSite: DebuggerClient.requester({
- type: "definitionSite"
- }, {
- before: function (packet) {
- if (this._grip.class != "Function") {
- throw new Error("getDefinitionSite is only valid for function grips.");
- }
- return packet;
- }
- }),
-
- /**
- * Request the names of a function's formal parameters.
- *
- * @param onResponse function
- * Called with an object of the form:
- * { parameterNames:[<parameterName>, ...] }
- * where each <parameterName> is the name of a parameter.
- */
- getParameterNames: DebuggerClient.requester({
- type: "parameterNames"
- }, {
- before: function (packet) {
- if (this._grip.class !== "Function") {
- throw new Error("getParameterNames is only valid for function grips.");
- }
- return packet;
- },
- }),
-
- /**
- * Request the names of the properties defined on the object and not its
- * prototype.
- *
- * @param onResponse function Called with the request's response.
- */
- getOwnPropertyNames: DebuggerClient.requester({
- type: "ownPropertyNames"
- }),
-
- /**
- * Request the prototype and own properties of the object.
- *
- * @param onResponse function Called with the request's response.
- */
- getPrototypeAndProperties: DebuggerClient.requester({
- type: "prototypeAndProperties"
- }),
-
- /**
- * Request a PropertyIteratorClient instance to ease listing
- * properties for this object.
- *
- * @param options Object
- * A dictionary object with various boolean attributes:
- * - ignoreIndexedProperties Boolean
- * If true, filters out Array items.
- * e.g. properties names between `0` and `object.length`.
- * - ignoreNonIndexedProperties Boolean
- * If true, filters out items that aren't array items
- * e.g. properties names that are not a number between `0`
- * and `object.length`.
- * - sort Boolean
- * If true, the iterator will sort the properties by name
- * before dispatching them.
- * @param onResponse function Called with the client instance.
- */
- enumProperties: DebuggerClient.requester({
- type: "enumProperties",
- options: arg(0)
- }, {
- after: function (response) {
- if (response.iterator) {
- return { iterator: new PropertyIteratorClient(this._client, response.iterator) };
- }
- return response;
- },
- }),
-
- /**
- * Request a PropertyIteratorClient instance to enumerate entries in a
- * Map/Set-like object.
- *
- * @param onResponse function Called with the request's response.
- */
- enumEntries: DebuggerClient.requester({
- type: "enumEntries"
- }, {
- before: function (packet) {
- if (!["Map", "WeakMap", "Set", "WeakSet"].includes(this._grip.class)) {
- throw new Error("enumEntries is only valid for Map/Set-like grips.");
- }
- return packet;
- },
- after: function (response) {
- if (response.iterator) {
- return {
- iterator: new PropertyIteratorClient(this._client, response.iterator)
- };
- }
- return response;
- }
- }),
-
- /**
- * Request a SymbolIteratorClient instance to enumerate symbols in an object.
- *
- * @param onResponse function Called with the request's response.
- */
- enumSymbols: DebuggerClient.requester({
- type: "enumSymbols"
- }, {
- before: function (packet) {
- if (this._grip.type !== "object") {
- throw new Error("enumSymbols is only valid for objects grips.");
- }
- return packet;
- },
- after: function (response) {
- if (response.iterator) {
- return {
- iterator: new SymbolIteratorClient(this._client, response.iterator)
- };
- }
- return response;
- }
- }),
-
- /**
- * Request the property descriptor of the object's specified property.
- *
- * @param name string The name of the requested property.
- * @param onResponse function Called with the request's response.
- */
- getProperty: DebuggerClient.requester({
- type: "property",
- name: arg(0)
- }),
-
- /**
- * Request the prototype of the object.
- *
- * @param onResponse function Called with the request's response.
- */
- getPrototype: DebuggerClient.requester({
- type: "prototype"
- }),
-
- /**
- * Request the display string of the object.
- *
- * @param onResponse function Called with the request's response.
- */
- getDisplayString: DebuggerClient.requester({
- type: "displayString"
- }),
-
- /**
- * Request the scope of the object.
- *
- * @param onResponse function Called with the request's response.
- */
- getScope: DebuggerClient.requester({
- type: "scope"
- }, {
- before: function (packet) {
- if (this._grip.class !== "Function") {
- throw new Error("scope is only valid for function grips.");
- }
- return packet;
- },
- }),
-
- /**
- * Request the promises directly depending on the current promise.
- */
- getDependentPromises: DebuggerClient.requester({
- type: "dependentPromises"
- }, {
- before: function (packet) {
- if (this._grip.class !== "Promise") {
- throw new Error("getDependentPromises is only valid for promise " +
- "grips.");
- }
- return packet;
- }
- }),
-
- /**
- * Request the stack to the promise's allocation point.
- */
- getPromiseAllocationStack: DebuggerClient.requester({
- type: "allocationStack"
- }, {
- before: function (packet) {
- if (this._grip.class !== "Promise") {
- throw new Error("getAllocationStack is only valid for promise grips.");
- }
- return packet;
- }
- }),
-
- /**
- * Request the stack to the promise's fulfillment point.
- */
- getPromiseFulfillmentStack: DebuggerClient.requester({
- type: "fulfillmentStack"
- }, {
- before: function (packet) {
- if (this._grip.class !== "Promise") {
- throw new Error("getPromiseFulfillmentStack is only valid for " +
- "promise grips.");
- }
- return packet;
- }
- }),
-
- /**
- * Request the stack to the promise's rejection point.
- */
- getPromiseRejectionStack: DebuggerClient.requester({
- type: "rejectionStack"
- }, {
- before: function (packet) {
- if (this._grip.class !== "Promise") {
- throw new Error("getPromiseRejectionStack is only valid for " +
- "promise grips.");
- }
- return packet;
- }
- })
-};
-
-/**
- * A PropertyIteratorClient provides a way to access to property names and
- * values of an object efficiently, slice by slice.
- * Note that the properties can be sorted in the backend,
- * this is controled while creating the PropertyIteratorClient
- * from ObjectClient.enumProperties.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param grip Object
- * A PropertyIteratorActor grip returned by the protocol via
- * TabActor.enumProperties request.
- */
-function PropertyIteratorClient(client, grip) {
- this._grip = grip;
- this._client = client;
- this.request = this._client.request;
-}
-
-PropertyIteratorClient.prototype = {
- get actor() {
- return this._grip.actor;
- },
-
- /**
- * Get the total number of properties available in the iterator.
- */
- get count() {
- return this._grip.count;
- },
-
- /**
- * Get one or more property names that correspond to the positions in the
- * indexes parameter.
- *
- * @param indexes Array
- * An array of property indexes.
- * @param callback Function
- * The function called when we receive the property names.
- */
- names: DebuggerClient.requester({
- type: "names",
- indexes: arg(0)
- }, {}),
-
- /**
- * Get a set of following property value(s).
- *
- * @param start Number
- * The index of the first property to fetch.
- * @param count Number
- * The number of properties to fetch.
- * @param callback Function
- * The function called when we receive the property values.
- */
- slice: DebuggerClient.requester({
- type: "slice",
- start: arg(0),
- count: arg(1)
- }, {}),
-
- /**
- * Get all the property values.
- *
- * @param callback Function
- * The function called when we receive the property values.
- */
- all: DebuggerClient.requester({
- type: "all"
- }, {}),
-};
-
-/**
- * A SymbolIteratorClient provides a way to access to symbols
- * of an object efficiently, slice by slice.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param grip Object
- * A SymbolIteratorActor grip returned by the protocol via
- * TabActor.enumSymbols request.
- */
-function SymbolIteratorClient(client, grip) {
- this._grip = grip;
- this._client = client;
- this.request = this._client.request;
-}
-
-SymbolIteratorClient.prototype = {
- get actor() {
- return this._grip.actor;
- },
-
- /**
- * Get the total number of symbols available in the iterator.
- */
- get count() {
- return this._grip.count;
- },
-
- /**
- * Get a set of following symbols.
- *
- * @param start Number
- * The index of the first symbol to fetch.
- * @param count Number
- * The number of symbols to fetch.
- * @param callback Function
- * The function called when we receive the symbols.
- */
- slice: DebuggerClient.requester({
- type: "slice",
- start: arg(0),
- count: arg(1)
- }, {}),
-
- /**
- * Get all the symbols.
- *
- * @param callback Function
- * The function called when we receive the symbols.
- */
- all: DebuggerClient.requester({
- type: "all"
- }, {}),
-};
-
-/**
- * A ArrayBufferClient provides a way to access ArrayBuffer from the
- * debugger server.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param grip Object
- * A pause-lifetime ArrayBuffer grip returned by the protocol.
- */
-function ArrayBufferClient(client, grip) {
- this._grip = grip;
- this._client = client;
- this.request = this._client.request;
-}
-ArrayBufferClient.prototype = {
- get actor() {
- return this._grip.actor;
- },
- get length() {
- return this._grip.length;
- },
- get _transport() {
- return this._client._transport;
- },
-
- valid: true,
-
- slice: DebuggerClient.requester({
- type: "slice",
- start: arg(0),
- count: arg(1)
- }),
-};
-
-/**
- * A LongStringClient provides a way to access "very long" strings from the
- * debugger server.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param grip Object
- * A pause-lifetime long string grip returned by the protocol.
- */
-function LongStringClient(client, grip) {
- this._grip = grip;
- this._client = client;
- this.request = this._client.request;
-}
-exports.LongStringClient = LongStringClient;
-
-LongStringClient.prototype = {
- get actor() {
- return this._grip.actor;
- },
- get length() {
- return this._grip.length;
- },
- get initial() {
- return this._grip.initial;
- },
- get _transport() {
- return this._client._transport;
- },
-
- valid: true,
-
- /**
- * Get the substring of this LongString from start to end.
- *
- * @param start Number
- * The starting index.
- * @param end Number
- * The ending index.
- * @param callback Function
- * The function called when we receive the substring.
- */
- substring: DebuggerClient.requester({
- type: "substring",
- start: arg(0),
- end: arg(1)
- }),
-};
-
-/**
- * A SourceClient provides a way to access the source text of a script.
- *
- * @param client ThreadClient
- * The thread client parent.
- * @param form Object
- * The form sent across the remote debugging protocol.
- */
-function SourceClient(client, form) {
- this._form = form;
- this._isBlackBoxed = form.isBlackBoxed;
- this._isPrettyPrinted = form.isPrettyPrinted;
- this._activeThread = client;
- this._client = client.client;
-}
-
-SourceClient.prototype = {
- get _transport() {
- return this._client._transport;
- },
- get isBlackBoxed() {
- return this._isBlackBoxed;
- },
- get isPrettyPrinted() {
- return this._isPrettyPrinted;
- },
- get actor() {
- return this._form.actor;
- },
- get request() {
- return this._client.request;
- },
- get url() {
- return this._form.url;
- },
-
- /**
- * Black box this SourceClient's source.
- *
- * @param callback Function
- * The callback function called when we receive the response from the server.
- */
- blackBox: DebuggerClient.requester({
- type: "blackbox"
- }, {
- after: function (response) {
- if (!response.error) {
- this._isBlackBoxed = true;
- if (this._activeThread) {
- this._activeThread.emit("blackboxchange", this);
- }
- }
- return response;
- }
- }),
-
- /**
- * Un-black box this SourceClient's source.
- *
- * @param callback Function
- * The callback function called when we receive the response from the server.
- */
- unblackBox: DebuggerClient.requester({
- type: "unblackbox"
- }, {
- after: function (response) {
- if (!response.error) {
- this._isBlackBoxed = false;
- if (this._activeThread) {
- this._activeThread.emit("blackboxchange", this);
- }
- }
- return response;
- }
- }),
-
- /**
- * Get Executable Lines from a source
- *
- * @param callback Function
- * The callback function called when we receive the response from the server.
- */
- getExecutableLines: function (cb = noop) {
- let packet = {
- to: this._form.actor,
- type: "getExecutableLines"
- };
-
- return this._client.request(packet).then(res => {
- cb(res.lines);
- return res.lines;
- });
- },
-
- /**
- * Get a long string grip for this SourceClient's source.
- */
- source: function (callback = noop) {
- let packet = {
- to: this._form.actor,
- type: "source"
- };
- return this._client.request(packet).then(response => {
- return this._onSourceResponse(response, callback);
- });
- },
-
- /**
- * Pretty print this source's text.
- */
- prettyPrint: function (indent, callback = noop) {
- const packet = {
- to: this._form.actor,
- type: "prettyPrint",
- indent
- };
- return this._client.request(packet).then(response => {
- if (!response.error) {
- this._isPrettyPrinted = true;
- this._activeThread._clearFrames();
- this._activeThread.emit("prettyprintchange", this);
- }
- return this._onSourceResponse(response, callback);
- });
- },
-
- /**
- * Stop pretty printing this source's text.
- */
- disablePrettyPrint: function (callback = noop) {
- const packet = {
- to: this._form.actor,
- type: "disablePrettyPrint"
- };
- return this._client.request(packet).then(response => {
- if (!response.error) {
- this._isPrettyPrinted = false;
- this._activeThread._clearFrames();
- this._activeThread.emit("prettyprintchange", this);
- }
- return this._onSourceResponse(response, callback);
- });
- },
-
- _onSourceResponse: function (response, callback) {
- if (response.error) {
- callback(response);
- return response;
- }
-
- if (typeof response.source === "string") {
- callback(response);
- return response;
- }
-
- let { contentType, source } = response;
- if (source.type === "arrayBuffer") {
- let arrayBuffer = this._activeThread.threadArrayBuffer(source);
- return arrayBuffer.slice(0, arrayBuffer.length).then(function (resp) {
- if (resp.error) {
- callback(resp);
- return resp;
- }
- // Keeping str as a string, ArrayBuffer/Uint8Array will not survive
- // setIn/mergeIn operations.
- const str = atob(resp.encoded);
- let newResponse = {
- source: {
- binary: str,
- toString: () => "[wasm]",
- },
- contentType,
- };
- callback(newResponse);
- return newResponse;
- });
- }
-
- let longString = this._activeThread.threadLongString(source);
- return longString.substring(0, longString.length).then(function (resp) {
- if (resp.error) {
- callback(resp);
- return resp;
- }
-
- let newResponse = {
- source: resp.substring,
- contentType: contentType
- };
- callback(newResponse);
- return newResponse;
- });
- },
-
- /**
- * Request to set a breakpoint in the specified location.
- *
- * @param object location
- * The location and condition of the breakpoint in
- * the form of { line[, column, condition] }.
- * @param function onResponse
- * Called with the thread's response.
- */
- setBreakpoint: function ({ line, column, condition, noSliding }, onResponse = noop) {
- // A helper function that sets the breakpoint.
- let doSetBreakpoint = callback => {
- let root = this._client.mainRoot;
- let location = {
- line,
- column,
- };
-
- let packet = {
- to: this.actor,
- type: "setBreakpoint",
- location,
- condition,
- noSliding,
- };
-
- // Backwards compatibility: send the breakpoint request to the
- // thread if the server doesn't support Debugger.Source actors.
- if (!root.traits.debuggerSourceActors) {
- packet.to = this._activeThread.actor;
- packet.location.url = this.url;
- }
-
- return this._client.request(packet).then(response => {
- // Ignoring errors, since the user may be setting a breakpoint in a
- // dead script that will reappear on a page reload.
- let bpClient;
- if (response.actor) {
- bpClient = new BreakpointClient(
- this._client,
- this,
- response.actor,
- location,
- root.traits.conditionalBreakpoints ? condition : undefined
- );
- }
- onResponse(response, bpClient);
- if (callback) {
- callback();
- }
- return [response, bpClient];
- });
- };
-
- // If the debuggee is paused, just set the breakpoint.
- if (this._activeThread.paused) {
- return doSetBreakpoint();
- }
- // Otherwise, force a pause in order to set the breakpoint.
- return this._activeThread.interrupt().then(response => {
- if (response.error) {
- // Can't set the breakpoint if pausing failed.
- onResponse(response);
- return response;
- }
-
- const { type, why } = response;
- const cleanUp = type == "paused" && why.type == "interrupted"
- ? () => this._activeThread.resume()
- : noop;
-
- return doSetBreakpoint(cleanUp);
- });
- }
-};
+const eventSource = require("./event-source");
+const {DebuggerClient} = require("./debugger-client");
/**
* Breakpoint clients are used to remove breakpoints that are no longer used.
*
* @param client DebuggerClient
* The debugger client parent.
* @param sourceClient SourceClient
* The source where this breakpoint exists
@@ -3376,51 +126,9 @@ BreakpointClient.prototype = {
}
return deferred.promise;
}
};
eventSource(BreakpointClient.prototype);
-/**
- * Environment clients are used to manipulate the lexical environment actors.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param form Object
- * The form sent across the remote debugging protocol.
- */
-function EnvironmentClient(client, form) {
- this._client = client;
- this._form = form;
- this.request = this._client.request;
-}
-exports.EnvironmentClient = EnvironmentClient;
-
-EnvironmentClient.prototype = {
-
- get actor() {
- return this._form.actor;
- },
- get _transport() {
- return this._client._transport;
- },
-
- /**
- * Fetches the bindings introduced by this lexical environment.
- */
- getBindings: DebuggerClient.requester({
- type: "bindings"
- }),
-
- /**
- * Changes the value of the identifier whose name is name (a string) to that
- * represented by value (a grip).
- */
- assign: DebuggerClient.requester({
- type: "assign",
- name: arg(0),
- value: arg(1)
- })
-};
-
-eventSource(EnvironmentClient.prototype);
+module.exports = BreakpointClient;
copy from devtools/shared/client/main.js
copy to devtools/shared/client/constants.js
--- a/devtools/shared/client/main.js
+++ b/devtools/shared/client/constants.js
@@ -1,146 +1,14 @@
-/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
-/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
-const { Ci, Cu } = require("chrome");
-const DevToolsUtils = require("devtools/shared/DevToolsUtils");
-const { getStack, callFunctionWithAsyncStack } = require("devtools/shared/platform/stack");
-
-const promise = Cu.import("resource://devtools/shared/deprecated-sync-thenables.js", {}).Promise;
-
-loader.lazyRequireGetter(this, "EventEmitter", "devtools/shared/event-emitter");
-loader.lazyRequireGetter(this, "WebConsoleClient", "devtools/shared/webconsole/client", true);
-loader.lazyRequireGetter(this, "DebuggerSocket", "devtools/shared/security/socket", true);
-loader.lazyRequireGetter(this, "Authentication", "devtools/shared/security/auth");
-
-const noop = () => {};
-
-/**
- * TODO: Get rid of this API in favor of EventTarget (bug 1042642)
- *
- * Add simple event notification to a prototype object. Any object that has
- * some use for event notifications or the observer pattern in general can be
- * augmented with the necessary facilities by passing its prototype to this
- * function.
- *
- * @param proto object
- * The prototype object that will be modified.
- */
-function eventSource(proto) {
- /**
- * Add a listener to the event source for a given event.
- *
- * @param name string
- * The event to listen for.
- * @param listener function
- * Called when the event is fired. If the same listener
- * is added more than once, it will be called once per
- * addListener call.
- */
- proto.addListener = function (name, listener) {
- if (typeof listener != "function") {
- throw TypeError("Listeners must be functions.");
- }
-
- if (!this._listeners) {
- this._listeners = {};
- }
-
- this._getListeners(name).push(listener);
- };
-
- /**
- * Add a listener to the event source for a given event. The
- * listener will be removed after it is called for the first time.
- *
- * @param name string
- * The event to listen for.
- * @param listener function
- * Called when the event is fired.
- */
- proto.addOneTimeListener = function (name, listener) {
- let l = (...args) => {
- this.removeListener(name, l);
- listener.apply(null, args);
- };
- this.addListener(name, l);
- };
-
- /**
- * Remove a listener from the event source previously added with
- * addListener().
- *
- * @param name string
- * The event name used during addListener to add the listener.
- * @param listener function
- * The callback to remove. If addListener was called multiple
- * times, all instances will be removed.
- */
- proto.removeListener = function (name, listener) {
- if (!this._listeners || (listener && !this._listeners[name])) {
- return;
- }
-
- if (!listener) {
- this._listeners[name] = [];
- } else {
- this._listeners[name] =
- this._listeners[name].filter(l => l != listener);
- }
- };
-
- /**
- * Returns the listeners for the specified event name. If none are defined it
- * initializes an empty list and returns that.
- *
- * @param name string
- * The event name.
- */
- proto._getListeners = function (name) {
- if (name in this._listeners) {
- return this._listeners[name];
- }
- this._listeners[name] = [];
- return this._listeners[name];
- };
-
- /**
- * Notify listeners of an event.
- *
- * @param name string
- * The event to fire.
- * @param arguments
- * All arguments will be passed along to the listeners,
- * including the name argument.
- */
- proto.emit = function () {
- if (!this._listeners) {
- return;
- }
-
- let name = arguments[0];
- let listeners = this._getListeners(name).slice(0);
-
- for (let listener of listeners) {
- try {
- listener.apply(null, arguments);
- } catch (e) {
- // Prevent a bad listener from interfering with the others.
- DevToolsUtils.reportException("notify event '" + name + "'", e);
- }
- }
- };
-}
-
/**
* Set of protocol messages that affect thread state, and the
* state the actor is in after each message.
*/
const ThreadStateTypes = {
"paused": "paused",
"resumed": "attached",
"detached": "detached",
@@ -191,3236 +59,13 @@ const UnsolicitedPauses = {
"resumeLimit": "resumeLimit",
"debuggerStatement": "debuggerStatement",
"breakpoint": "breakpoint",
"DOMEvent": "DOMEvent",
"watchpoint": "watchpoint",
"exception": "exception"
};
-/**
- * Creates a client for the remote debugging protocol server. This client
- * provides the means to communicate with the server and exchange the messages
- * required by the protocol in a traditional JavaScript API.
- */
-const DebuggerClient = exports.DebuggerClient = function (transport) {
- this._transport = transport;
- this._transport.hooks = this;
-
- // Map actor ID to client instance for each actor type.
- this._clients = new Map();
-
- this._pendingRequests = new Map();
- this._activeRequests = new Map();
- this._eventsEnabled = true;
-
- this.traits = {};
-
- this.request = this.request.bind(this);
- this.localTransport = this._transport.onOutputStreamReady === undefined;
-
- /*
- * As the first thing on the connection, expect a greeting packet from
- * the connection's root actor.
- */
- this.mainRoot = null;
- this.expectReply("root", (packet) => {
- this.mainRoot = new RootClient(this, packet);
- this.emit("connected", packet.applicationType, packet.traits);
- });
-};
-
-/**
- * A declarative helper for defining methods that send requests to the server.
- *
- * @param packetSkeleton
- * The form of the packet to send. Can specify fields to be filled from
- * the parameters by using the |arg| function.
- * @param before
- * The function to call before sending the packet. Is passed the packet,
- * and the return value is used as the new packet. The |this| context is
- * the instance of the client object we are defining a method for.
- * @param after
- * The function to call after the response is received. It is passed the
- * response, and the return value is considered the new response that
- * will be passed to the callback. The |this| context is the instance of
- * the client object we are defining a method for.
- * @return Request
- * The `Request` object that is a Promise object and resolves once
- * we receive the response. (See request method for more details)
- */
-DebuggerClient.requester = function (packetSkeleton, config = {}) {
- let { before, after } = config;
- return DevToolsUtils.makeInfallible(function (...args) {
- let outgoingPacket = {
- to: packetSkeleton.to || this.actor
- };
-
- let maxPosition = -1;
- for (let k of Object.keys(packetSkeleton)) {
- if (packetSkeleton[k] instanceof DebuggerClient.Argument) {
- let { position } = packetSkeleton[k];
- outgoingPacket[k] = packetSkeleton[k].getArgument(args);
- maxPosition = Math.max(position, maxPosition);
- } else {
- outgoingPacket[k] = packetSkeleton[k];
- }
- }
-
- if (before) {
- outgoingPacket = before.call(this, outgoingPacket);
- }
-
- return this.request(outgoingPacket, DevToolsUtils.makeInfallible((response) => {
- if (after) {
- let { from } = response;
- response = after.call(this, response);
- if (!response.from) {
- response.from = from;
- }
- }
-
- // The callback is always the last parameter.
- let thisCallback = args[maxPosition + 1];
- if (thisCallback) {
- thisCallback(response);
- }
- return response;
- }, "DebuggerClient.requester request callback"));
- }, "DebuggerClient.requester");
-};
-
-function arg(pos) {
- return new DebuggerClient.Argument(pos);
-}
-
-DebuggerClient.Argument = function (position) {
- this.position = position;
-};
-
-DebuggerClient.Argument.prototype.getArgument = function (params) {
- if (!(this.position in params)) {
- throw new Error("Bad index into params: " + this.position);
- }
- return params[this.position];
-};
-
-// Expose these to save callers the trouble of importing DebuggerSocket
-DebuggerClient.socketConnect = function (options) {
- // Defined here instead of just copying the function to allow lazy-load
- return DebuggerSocket.connect(options);
-};
-DevToolsUtils.defineLazyGetter(DebuggerClient, "Authenticators", () => {
- return Authentication.Authenticators;
-});
-DevToolsUtils.defineLazyGetter(DebuggerClient, "AuthenticationResult", () => {
- return Authentication.AuthenticationResult;
-});
-
-DebuggerClient.prototype = {
- /**
- * Connect to the server and start exchanging protocol messages.
- *
- * @param onConnected function
- * If specified, will be called when the greeting packet is
- * received from the debugging server.
- *
- * @return Promise
- * Resolves once connected with an array whose first element
- * is the application type, by default "browser", and the second
- * element is the traits object (help figure out the features
- * and behaviors of the server we connect to. See RootActor).
- */
- connect: function (onConnected) {
- let deferred = promise.defer();
- this.emit("connect");
-
- // Also emit the event on the |DebuggerClient| object (not on the instance),
- // so it's possible to track all instances.
- EventEmitter.emit(DebuggerClient, "connect", this);
-
- this.addOneTimeListener("connected", (name, applicationType, traits) => {
- this.traits = traits;
- if (onConnected) {
- onConnected(applicationType, traits);
- }
- deferred.resolve([applicationType, traits]);
- });
-
- this._transport.ready();
- return deferred.promise;
- },
-
- /**
- * Shut down communication with the debugging server.
- *
- * @param onClosed function
- * If specified, will be called when the debugging connection
- * has been closed. This parameter is deprecated - please use
- * the returned Promise.
- * @return Promise
- * Resolves after the underlying transport is closed.
- */
- close: function (onClosed) {
- let deferred = promise.defer();
- if (onClosed) {
- deferred.promise.then(onClosed);
- }
-
- // Disable detach event notifications, because event handlers will be in a
- // cleared scope by the time they run.
- this._eventsEnabled = false;
-
- let cleanup = () => {
- this._transport.close();
- this._transport = null;
- };
-
- // If the connection is already closed,
- // there is no need to detach client
- // as we won't be able to send any message.
- if (this._closed) {
- cleanup();
- deferred.resolve();
- return deferred.promise;
- }
-
- this.addOneTimeListener("closed", deferred.resolve);
-
- // Call each client's `detach` method by calling
- // lastly registered ones first to give a chance
- // to detach child clients first.
- let clients = [...this._clients.values()];
- this._clients.clear();
- const detachClients = () => {
- let client = clients.pop();
- if (!client) {
- // All clients detached.
- cleanup();
- return;
- }
- if (client.detach) {
- client.detach(detachClients);
- return;
- }
- detachClients();
- };
- detachClients();
-
- return deferred.promise;
- },
-
- /*
- * This function exists only to preserve DebuggerClient's interface;
- * new code should say 'client.mainRoot.listTabs()'.
- */
- listTabs: function (onResponse) {
- return this.mainRoot.listTabs(onResponse);
- },
-
- /*
- * This function exists only to preserve DebuggerClient's interface;
- * new code should say 'client.mainRoot.listAddons()'.
- */
- listAddons: function (onResponse) {
- return this.mainRoot.listAddons(onResponse);
- },
-
- getTab: function (filter) {
- return this.mainRoot.getTab(filter);
- },
-
- /**
- * Attach to a tab actor.
- *
- * @param string tabActor
- * The actor ID for the tab to attach.
- * @param function onResponse
- * Called with the response packet and a TabClient
- * (which will be undefined on error).
- */
- attachTab: function (tabActor, onResponse = noop) {
- if (this._clients.has(tabActor)) {
- let cachedTab = this._clients.get(tabActor);
- let cachedResponse = {
- cacheDisabled: cachedTab.cacheDisabled,
- javascriptEnabled: cachedTab.javascriptEnabled,
- traits: cachedTab.traits,
- };
- DevToolsUtils.executeSoon(() => onResponse(cachedResponse, cachedTab));
- return promise.resolve([cachedResponse, cachedTab]);
- }
-
- let packet = {
- to: tabActor,
- type: "attach"
- };
- return this.request(packet).then(response => {
- let tabClient;
- if (!response.error) {
- tabClient = new TabClient(this, response);
- this.registerClient(tabClient);
- }
- onResponse(response, tabClient);
- return [response, tabClient];
- });
- },
-
- attachWorker: function (workerActor, onResponse = noop) {
- let workerClient = this._clients.get(workerActor);
- if (workerClient !== undefined) {
- let response = {
- from: workerClient.actor,
- type: "attached",
- url: workerClient.url
- };
- DevToolsUtils.executeSoon(() => onResponse(response, workerClient));
- return promise.resolve([response, workerClient]);
- }
-
- return this.request({ to: workerActor, type: "attach" }).then(response => {
- if (response.error) {
- onResponse(response, null);
- return [response, null];
- }
-
- workerClient = new WorkerClient(this, response);
- this.registerClient(workerClient);
- onResponse(response, workerClient);
- return [response, workerClient];
- });
- },
-
- /**
- * Attach to an addon actor.
- *
- * @param string addonActor
- * The actor ID for the addon to attach.
- * @param function onResponse
- * Called with the response packet and a AddonClient
- * (which will be undefined on error).
- */
- attachAddon: function (addonActor, onResponse = noop) {
- let packet = {
- to: addonActor,
- type: "attach"
- };
- return this.request(packet).then(response => {
- let addonClient;
- if (!response.error) {
- addonClient = new AddonClient(this, addonActor);
- this.registerClient(addonClient);
- this.activeAddon = addonClient;
- }
- onResponse(response, addonClient);
- return [response, addonClient];
- });
- },
-
- /**
- * Attach to a Web Console actor.
- *
- * @param string consoleActor
- * The ID for the console actor to attach to.
- * @param array listeners
- * The console listeners you want to start.
- * @param function onResponse
- * Called with the response packet and a WebConsoleClient
- * instance (which will be undefined on error).
- */
- attachConsole:
- function (consoleActor, listeners, onResponse = noop) {
- let packet = {
- to: consoleActor,
- type: "startListeners",
- listeners: listeners,
- };
-
- return this.request(packet).then(response => {
- let consoleClient;
- if (!response.error) {
- if (this._clients.has(consoleActor)) {
- consoleClient = this._clients.get(consoleActor);
- } else {
- consoleClient = new WebConsoleClient(this, response);
- this.registerClient(consoleClient);
- }
- }
- onResponse(response, consoleClient);
- return [response, consoleClient];
- });
- },
-
- /**
- * Attach to a global-scoped thread actor for chrome debugging.
- *
- * @param string threadActor
- * The actor ID for the thread to attach.
- * @param function onResponse
- * Called with the response packet and a ThreadClient
- * (which will be undefined on error).
- * @param object options
- * Configuration options.
- * - useSourceMaps: whether to use source maps or not.
- */
- attachThread: function (threadActor, onResponse = noop, options = {}) {
- if (this._clients.has(threadActor)) {
- let client = this._clients.get(threadActor);
- DevToolsUtils.executeSoon(() => onResponse({}, client));
- return promise.resolve([{}, client]);
- }
-
- let packet = {
- to: threadActor,
- type: "attach",
- options,
- };
- return this.request(packet).then(response => {
- let threadClient;
- if (!response.error) {
- threadClient = new ThreadClient(this, threadActor);
- this.registerClient(threadClient);
- }
- onResponse(response, threadClient);
- return [response, threadClient];
- });
- },
-
- /**
- * Attach to a trace actor.
- *
- * @param string traceActor
- * The actor ID for the tracer to attach.
- * @param function onResponse
- * Called with the response packet and a TraceClient
- * (which will be undefined on error).
- */
- attachTracer: function (traceActor, onResponse = noop) {
- if (this._clients.has(traceActor)) {
- let client = this._clients.get(traceActor);
- DevToolsUtils.executeSoon(() => onResponse({}, client));
- return promise.resolve([{}, client]);
- }
-
- let packet = {
- to: traceActor,
- type: "attach"
- };
- return this.request(packet).then(response => {
- let traceClient;
- if (!response.error) {
- traceClient = new TraceClient(this, traceActor);
- this.registerClient(traceClient);
- }
- onResponse(response, traceClient);
- return [response, traceClient];
- });
- },
-
- /**
- * Fetch the ChromeActor for the main process or ChildProcessActor for a
- * a given child process ID.
- *
- * @param number id
- * The ID for the process to attach (returned by `listProcesses`).
- * Connected to the main process if omitted, or is 0.
- */
- getProcess: function (id) {
- let packet = {
- to: "root",
- type: "getProcess"
- };
- if (typeof (id) == "number") {
- packet.id = id;
- }
- return this.request(packet);
- },
-
- /**
- * Release an object actor.
- *
- * @param string actor
- * The actor ID to send the request to.
- * @param onResponse function
- * If specified, will be called with the response packet when
- * debugging server responds.
- */
- release: DebuggerClient.requester({
- to: arg(0),
- type: "release"
- }),
-
- /**
- * Send a request to the debugging server.
- *
- * @param packet object
- * A JSON packet to send to the debugging server.
- * @param onResponse function
- * If specified, will be called with the JSON response packet when
- * debugging server responds.
- * @return Request
- * This object emits a number of events to allow you to respond to
- * different parts of the request lifecycle.
- * It is also a Promise object, with a `then` method, that is resolved
- * whenever a JSON or a Bulk response is received; and is rejected
- * if the response is an error.
- * Note: This return value can be ignored if you are using JSON alone,
- * because the callback provided in |onResponse| will be bound to the
- * "json-reply" event automatically.
- *
- * Events emitted:
- * * json-reply: The server replied with a JSON packet, which is
- * passed as event data.
- * * bulk-reply: The server replied with bulk data, which you can read
- * using the event data object containing:
- * * actor: Name of actor that received the packet
- * * type: Name of actor's method that was called on receipt
- * * length: Size of the data to be read
- * * stream: This input stream should only be used directly if you
- * can ensure that you will read exactly |length| bytes
- * and will not close the stream when reading is complete
- * * done: If you use the stream directly (instead of |copyTo|
- * below), you must signal completion by resolving /
- * rejecting this deferred. If it's rejected, the
- * transport will be closed. If an Error is supplied as a
- * rejection value, it will be logged via |dumpn|. If you
- * do use |copyTo|, resolving is taken care of for you
- * when copying completes.
- * * copyTo: A helper function for getting your data out of the
- * stream that meets the stream handling requirements
- * above, and has the following signature:
- * @param output nsIAsyncOutputStream
- * The stream to copy to.
- * @return Promise
- * The promise is resolved when copying completes or
- * rejected if any (unexpected) errors occur.
- * This object also emits "progress" events for each chunk
- * that is copied. See stream-utils.js.
- */
- request: function (packet, onResponse) {
- if (!this.mainRoot) {
- throw Error("Have not yet received a hello packet from the server.");
- }
- let type = packet.type || "";
- if (!packet.to) {
- throw Error("'" + type + "' request packet has no destination.");
- }
-
- // The onResponse callback might modify the response, so we need to call
- // it and resolve the promise with its result if it's truthy.
- const safeOnResponse = response => {
- if (!onResponse) {
- return response;
- }
- return onResponse(response) || response;
- };
-
- if (this._closed) {
- let msg = "'" + type + "' request packet to " +
- "'" + packet.to + "' " +
- "can't be sent as the connection is closed.";
- let resp = { error: "connectionClosed", message: msg };
- return promise.reject(safeOnResponse(resp));
- }
-
- let request = new Request(packet);
- request.format = "json";
- request.stack = getStack();
-
- // Implement a Promise like API on the returned object
- // that resolves/rejects on request response
- let deferred = promise.defer();
- function listenerJson(resp) {
- removeRequestListeners();
- if (resp.error) {
- deferred.reject(safeOnResponse(resp));
- } else {
- deferred.resolve(safeOnResponse(resp));
- }
- }
- function listenerBulk(resp) {
- removeRequestListeners();
- deferred.resolve(safeOnResponse(resp));
- }
-
- const removeRequestListeners = () => {
- request.off("json-reply", listenerJson);
- request.off("bulk-reply", listenerBulk);
- };
-
- request.on("json-reply", listenerJson);
- request.on("bulk-reply", listenerBulk);
-
- this._sendOrQueueRequest(request);
- request.then = deferred.promise.then.bind(deferred.promise);
-
- return request;
- },
-
- /**
- * Transmit streaming data via a bulk request.
- *
- * This method initiates the bulk send process by queuing up the header data.
- * The caller receives eventual access to a stream for writing.
- *
- * Since this opens up more options for how the server might respond (it could
- * send back either JSON or bulk data), and the returned Request object emits
- * events for different stages of the request process that you may want to
- * react to.
- *
- * @param request Object
- * This is modeled after the format of JSON packets above, but does not
- * actually contain the data, but is instead just a routing header:
- * * actor: Name of actor that will receive the packet
- * * type: Name of actor's method that should be called on receipt
- * * length: Size of the data to be sent
- * @return Request
- * This object emits a number of events to allow you to respond to
- * different parts of the request lifecycle.
- *
- * Events emitted:
- * * bulk-send-ready: Ready to send bulk data to the server, using the
- * event data object containing:
- * * stream: This output stream should only be used directly if
- * you can ensure that you will write exactly |length|
- * bytes and will not close the stream when writing is
- * complete
- * * done: If you use the stream directly (instead of |copyFrom|
- * below), you must signal completion by resolving /
- * rejecting this deferred. If it's rejected, the
- * transport will be closed. If an Error is supplied as
- * a rejection value, it will be logged via |dumpn|. If
- * you do use |copyFrom|, resolving is taken care of for
- * you when copying completes.
- * * copyFrom: A helper function for getting your data onto the
- * stream that meets the stream handling requirements
- * above, and has the following signature:
- * @param input nsIAsyncInputStream
- * The stream to copy from.
- * @return Promise
- * The promise is resolved when copying completes or
- * rejected if any (unexpected) errors occur.
- * This object also emits "progress" events for each chunk
- * that is copied. See stream-utils.js.
- * * json-reply: The server replied with a JSON packet, which is
- * passed as event data.
- * * bulk-reply: The server replied with bulk data, which you can read
- * using the event data object containing:
- * * actor: Name of actor that received the packet
- * * type: Name of actor's method that was called on receipt
- * * length: Size of the data to be read
- * * stream: This input stream should only be used directly if you
- * can ensure that you will read exactly |length| bytes
- * and will not close the stream when reading is complete
- * * done: If you use the stream directly (instead of |copyTo|
- * below), you must signal completion by resolving /
- * rejecting this deferred. If it's rejected, the
- * transport will be closed. If an Error is supplied as a
- * rejection value, it will be logged via |dumpn|. If you
- * do use |copyTo|, resolving is taken care of for you
- * when copying completes.
- * * copyTo: A helper function for getting your data out of the
- * stream that meets the stream handling requirements
- * above, and has the following signature:
- * @param output nsIAsyncOutputStream
- * The stream to copy to.
- * @return Promise
- * The promise is resolved when copying completes or
- * rejected if any (unexpected) errors occur.
- * This object also emits "progress" events for each chunk
- * that is copied. See stream-utils.js.
- */
- startBulkRequest: function (request) {
- if (!this.traits.bulk) {
- throw Error("Server doesn't support bulk transfers");
- }
- if (!this.mainRoot) {
- throw Error("Have not yet received a hello packet from the server.");
- }
- if (!request.type) {
- throw Error("Bulk packet is missing the required 'type' field.");
- }
- if (!request.actor) {
- throw Error("'" + request.type + "' bulk packet has no destination.");
- }
- if (!request.length) {
- throw Error("'" + request.type + "' bulk packet has no length.");
- }
-
- request = new Request(request);
- request.format = "bulk";
-
- this._sendOrQueueRequest(request);
-
- return request;
- },
-
- /**
- * If a new request can be sent immediately, do so. Otherwise, queue it.
- */
- _sendOrQueueRequest(request) {
- let actor = request.actor;
- if (!this._activeRequests.has(actor)) {
- this._sendRequest(request);
- } else {
- this._queueRequest(request);
- }
- },
-
- /**
- * Send a request.
- * @throws Error if there is already an active request in flight for the same
- * actor.
- */
- _sendRequest(request) {
- let actor = request.actor;
- this.expectReply(actor, request);
-
- if (request.format === "json") {
- this._transport.send(request.request);
- return;
- }
-
- this._transport.startBulkSend(request.request).then((...args) => {
- request.emit("bulk-send-ready", ...args);
- });
- },
-
- /**
- * Queue a request to be sent later. Queues are only drained when an in
- * flight request to a given actor completes.
- */
- _queueRequest(request) {
- let actor = request.actor;
- let queue = this._pendingRequests.get(actor) || [];
- queue.push(request);
- this._pendingRequests.set(actor, queue);
- },
-
- /**
- * Attempt the next request to a given actor (if any).
- */
- _attemptNextRequest(actor) {
- if (this._activeRequests.has(actor)) {
- return;
- }
- let queue = this._pendingRequests.get(actor);
- if (!queue) {
- return;
- }
- let request = queue.shift();
- if (queue.length === 0) {
- this._pendingRequests.delete(actor);
- }
- this._sendRequest(request);
- },
-
- /**
- * Arrange to hand the next reply from |actor| to the handler bound to
- * |request|.
- *
- * DebuggerClient.prototype.request / startBulkRequest usually takes care of
- * establishing the handler for a given request, but in rare cases (well,
- * greetings from new root actors, is the only case at the moment) we must be
- * prepared for a "reply" that doesn't correspond to any request we sent.
- */
- expectReply: function (actor, request) {
- if (this._activeRequests.has(actor)) {
- throw Error("clashing handlers for next reply from " + actor);
- }
-
- // If a handler is passed directly (as it is with the handler for the root
- // actor greeting), create a dummy request to bind this to.
- if (typeof request === "function") {
- let handler = request;
- request = new Request();
- request.on("json-reply", handler);
- }
-
- this._activeRequests.set(actor, request);
- },
-
- // Transport hooks.
-
- /**
- * Called by DebuggerTransport to dispatch incoming packets as appropriate.
- *
- * @param packet object
- * The incoming packet.
- */
- onPacket: function (packet) {
- if (!packet.from) {
- DevToolsUtils.reportException(
- "onPacket",
- new Error("Server did not specify an actor, dropping packet: " +
- JSON.stringify(packet)));
- return;
- }
-
- // If we have a registered Front for this actor, let it handle the packet
- // and skip all the rest of this unpleasantness.
- let front = this.getActor(packet.from);
- if (front) {
- front.onPacket(packet);
- return;
- }
-
- // Check for "forwardingCancelled" here instead of using a client to handle it.
- // This is necessary because we might receive this event while the client is closing,
- // and the clients have already been removed by that point.
- if (this.mainRoot &&
- packet.from == this.mainRoot.actor &&
- packet.type == "forwardingCancelled") {
- this.purgeRequests(packet.prefix);
- return;
- }
-
- if (this._clients.has(packet.from) && packet.type) {
- let client = this._clients.get(packet.from);
- let type = packet.type;
- if (client.events.indexOf(type) != -1) {
- client.emit(type, packet);
- // we ignore the rest, as the client is expected to handle this packet.
- return;
- }
- }
-
- let activeRequest;
- // See if we have a handler function waiting for a reply from this
- // actor. (Don't count unsolicited notifications or pauses as
- // replies.)
- if (this._activeRequests.has(packet.from) &&
- !(packet.type in UnsolicitedNotifications) &&
- !(packet.type == ThreadStateTypes.paused &&
- packet.why.type in UnsolicitedPauses)) {
- activeRequest = this._activeRequests.get(packet.from);
- this._activeRequests.delete(packet.from);
- }
-
- // If there is a subsequent request for the same actor, hand it off to the
- // transport. Delivery of packets on the other end is always async, even
- // in the local transport case.
- this._attemptNextRequest(packet.from);
-
- // Packets that indicate thread state changes get special treatment.
- if (packet.type in ThreadStateTypes &&
- this._clients.has(packet.from) &&
- typeof this._clients.get(packet.from)._onThreadState == "function") {
- this._clients.get(packet.from)._onThreadState(packet);
- }
-
- // TODO: Bug 1151156 - Remove once Gecko 40 is on b2g-stable.
- if (!this.traits.noNeedToFakeResumptionOnNavigation) {
- // On navigation the server resumes, so the client must resume as well.
- // We achieve that by generating a fake resumption packet that triggers
- // the client's thread state change listeners.
- if (packet.type == UnsolicitedNotifications.tabNavigated &&
- this._clients.has(packet.from) &&
- this._clients.get(packet.from).thread) {
- let thread = this._clients.get(packet.from).thread;
- let resumption = { from: thread._actor, type: "resumed" };
- thread._onThreadState(resumption);
- }
- }
-
- // Only try to notify listeners on events, not responses to requests
- // that lack a packet type.
- if (packet.type) {
- this.emit(packet.type, packet);
- }
-
- if (activeRequest) {
- let emitReply = () => activeRequest.emit("json-reply", packet);
- if (activeRequest.stack) {
- callFunctionWithAsyncStack(emitReply, activeRequest.stack,
- "DevTools RDP");
- } else {
- emitReply();
- }
- }
- },
-
- /**
- * Called by the DebuggerTransport to dispatch incoming bulk packets as
- * appropriate.
- *
- * @param packet object
- * The incoming packet, which contains:
- * * actor: Name of actor that will receive the packet
- * * type: Name of actor's method that should be called on receipt
- * * length: Size of the data to be read
- * * stream: This input stream should only be used directly if you can
- * ensure that you will read exactly |length| bytes and will
- * not close the stream when reading is complete
- * * done: If you use the stream directly (instead of |copyTo|
- * below), you must signal completion by resolving /
- * rejecting this deferred. If it's rejected, the transport
- * will be closed. If an Error is supplied as a rejection
- * value, it will be logged via |dumpn|. If you do use
- * |copyTo|, resolving is taken care of for you when copying
- * completes.
- * * copyTo: A helper function for getting your data out of the stream
- * that meets the stream handling requirements above, and has
- * the following signature:
- * @param output nsIAsyncOutputStream
- * The stream to copy to.
- * @return Promise
- * The promise is resolved when copying completes or rejected
- * if any (unexpected) errors occur.
- * This object also emits "progress" events for each chunk
- * that is copied. See stream-utils.js.
- */
- onBulkPacket: function (packet) {
- let { actor } = packet;
-
- if (!actor) {
- DevToolsUtils.reportException(
- "onBulkPacket",
- new Error("Server did not specify an actor, dropping bulk packet: " +
- JSON.stringify(packet)));
- return;
- }
-
- // See if we have a handler function waiting for a reply from this
- // actor.
- if (!this._activeRequests.has(actor)) {
- return;
- }
-
- let activeRequest = this._activeRequests.get(actor);
- this._activeRequests.delete(actor);
-
- // If there is a subsequent request for the same actor, hand it off to the
- // transport. Delivery of packets on the other end is always async, even
- // in the local transport case.
- this._attemptNextRequest(actor);
-
- activeRequest.emit("bulk-reply", packet);
- },
-
- /**
- * Called by DebuggerTransport when the underlying stream is closed.
- *
- * @param status nsresult
- * The status code that corresponds to the reason for closing
- * the stream.
- */
- onClosed: function () {
- this._closed = true;
- this.emit("closed");
-
- this.purgeRequests();
-
- // The |_pools| array on the client-side currently is used only by
- // protocol.js to store active fronts, mirroring the actor pools found in
- // the server. So, read all usages of "pool" as "protocol.js front".
- //
- // In the normal case where we shutdown cleanly, the toolbox tells each tool
- // to close, and they each call |destroy| on any fronts they were using.
- // When |destroy| or |cleanup| is called on a protocol.js front, it also
- // removes itself from the |_pools| array. Once the toolbox has shutdown,
- // the connection is closed, and we reach here. All fronts (should have
- // been) |destroy|ed, so |_pools| should empty.
- //
- // If the connection instead aborts unexpectedly, we may end up here with
- // all fronts used during the life of the connection. So, we call |cleanup|
- // on them clear their state, reject pending requests, and remove themselves
- // from |_pools|. This saves the toolbox from hanging indefinitely, in case
- // it waits for some server response before shutdown that will now never
- // arrive.
- for (let pool of this._pools) {
- pool.cleanup();
- }
- },
-
- /**
- * Purge pending and active requests in this client.
- *
- * @param prefix string (optional)
- * If a prefix is given, only requests for actor IDs that start with the prefix
- * will be cleaned up. This is useful when forwarding of a portion of requests
- * is cancelled on the server.
- */
- purgeRequests(prefix = "") {
- let reject = function (type, request) {
- // Server can send packets on its own and client only pass a callback
- // to expectReply, so that there is no request object.
- let msg;
- if (request.request) {
- msg = "'" + request.request.type + "' " + type + " request packet" +
- " to '" + request.actor + "' " +
- "can't be sent as the connection just closed.";
- } else {
- msg = "server side packet can't be received as the connection just closed.";
- }
- let packet = { error: "connectionClosed", message: msg };
- request.emit("json-reply", packet);
- };
-
- let pendingRequestsToReject = [];
- this._pendingRequests.forEach((requests, actor) => {
- if (!actor.startsWith(prefix)) {
- return;
- }
- this._pendingRequests.delete(actor);
- pendingRequestsToReject = pendingRequestsToReject.concat(requests);
- });
- pendingRequestsToReject.forEach(request => reject("pending", request));
-
- let activeRequestsToReject = [];
- this._activeRequests.forEach((request, actor) => {
- if (!actor.startsWith(prefix)) {
- return;
- }
- this._activeRequests.delete(actor);
- activeRequestsToReject = activeRequestsToReject.concat(request);
- });
- activeRequestsToReject.forEach(request => reject("active", request));
- },
-
- /**
- * Search for all requests in process for this client, including those made via
- * protocol.js and wait all of them to complete. Since the requests seen when this is
- * first called may in turn trigger more requests, we keep recursing through this
- * function until there is no more activity.
- *
- * This is a fairly heavy weight process, so it's only meant to be used in tests.
- *
- * @return Promise
- * Resolved when all requests have settled.
- */
- waitForRequestsToSettle() {
- let requests = [];
-
- // Gather all pending and active requests in this client
- // The request object supports a Promise API for completion (it has .then())
- this._pendingRequests.forEach(requestsForActor => {
- // Each value is an array of pending requests
- requests = requests.concat(requestsForActor);
- });
- this._activeRequests.forEach(requestForActor => {
- // Each value is a single active request
- requests = requests.concat(requestForActor);
- });
-
- // protocol.js
- // Use a Set because some fronts (like domwalker) seem to have multiple parents.
- let fronts = new Set();
- let poolsToVisit = [...this._pools];
-
- // With protocol.js, each front can potentially have it's own pools containing child
- // fronts, forming a tree. Descend through all the pools to locate all child fronts.
- while (poolsToVisit.length) {
- let pool = poolsToVisit.shift();
- fronts.add(pool);
- for (let child of pool.poolChildren()) {
- poolsToVisit.push(child);
- }
- }
-
- // For each front, wait for its requests to settle
- for (let front of fronts) {
- if (front.hasRequests()) {
- requests.push(front.waitForRequestsToSettle());
- }
- }
-
- // Abort early if there are no requests
- if (!requests.length) {
- return Promise.resolve();
- }
-
- return DevToolsUtils.settleAll(requests).catch(() => {
- // One of the requests might have failed, but ignore that situation here and pipe
- // both success and failure through the same path. The important part is just that
- // we waited.
- }).then(() => {
- // Repeat, more requests may have started in response to those we just waited for
- return this.waitForRequestsToSettle();
- });
- },
-
- registerClient: function (client) {
- let actorID = client.actor;
- if (!actorID) {
- throw new Error("DebuggerServer.registerClient expects " +
- "a client instance with an `actor` attribute.");
- }
- if (!Array.isArray(client.events)) {
- throw new Error("DebuggerServer.registerClient expects " +
- "a client instance with an `events` attribute " +
- "that is an array.");
- }
- if (client.events.length > 0 && typeof (client.emit) != "function") {
- throw new Error("DebuggerServer.registerClient expects " +
- "a client instance with non-empty `events` array to" +
- "have an `emit` function.");
- }
- if (this._clients.has(actorID)) {
- throw new Error("DebuggerServer.registerClient already registered " +
- "a client for this actor.");
- }
- this._clients.set(actorID, client);
- },
-
- unregisterClient: function (client) {
- let actorID = client.actor;
- if (!actorID) {
- throw new Error("DebuggerServer.unregisterClient expects " +
- "a Client instance with a `actor` attribute.");
- }
- this._clients.delete(actorID);
- },
-
- /**
- * Actor lifetime management, echos the server's actor pools.
- */
- __pools: null,
- get _pools() {
- if (this.__pools) {
- return this.__pools;
- }
- this.__pools = new Set();
- return this.__pools;
- },
-
- addActorPool: function (pool) {
- this._pools.add(pool);
- },
- removeActorPool: function (pool) {
- this._pools.delete(pool);
- },
- getActor: function (actorID) {
- let pool = this.poolFor(actorID);
- return pool ? pool.get(actorID) : null;
- },
-
- poolFor: function (actorID) {
- for (let pool of this._pools) {
- if (pool.has(actorID)) {
- return pool;
- }
- }
- return null;
- },
-
- /**
- * Currently attached addon.
- */
- activeAddon: null
-};
-
-eventSource(DebuggerClient.prototype);
-
-class Request extends EventEmitter {
- constructor(request) {
- super();
- this.request = request;
- }
-
- get actor() {
- return this.request.to || this.request.actor;
- }
-}
-
-/**
- * Creates a tab client for the remote debugging protocol server. This client
- * is a front to the tab actor created in the server side, hiding the protocol
- * details in a traditional JavaScript API.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param form object
- * The protocol form for this tab.
- */
-function TabClient(client, form) {
- this.client = client;
- this._actor = form.from;
- this._threadActor = form.threadActor;
- this.javascriptEnabled = form.javascriptEnabled;
- this.cacheDisabled = form.cacheDisabled;
- this.thread = null;
- this.request = this.client.request;
- this.traits = form.traits || {};
- this.events = ["workerListChanged"];
-}
-
-TabClient.prototype = {
- get actor() {
- return this._actor;
- },
- get _transport() {
- return this.client._transport;
- },
-
- /**
- * Attach to a thread actor.
- *
- * @param object options
- * Configuration options.
- * - useSourceMaps: whether to use source maps or not.
- * @param function onResponse
- * Called with the response packet and a ThreadClient
- * (which will be undefined on error).
- */
- attachThread: function (options = {}, onResponse = noop) {
- if (this.thread) {
- DevToolsUtils.executeSoon(() => onResponse({}, this.thread));
- return promise.resolve([{}, this.thread]);
- }
-
- let packet = {
- to: this._threadActor,
- type: "attach",
- options,
- };
- return this.request(packet).then(response => {
- if (!response.error) {
- this.thread = new ThreadClient(this, this._threadActor);
- this.client.registerClient(this.thread);
- }
- onResponse(response, this.thread);
- return [response, this.thread];
- });
- },
-
- /**
- * Detach the client from the tab actor.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- detach: DebuggerClient.requester({
- type: "detach"
- }, {
- before: function (packet) {
- if (this.thread) {
- this.thread.detach();
- }
- return packet;
- },
- after: function (response) {
- this.client.unregisterClient(this);
- return response;
- },
- }),
-
- /**
- * Bring the window to the front.
- */
- focus: DebuggerClient.requester({
- type: "focus"
- }, {}),
-
- /**
- * Reload the page in this tab.
- *
- * @param [optional] object options
- * An object with a `force` property indicating whether or not
- * this reload should skip the cache
- */
- reload: function (options = { force: false }) {
- return this._reload(options);
- },
- _reload: DebuggerClient.requester({
- type: "reload",
- options: arg(0)
- }),
-
- /**
- * Navigate to another URL.
- *
- * @param string url
- * The URL to navigate to.
- */
- navigateTo: DebuggerClient.requester({
- type: "navigateTo",
- url: arg(0)
- }),
-
- /**
- * Reconfigure the tab actor.
- *
- * @param object options
- * A dictionary object of the new options to use in the tab actor.
- * @param function onResponse
- * Called with the response packet.
- */
- reconfigure: DebuggerClient.requester({
- type: "reconfigure",
- options: arg(0)
- }),
-
- listWorkers: DebuggerClient.requester({
- type: "listWorkers"
- }),
-
- attachWorker: function (workerActor, onResponse) {
- return this.client.attachWorker(workerActor, onResponse);
- },
-};
-
-eventSource(TabClient.prototype);
-
-function WorkerClient(client, form) {
- this.client = client;
- this._actor = form.from;
- this._isClosed = false;
- this._url = form.url;
-
- this._onClose = this._onClose.bind(this);
-
- this.addListener("close", this._onClose);
-
- this.traits = {};
-}
-
-WorkerClient.prototype = {
- get _transport() {
- return this.client._transport;
- },
-
- get request() {
- return this.client.request;
- },
-
- get actor() {
- return this._actor;
- },
-
- get url() {
- return this._url;
- },
-
- get isClosed() {
- return this._isClosed;
- },
-
- detach: DebuggerClient.requester({ type: "detach" }, {
- after: function (response) {
- if (this.thread) {
- this.client.unregisterClient(this.thread);
- }
- this.client.unregisterClient(this);
- return response;
- },
- }),
-
- attachThread: function (options = {}, onResponse = noop) {
- if (this.thread) {
- let response = [{
- type: "connected",
- threadActor: this.thread._actor,
- consoleActor: this.consoleActor,
- }, this.thread];
- DevToolsUtils.executeSoon(() => onResponse(response));
- return response;
- }
-
- // The connect call on server doesn't attach the thread as of version 44.
- return this.request({
- to: this._actor,
- type: "connect",
- options,
- }).then(connectResponse => {
- if (connectResponse.error) {
- onResponse(connectResponse, null);
- return [connectResponse, null];
- }
-
- return this.request({
- to: connectResponse.threadActor,
- type: "attach",
- options,
- }).then(attachResponse => {
- if (attachResponse.error) {
- onResponse(attachResponse, null);
- }
-
- this.thread = new ThreadClient(this, connectResponse.threadActor);
- this.consoleActor = connectResponse.consoleActor;
- this.client.registerClient(this.thread);
-
- onResponse(connectResponse, this.thread);
- return [connectResponse, this.thread];
- });
- }, error => {
- onResponse(error, null);
- });
- },
-
- _onClose: function () {
- this.removeListener("close", this._onClose);
-
- if (this.thread) {
- this.client.unregisterClient(this.thread);
- }
- this.client.unregisterClient(this);
- this._isClosed = true;
- },
-
- reconfigure: function () {
- return Promise.resolve();
- },
-
- events: ["close"]
-};
-
-eventSource(WorkerClient.prototype);
-
-function AddonClient(client, actor) {
- this._client = client;
- this._actor = actor;
- this.request = this._client.request;
- this.events = [];
-}
-
-AddonClient.prototype = {
- get actor() {
- return this._actor;
- },
- get _transport() {
- return this._client._transport;
- },
-
- /**
- * Detach the client from the addon actor.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- detach: DebuggerClient.requester({
- type: "detach"
- }, {
- after: function (response) {
- if (this._client.activeAddon === this) {
- this._client.activeAddon = null;
- }
- this._client.unregisterClient(this);
- return response;
- },
- })
-};
-
-/**
- * A RootClient object represents a root actor on the server. Each
- * DebuggerClient keeps a RootClient instance representing the root actor
- * for the initial connection; DebuggerClient's 'listTabs' and
- * 'listChildProcesses' methods forward to that root actor.
- *
- * @param client object
- * The client connection to which this actor belongs.
- * @param greeting string
- * The greeting packet from the root actor we're to represent.
- *
- * Properties of a RootClient instance:
- *
- * @property actor string
- * The name of this child's root actor.
- * @property applicationType string
- * The application type, as given in the root actor's greeting packet.
- * @property traits object
- * The traits object, as given in the root actor's greeting packet.
- */
-function RootClient(client, greeting) {
- this._client = client;
- this.actor = greeting.from;
- this.applicationType = greeting.applicationType;
- this.traits = greeting.traits;
-}
-exports.RootClient = RootClient;
-
-RootClient.prototype = {
- constructor: RootClient,
-
- /**
- * Gets the "root" form, which lists all the global actors that affect the entire
- * browser. This can replace usages of `listTabs` that only wanted the global actors
- * and didn't actually care about tabs.
- */
- getRoot: DebuggerClient.requester({ type: "getRoot" }),
-
- /**
- * List the open tabs.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- listTabs: DebuggerClient.requester({ type: "listTabs" }),
-
- /**
- * List the installed addons.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- listAddons: DebuggerClient.requester({ type: "listAddons" }),
-
- /**
- * List the registered workers.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- listWorkers: DebuggerClient.requester({ type: "listWorkers" }),
-
- /**
- * List the registered service workers.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- listServiceWorkerRegistrations: DebuggerClient.requester({
- type: "listServiceWorkerRegistrations"
- }),
-
- /**
- * List the running processes.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- listProcesses: DebuggerClient.requester({ type: "listProcesses" }),
-
- /**
- * Fetch the TabActor for the currently selected tab, or for a specific
- * tab given as first parameter.
- *
- * @param [optional] object filter
- * A dictionary object with following optional attributes:
- * - outerWindowID: used to match tabs in parent process
- * - tabId: used to match tabs in child processes
- * - tab: a reference to xul:tab element
- * If nothing is specified, returns the actor for the currently
- * selected tab.
- */
- getTab: function (filter) {
- let packet = {
- to: this.actor,
- type: "getTab"
- };
-
- if (filter) {
- if (typeof (filter.outerWindowID) == "number") {
- packet.outerWindowID = filter.outerWindowID;
- } else if (typeof (filter.tabId) == "number") {
- packet.tabId = filter.tabId;
- } else if ("tab" in filter) {
- let browser = filter.tab.linkedBrowser;
- if (browser.frameLoader.tabParent) {
- // Tabs in child process
- packet.tabId = browser.frameLoader.tabParent.tabId;
- } else if (browser.outerWindowID) {
- // <xul:browser> tabs in parent process
- packet.outerWindowID = browser.outerWindowID;
- } else {
- // <iframe mozbrowser> tabs in parent process
- let windowUtils = browser.contentWindow
- .QueryInterface(Ci.nsIInterfaceRequestor)
- .getInterface(Ci.nsIDOMWindowUtils);
- packet.outerWindowID = windowUtils.outerWindowID;
- }
- } else {
- // Throw if a filter object have been passed but without
- // any clearly idenfified filter.
- throw new Error("Unsupported argument given to getTab request");
- }
- }
-
- return this.request(packet);
- },
-
- /**
- * Fetch the WindowActor for a specific window, like a browser window in
- * Firefox, but it can be used to reach any window in the process.
- *
- * @param number outerWindowID
- * The outerWindowID of the top level window you are looking for.
- */
- getWindow: function ({ outerWindowID }) {
- if (!outerWindowID) {
- throw new Error("Must specify outerWindowID");
- }
-
- let packet = {
- to: this.actor,
- type: "getWindow",
- outerWindowID,
- };
-
- return this.request(packet);
- },
-
- /**
- * Description of protocol's actors and methods.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- protocolDescription: DebuggerClient.requester({ type: "protocolDescription" }),
-
- /*
- * Methods constructed by DebuggerClient.requester require these forwards
- * on their 'this'.
- */
- get _transport() {
- return this._client._transport;
- },
- get request() {
- return this._client.request;
- }
+module.exports = {
+ ThreadStateTypes,
+ UnsolicitedNotifications,
+ UnsolicitedPauses,
};
-
-/**
- * Creates a thread client for the remote debugging protocol server. This client
- * is a front to the thread actor created in the server side, hiding the
- * protocol details in a traditional JavaScript API.
- *
- * @param client DebuggerClient|TabClient
- * The parent of the thread (tab for tab-scoped debuggers, DebuggerClient
- * for chrome debuggers).
- * @param actor string
- * The actor ID for this thread.
- */
-function ThreadClient(client, actor) {
- this._parent = client;
- this.client = client instanceof DebuggerClient ? client : client.client;
- this._actor = actor;
- this._frameCache = [];
- this._scriptCache = {};
- this._pauseGrips = {};
- this._threadGrips = {};
- this.request = this.client.request;
-}
-
-ThreadClient.prototype = {
- _state: "paused",
- get state() {
- return this._state;
- },
- get paused() {
- return this._state === "paused";
- },
-
- _pauseOnExceptions: false,
- _ignoreCaughtExceptions: false,
- _pauseOnDOMEvents: null,
-
- _actor: null,
- get actor() {
- return this._actor;
- },
-
- get _transport() {
- return this.client._transport;
- },
-
- _assertPaused: function (command) {
- if (!this.paused) {
- throw Error(command + " command sent while not paused. Currently " + this._state);
- }
- },
-
- /**
- * Resume a paused thread. If the optional limit parameter is present, then
- * the thread will also pause when that limit is reached.
- *
- * @param [optional] object limit
- * An object with a type property set to the appropriate limit (next,
- * step, or finish) per the remote debugging protocol specification.
- * Use null to specify no limit.
- * @param function onResponse
- * Called with the response packet.
- */
- _doResume: DebuggerClient.requester({
- type: "resume",
- resumeLimit: arg(0)
- }, {
- before: function (packet) {
- this._assertPaused("resume");
-
- // Put the client in a tentative "resuming" state so we can prevent
- // further requests that should only be sent in the paused state.
- this._previousState = this._state;
- this._state = "resuming";
-
- if (this._pauseOnExceptions) {
- packet.pauseOnExceptions = this._pauseOnExceptions;
- }
- if (this._ignoreCaughtExceptions) {
- packet.ignoreCaughtExceptions = this._ignoreCaughtExceptions;
- }
- if (this._pauseOnDOMEvents) {
- packet.pauseOnDOMEvents = this._pauseOnDOMEvents;
- }
- return packet;
- },
- after: function (response) {
- if (response.error && this._state == "resuming") {
- // There was an error resuming, update the state to the new one
- // reported by the server, if given (only on wrongState), otherwise
- // reset back to the previous state.
- if (response.state) {
- this._state = ThreadStateTypes[response.state];
- } else {
- this._state = this._previousState;
- }
- }
- delete this._previousState;
- return response;
- },
- }),
-
- /**
- * Reconfigure the thread actor.
- *
- * @param object options
- * A dictionary object of the new options to use in the thread actor.
- * @param function onResponse
- * Called with the response packet.
- */
- reconfigure: DebuggerClient.requester({
- type: "reconfigure",
- options: arg(0)
- }),
-
- /**
- * Resume a paused thread.
- */
- resume: function (onResponse) {
- return this._doResume(null, onResponse);
- },
-
- /**
- * Resume then pause without stepping.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- resumeThenPause: function (onResponse) {
- return this._doResume({ type: "break" }, onResponse);
- },
-
- /**
- * Step over a function call.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- stepOver: function (onResponse) {
- return this._doResume({ type: "next" }, onResponse);
- },
-
- /**
- * Step into a function call.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- stepIn: function (onResponse) {
- return this._doResume({ type: "step" }, onResponse);
- },
-
- /**
- * Step out of a function call.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- stepOut: function (onResponse) {
- return this._doResume({ type: "finish" }, onResponse);
- },
-
- /**
- * Immediately interrupt a running thread.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- interrupt: function (onResponse) {
- return this._doInterrupt(null, onResponse);
- },
-
- /**
- * Pause execution right before the next JavaScript bytecode is executed.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- breakOnNext: function (onResponse) {
- return this._doInterrupt("onNext", onResponse);
- },
-
- /**
- * Interrupt a running thread.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- _doInterrupt: DebuggerClient.requester({
- type: "interrupt",
- when: arg(0)
- }),
-
- /**
- * Enable or disable pausing when an exception is thrown.
- *
- * @param boolean pauseOnExceptions
- * Enables pausing if true, disables otherwise.
- * @param boolean ignoreCaughtExceptions
- * Whether to ignore caught exceptions
- * @param function onResponse
- * Called with the response packet.
- */
- pauseOnExceptions: function (pauseOnExceptions,
- ignoreCaughtExceptions,
- onResponse = noop) {
- this._pauseOnExceptions = pauseOnExceptions;
- this._ignoreCaughtExceptions = ignoreCaughtExceptions;
-
- // Otherwise send the flag using a standard resume request.
- if (!this.paused) {
- return this.interrupt(response => {
- if (response.error) {
- // Can't continue if pausing failed.
- onResponse(response);
- return response;
- }
- return this.resume(onResponse);
- });
- }
-
- onResponse();
- return promise.resolve();
- },
-
- /**
- * Enable pausing when the specified DOM events are triggered. Disabling
- * pausing on an event can be realized by calling this method with the updated
- * array of events that doesn't contain it.
- *
- * @param array|string events
- * An array of strings, representing the DOM event types to pause on,
- * or "*" to pause on all DOM events. Pass an empty array to
- * completely disable pausing on DOM events.
- * @param function onResponse
- * Called with the response packet in a future turn of the event loop.
- */
- pauseOnDOMEvents: function (events, onResponse = noop) {
- this._pauseOnDOMEvents = events;
- // If the debuggee is paused, the value of the array will be communicated in
- // the next resumption. Otherwise we have to force a pause in order to send
- // the array.
- if (this.paused) {
- DevToolsUtils.executeSoon(() => onResponse({}));
- return {};
- }
- return this.interrupt(response => {
- // Can't continue if pausing failed.
- if (response.error) {
- onResponse(response);
- return response;
- }
- return this.resume(onResponse);
- });
- },
-
- /**
- * Send a clientEvaluate packet to the debuggee. Response
- * will be a resume packet.
- *
- * @param string frame
- * The actor ID of the frame where the evaluation should take place.
- * @param string expression
- * The expression that will be evaluated in the scope of the frame
- * above.
- * @param function onResponse
- * Called with the response packet.
- */
- eval: DebuggerClient.requester({
- type: "clientEvaluate",
- frame: arg(0),
- expression: arg(1)
- }, {
- before: function (packet) {
- this._assertPaused("eval");
- // Put the client in a tentative "resuming" state so we can prevent
- // further requests that should only be sent in the paused state.
- this._state = "resuming";
- return packet;
- },
- after: function (response) {
- if (response.error) {
- // There was an error resuming, back to paused state.
- this._state = "paused";
- }
- return response;
- },
- }),
-
- /**
- * Detach from the thread actor.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- detach: DebuggerClient.requester({
- type: "detach"
- }, {
- after: function (response) {
- this.client.unregisterClient(this);
- this._parent.thread = null;
- return response;
- },
- }),
-
- /**
- * Release multiple thread-lifetime object actors. If any pause-lifetime
- * actors are included in the request, a |notReleasable| error will return,
- * but all the thread-lifetime ones will have been released.
- *
- * @param array actors
- * An array with actor IDs to release.
- */
- releaseMany: DebuggerClient.requester({
- type: "releaseMany",
- actors: arg(0),
- }),
-
- /**
- * Promote multiple pause-lifetime object actors to thread-lifetime ones.
- *
- * @param array actors
- * An array with actor IDs to promote.
- */
- threadGrips: DebuggerClient.requester({
- type: "threadGrips",
- actors: arg(0)
- }),
-
- /**
- * Return the event listeners defined on the page.
- *
- * @param onResponse Function
- * Called with the thread's response.
- */
- eventListeners: DebuggerClient.requester({
- type: "eventListeners"
- }),
-
- /**
- * Request the loaded sources for the current thread.
- *
- * @param onResponse Function
- * Called with the thread's response.
- */
- getSources: DebuggerClient.requester({
- type: "sources"
- }),
-
- /**
- * Clear the thread's source script cache. A scriptscleared event
- * will be sent.
- */
- _clearScripts: function () {
- if (Object.keys(this._scriptCache).length > 0) {
- this._scriptCache = {};
- this.emit("scriptscleared");
- }
- },
-
- /**
- * Request frames from the callstack for the current thread.
- *
- * @param start integer
- * The number of the youngest stack frame to return (the youngest
- * frame is 0).
- * @param count integer
- * The maximum number of frames to return, or null to return all
- * frames.
- * @param onResponse function
- * Called with the thread's response.
- */
- getFrames: DebuggerClient.requester({
- type: "frames",
- start: arg(0),
- count: arg(1)
- }),
-
- /**
- * An array of cached frames. Clients can observe the framesadded and
- * framescleared event to keep up to date on changes to this cache,
- * and can fill it using the fillFrames method.
- */
- get cachedFrames() {
- return this._frameCache;
- },
-
- /**
- * true if there are more stack frames available on the server.
- */
- get moreFrames() {
- return this.paused && (!this._frameCache || this._frameCache.length == 0
- || !this._frameCache[this._frameCache.length - 1].oldest);
- },
-
- /**
- * Request the frame environment.
- *
- * @param frameId string
- */
- getEnvironment: function (frameId) {
- return this.request({ to: frameId, type: "getEnvironment" });
- },
-
- /**
- * Ensure that at least total stack frames have been loaded in the
- * ThreadClient's stack frame cache. A framesadded event will be
- * sent when the stack frame cache is updated.
- *
- * @param total number
- * The minimum number of stack frames to be included.
- * @param callback function
- * Optional callback function called when frames have been loaded
- * @returns true if a framesadded notification should be expected.
- */
- fillFrames: function (total, callback = noop) {
- this._assertPaused("fillFrames");
- if (this._frameCache.length >= total) {
- return false;
- }
-
- let numFrames = this._frameCache.length;
-
- this.getFrames(numFrames, total - numFrames, (response) => {
- if (response.error) {
- callback(response);
- return;
- }
-
- let threadGrips = DevToolsUtils.values(this._threadGrips);
-
- for (let i in response.frames) {
- let frame = response.frames[i];
- if (!frame.where.source) {
- // Older servers use urls instead, so we need to resolve
- // them to source actors
- for (let grip of threadGrips) {
- if (grip instanceof SourceClient && grip.url === frame.url) {
- frame.where.source = grip._form;
- }
- }
- }
-
- this._frameCache[frame.depth] = frame;
- }
-
- // If we got as many frames as we asked for, there might be more
- // frames available.
- this.emit("framesadded");
-
- callback(response);
- });
-
- return true;
- },
-
- /**
- * Clear the thread's stack frame cache. A framescleared event
- * will be sent.
- */
- _clearFrames: function () {
- if (this._frameCache.length > 0) {
- this._frameCache = [];
- this.emit("framescleared");
- }
- },
-
- /**
- * Return a ObjectClient object for the given object grip.
- *
- * @param grip object
- * A pause-lifetime object grip returned by the protocol.
- */
- pauseGrip: function (grip) {
- if (grip.actor in this._pauseGrips) {
- return this._pauseGrips[grip.actor];
- }
-
- let client = new ObjectClient(this.client, grip);
- this._pauseGrips[grip.actor] = client;
- return client;
- },
-
- /**
- * Get or create a long string client, checking the grip client cache if it
- * already exists.
- *
- * @param grip Object
- * The long string grip returned by the protocol.
- * @param gripCacheName String
- * The property name of the grip client cache to check for existing
- * clients in.
- */
- _longString: function (grip, gripCacheName) {
- if (grip.actor in this[gripCacheName]) {
- return this[gripCacheName][grip.actor];
- }
-
- let client = new LongStringClient(this.client, grip);
- this[gripCacheName][grip.actor] = client;
- return client;
- },
-
- /**
- * Return an instance of LongStringClient for the given long string grip that
- * is scoped to the current pause.
- *
- * @param grip Object
- * The long string grip returned by the protocol.
- */
- pauseLongString: function (grip) {
- return this._longString(grip, "_pauseGrips");
- },
-
- /**
- * Return an instance of LongStringClient for the given long string grip that
- * is scoped to the thread lifetime.
- *
- * @param grip Object
- * The long string grip returned by the protocol.
- */
- threadLongString: function (grip) {
- return this._longString(grip, "_threadGrips");
- },
-
- /**
- * Get or create an ArrayBuffer client, checking the grip client cache if it
- * already exists.
- *
- * @param grip Object
- * The ArrayBuffer grip returned by the protocol.
- * @param gripCacheName String
- * The property name of the grip client cache to check for existing
- * clients in.
- */
- _arrayBuffer: function (grip, gripCacheName) {
- if (grip.actor in this[gripCacheName]) {
- return this[gripCacheName][grip.actor];
- }
-
- let client = new ArrayBufferClient(this.client, grip);
- this[gripCacheName][grip.actor] = client;
- return client;
- },
-
- /**
- * Return an instance of ArrayBufferClient for the given ArrayBuffer grip that
- * is scoped to the thread lifetime.
- *
- * @param grip Object
- * The ArrayBuffer grip returned by the protocol.
- */
- threadArrayBuffer: function (grip) {
- return this._arrayBuffer(grip, "_threadGrips");
- },
-
- /**
- * Clear and invalidate all the grip clients from the given cache.
- *
- * @param gripCacheName
- * The property name of the grip cache we want to clear.
- */
- _clearObjectClients: function (gripCacheName) {
- for (let id in this[gripCacheName]) {
- this[gripCacheName][id].valid = false;
- }
- this[gripCacheName] = {};
- },
-
- /**
- * Invalidate pause-lifetime grip clients and clear the list of current grip
- * clients.
- */
- _clearPauseGrips: function () {
- this._clearObjectClients("_pauseGrips");
- },
-
- /**
- * Invalidate thread-lifetime grip clients and clear the list of current grip
- * clients.
- */
- _clearThreadGrips: function () {
- this._clearObjectClients("_threadGrips");
- },
-
- /**
- * Handle thread state change by doing necessary cleanup and notifying all
- * registered listeners.
- */
- _onThreadState: function (packet) {
- this._state = ThreadStateTypes[packet.type];
- // The debugger UI may not be initialized yet so we want to keep
- // the packet around so it knows what to pause state to display
- // when it's initialized
- this._lastPausePacket = packet.type === "resumed" ? null : packet;
- this._clearFrames();
- this._clearPauseGrips();
- packet.type === ThreadStateTypes.detached && this._clearThreadGrips();
- this.client._eventsEnabled && this.emit(packet.type, packet);
- },
-
- getLastPausePacket: function () {
- return this._lastPausePacket;
- },
-
- /**
- * Return an EnvironmentClient instance for the given environment actor form.
- */
- environment: function (form) {
- return new EnvironmentClient(this.client, form);
- },
-
- /**
- * Return an instance of SourceClient for the given source actor form.
- */
- source: function (form) {
- if (form.actor in this._threadGrips) {
- return this._threadGrips[form.actor];
- }
-
- this._threadGrips[form.actor] = new SourceClient(this, form);
- return this._threadGrips[form.actor];
- },
-
- /**
- * Request the prototype and own properties of mutlipleObjects.
- *
- * @param onResponse function
- * Called with the request's response.
- * @param actors [string]
- * List of actor ID of the queried objects.
- */
- getPrototypesAndProperties: DebuggerClient.requester({
- type: "prototypesAndProperties",
- actors: arg(0)
- }),
-
- events: ["newSource"]
-};
-
-eventSource(ThreadClient.prototype);
-
-/**
- * Creates a tracing profiler client for the remote debugging protocol
- * server. This client is a front to the trace actor created on the
- * server side, hiding the protocol details in a traditional
- * JavaScript API.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param actor string
- * The actor ID for this thread.
- */
-function TraceClient(client, actor) {
- this._client = client;
- this._actor = actor;
- this._activeTraces = new Set();
- this._waitingPackets = new Map();
- this._expectedPacket = 0;
- this.request = this._client.request;
- this.events = [];
-}
-
-TraceClient.prototype = {
- get actor() {
- return this._actor;
- },
- get tracing() {
- return this._activeTraces.size > 0;
- },
-
- get _transport() {
- return this._client._transport;
- },
-
- /**
- * Detach from the trace actor.
- */
- detach: DebuggerClient.requester({
- type: "detach"
- }, {
- after: function (response) {
- this._client.unregisterClient(this);
- return response;
- },
- }),
-
- /**
- * Start a new trace.
- *
- * @param trace [string]
- * An array of trace types to be recorded by the new trace.
- *
- * @param name string
- * The name of the new trace.
- *
- * @param onResponse function
- * Called with the request's response.
- */
- startTrace: DebuggerClient.requester({
- type: "startTrace",
- name: arg(1),
- trace: arg(0)
- }, {
- after: function (response) {
- if (response.error) {
- return response;
- }
-
- if (!this.tracing) {
- this._waitingPackets.clear();
- this._expectedPacket = 0;
- }
- this._activeTraces.add(response.name);
-
- return response;
- },
- }),
-
- /**
- * End a trace. If a name is provided, stop the named
- * trace. Otherwise, stop the most recently started trace.
- *
- * @param name string
- * The name of the trace to stop.
- *
- * @param onResponse function
- * Called with the request's response.
- */
- stopTrace: DebuggerClient.requester({
- type: "stopTrace",
- name: arg(0)
- }, {
- after: function (response) {
- if (response.error) {
- return response;
- }
-
- this._activeTraces.delete(response.name);
-
- return response;
- },
- })
-};
-
-/**
- * Grip clients are used to retrieve information about the relevant object.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param grip object
- * A pause-lifetime object grip returned by the protocol.
- */
-function ObjectClient(client, grip) {
- this._grip = grip;
- this._client = client;
- this.request = this._client.request;
-}
-exports.ObjectClient = ObjectClient;
-
-ObjectClient.prototype = {
- get actor() {
- return this._grip.actor;
- },
- get _transport() {
- return this._client._transport;
- },
-
- valid: true,
-
- get isFrozen() {
- return this._grip.frozen;
- },
- get isSealed() {
- return this._grip.sealed;
- },
- get isExtensible() {
- return this._grip.extensible;
- },
-
- getDefinitionSite: DebuggerClient.requester({
- type: "definitionSite"
- }, {
- before: function (packet) {
- if (this._grip.class != "Function") {
- throw new Error("getDefinitionSite is only valid for function grips.");
- }
- return packet;
- }
- }),
-
- /**
- * Request the names of a function's formal parameters.
- *
- * @param onResponse function
- * Called with an object of the form:
- * { parameterNames:[<parameterName>, ...] }
- * where each <parameterName> is the name of a parameter.
- */
- getParameterNames: DebuggerClient.requester({
- type: "parameterNames"
- }, {
- before: function (packet) {
- if (this._grip.class !== "Function") {
- throw new Error("getParameterNames is only valid for function grips.");
- }
- return packet;
- },
- }),
-
- /**
- * Request the names of the properties defined on the object and not its
- * prototype.
- *
- * @param onResponse function Called with the request's response.
- */
- getOwnPropertyNames: DebuggerClient.requester({
- type: "ownPropertyNames"
- }),
-
- /**
- * Request the prototype and own properties of the object.
- *
- * @param onResponse function Called with the request's response.
- */
- getPrototypeAndProperties: DebuggerClient.requester({
- type: "prototypeAndProperties"
- }),
-
- /**
- * Request a PropertyIteratorClient instance to ease listing
- * properties for this object.
- *
- * @param options Object
- * A dictionary object with various boolean attributes:
- * - ignoreIndexedProperties Boolean
- * If true, filters out Array items.
- * e.g. properties names between `0` and `object.length`.
- * - ignoreNonIndexedProperties Boolean
- * If true, filters out items that aren't array items
- * e.g. properties names that are not a number between `0`
- * and `object.length`.
- * - sort Boolean
- * If true, the iterator will sort the properties by name
- * before dispatching them.
- * @param onResponse function Called with the client instance.
- */
- enumProperties: DebuggerClient.requester({
- type: "enumProperties",
- options: arg(0)
- }, {
- after: function (response) {
- if (response.iterator) {
- return { iterator: new PropertyIteratorClient(this._client, response.iterator) };
- }
- return response;
- },
- }),
-
- /**
- * Request a PropertyIteratorClient instance to enumerate entries in a
- * Map/Set-like object.
- *
- * @param onResponse function Called with the request's response.
- */
- enumEntries: DebuggerClient.requester({
- type: "enumEntries"
- }, {
- before: function (packet) {
- if (!["Map", "WeakMap", "Set", "WeakSet"].includes(this._grip.class)) {
- throw new Error("enumEntries is only valid for Map/Set-like grips.");
- }
- return packet;
- },
- after: function (response) {
- if (response.iterator) {
- return {
- iterator: new PropertyIteratorClient(this._client, response.iterator)
- };
- }
- return response;
- }
- }),
-
- /**
- * Request a SymbolIteratorClient instance to enumerate symbols in an object.
- *
- * @param onResponse function Called with the request's response.
- */
- enumSymbols: DebuggerClient.requester({
- type: "enumSymbols"
- }, {
- before: function (packet) {
- if (this._grip.type !== "object") {
- throw new Error("enumSymbols is only valid for objects grips.");
- }
- return packet;
- },
- after: function (response) {
- if (response.iterator) {
- return {
- iterator: new SymbolIteratorClient(this._client, response.iterator)
- };
- }
- return response;
- }
- }),
-
- /**
- * Request the property descriptor of the object's specified property.
- *
- * @param name string The name of the requested property.
- * @param onResponse function Called with the request's response.
- */
- getProperty: DebuggerClient.requester({
- type: "property",
- name: arg(0)
- }),
-
- /**
- * Request the prototype of the object.
- *
- * @param onResponse function Called with the request's response.
- */
- getPrototype: DebuggerClient.requester({
- type: "prototype"
- }),
-
- /**
- * Request the display string of the object.
- *
- * @param onResponse function Called with the request's response.
- */
- getDisplayString: DebuggerClient.requester({
- type: "displayString"
- }),
-
- /**
- * Request the scope of the object.
- *
- * @param onResponse function Called with the request's response.
- */
- getScope: DebuggerClient.requester({
- type: "scope"
- }, {
- before: function (packet) {
- if (this._grip.class !== "Function") {
- throw new Error("scope is only valid for function grips.");
- }
- return packet;
- },
- }),
-
- /**
- * Request the promises directly depending on the current promise.
- */
- getDependentPromises: DebuggerClient.requester({
- type: "dependentPromises"
- }, {
- before: function (packet) {
- if (this._grip.class !== "Promise") {
- throw new Error("getDependentPromises is only valid for promise " +
- "grips.");
- }
- return packet;
- }
- }),
-
- /**
- * Request the stack to the promise's allocation point.
- */
- getPromiseAllocationStack: DebuggerClient.requester({
- type: "allocationStack"
- }, {
- before: function (packet) {
- if (this._grip.class !== "Promise") {
- throw new Error("getAllocationStack is only valid for promise grips.");
- }
- return packet;
- }
- }),
-
- /**
- * Request the stack to the promise's fulfillment point.
- */
- getPromiseFulfillmentStack: DebuggerClient.requester({
- type: "fulfillmentStack"
- }, {
- before: function (packet) {
- if (this._grip.class !== "Promise") {
- throw new Error("getPromiseFulfillmentStack is only valid for " +
- "promise grips.");
- }
- return packet;
- }
- }),
-
- /**
- * Request the stack to the promise's rejection point.
- */
- getPromiseRejectionStack: DebuggerClient.requester({
- type: "rejectionStack"
- }, {
- before: function (packet) {
- if (this._grip.class !== "Promise") {
- throw new Error("getPromiseRejectionStack is only valid for " +
- "promise grips.");
- }
- return packet;
- }
- })
-};
-
-/**
- * A PropertyIteratorClient provides a way to access to property names and
- * values of an object efficiently, slice by slice.
- * Note that the properties can be sorted in the backend,
- * this is controled while creating the PropertyIteratorClient
- * from ObjectClient.enumProperties.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param grip Object
- * A PropertyIteratorActor grip returned by the protocol via
- * TabActor.enumProperties request.
- */
-function PropertyIteratorClient(client, grip) {
- this._grip = grip;
- this._client = client;
- this.request = this._client.request;
-}
-
-PropertyIteratorClient.prototype = {
- get actor() {
- return this._grip.actor;
- },
-
- /**
- * Get the total number of properties available in the iterator.
- */
- get count() {
- return this._grip.count;
- },
-
- /**
- * Get one or more property names that correspond to the positions in the
- * indexes parameter.
- *
- * @param indexes Array
- * An array of property indexes.
- * @param callback Function
- * The function called when we receive the property names.
- */
- names: DebuggerClient.requester({
- type: "names",
- indexes: arg(0)
- }, {}),
-
- /**
- * Get a set of following property value(s).
- *
- * @param start Number
- * The index of the first property to fetch.
- * @param count Number
- * The number of properties to fetch.
- * @param callback Function
- * The function called when we receive the property values.
- */
- slice: DebuggerClient.requester({
- type: "slice",
- start: arg(0),
- count: arg(1)
- }, {}),
-
- /**
- * Get all the property values.
- *
- * @param callback Function
- * The function called when we receive the property values.
- */
- all: DebuggerClient.requester({
- type: "all"
- }, {}),
-};
-
-/**
- * A SymbolIteratorClient provides a way to access to symbols
- * of an object efficiently, slice by slice.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param grip Object
- * A SymbolIteratorActor grip returned by the protocol via
- * TabActor.enumSymbols request.
- */
-function SymbolIteratorClient(client, grip) {
- this._grip = grip;
- this._client = client;
- this.request = this._client.request;
-}
-
-SymbolIteratorClient.prototype = {
- get actor() {
- return this._grip.actor;
- },
-
- /**
- * Get the total number of symbols available in the iterator.
- */
- get count() {
- return this._grip.count;
- },
-
- /**
- * Get a set of following symbols.
- *
- * @param start Number
- * The index of the first symbol to fetch.
- * @param count Number
- * The number of symbols to fetch.
- * @param callback Function
- * The function called when we receive the symbols.
- */
- slice: DebuggerClient.requester({
- type: "slice",
- start: arg(0),
- count: arg(1)
- }, {}),
-
- /**
- * Get all the symbols.
- *
- * @param callback Function
- * The function called when we receive the symbols.
- */
- all: DebuggerClient.requester({
- type: "all"
- }, {}),
-};
-
-/**
- * A ArrayBufferClient provides a way to access ArrayBuffer from the
- * debugger server.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param grip Object
- * A pause-lifetime ArrayBuffer grip returned by the protocol.
- */
-function ArrayBufferClient(client, grip) {
- this._grip = grip;
- this._client = client;
- this.request = this._client.request;
-}
-ArrayBufferClient.prototype = {
- get actor() {
- return this._grip.actor;
- },
- get length() {
- return this._grip.length;
- },
- get _transport() {
- return this._client._transport;
- },
-
- valid: true,
-
- slice: DebuggerClient.requester({
- type: "slice",
- start: arg(0),
- count: arg(1)
- }),
-};
-
-/**
- * A LongStringClient provides a way to access "very long" strings from the
- * debugger server.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param grip Object
- * A pause-lifetime long string grip returned by the protocol.
- */
-function LongStringClient(client, grip) {
- this._grip = grip;
- this._client = client;
- this.request = this._client.request;
-}
-exports.LongStringClient = LongStringClient;
-
-LongStringClient.prototype = {
- get actor() {
- return this._grip.actor;
- },
- get length() {
- return this._grip.length;
- },
- get initial() {
- return this._grip.initial;
- },
- get _transport() {
- return this._client._transport;
- },
-
- valid: true,
-
- /**
- * Get the substring of this LongString from start to end.
- *
- * @param start Number
- * The starting index.
- * @param end Number
- * The ending index.
- * @param callback Function
- * The function called when we receive the substring.
- */
- substring: DebuggerClient.requester({
- type: "substring",
- start: arg(0),
- end: arg(1)
- }),
-};
-
-/**
- * A SourceClient provides a way to access the source text of a script.
- *
- * @param client ThreadClient
- * The thread client parent.
- * @param form Object
- * The form sent across the remote debugging protocol.
- */
-function SourceClient(client, form) {
- this._form = form;
- this._isBlackBoxed = form.isBlackBoxed;
- this._isPrettyPrinted = form.isPrettyPrinted;
- this._activeThread = client;
- this._client = client.client;
-}
-
-SourceClient.prototype = {
- get _transport() {
- return this._client._transport;
- },
- get isBlackBoxed() {
- return this._isBlackBoxed;
- },
- get isPrettyPrinted() {
- return this._isPrettyPrinted;
- },
- get actor() {
- return this._form.actor;
- },
- get request() {
- return this._client.request;
- },
- get url() {
- return this._form.url;
- },
-
- /**
- * Black box this SourceClient's source.
- *
- * @param callback Function
- * The callback function called when we receive the response from the server.
- */
- blackBox: DebuggerClient.requester({
- type: "blackbox"
- }, {
- after: function (response) {
- if (!response.error) {
- this._isBlackBoxed = true;
- if (this._activeThread) {
- this._activeThread.emit("blackboxchange", this);
- }
- }
- return response;
- }
- }),
-
- /**
- * Un-black box this SourceClient's source.
- *
- * @param callback Function
- * The callback function called when we receive the response from the server.
- */
- unblackBox: DebuggerClient.requester({
- type: "unblackbox"
- }, {
- after: function (response) {
- if (!response.error) {
- this._isBlackBoxed = false;
- if (this._activeThread) {
- this._activeThread.emit("blackboxchange", this);
- }
- }
- return response;
- }
- }),
-
- /**
- * Get Executable Lines from a source
- *
- * @param callback Function
- * The callback function called when we receive the response from the server.
- */
- getExecutableLines: function (cb = noop) {
- let packet = {
- to: this._form.actor,
- type: "getExecutableLines"
- };
-
- return this._client.request(packet).then(res => {
- cb(res.lines);
- return res.lines;
- });
- },
-
- /**
- * Get a long string grip for this SourceClient's source.
- */
- source: function (callback = noop) {
- let packet = {
- to: this._form.actor,
- type: "source"
- };
- return this._client.request(packet).then(response => {
- return this._onSourceResponse(response, callback);
- });
- },
-
- /**
- * Pretty print this source's text.
- */
- prettyPrint: function (indent, callback = noop) {
- const packet = {
- to: this._form.actor,
- type: "prettyPrint",
- indent
- };
- return this._client.request(packet).then(response => {
- if (!response.error) {
- this._isPrettyPrinted = true;
- this._activeThread._clearFrames();
- this._activeThread.emit("prettyprintchange", this);
- }
- return this._onSourceResponse(response, callback);
- });
- },
-
- /**
- * Stop pretty printing this source's text.
- */
- disablePrettyPrint: function (callback = noop) {
- const packet = {
- to: this._form.actor,
- type: "disablePrettyPrint"
- };
- return this._client.request(packet).then(response => {
- if (!response.error) {
- this._isPrettyPrinted = false;
- this._activeThread._clearFrames();
- this._activeThread.emit("prettyprintchange", this);
- }
- return this._onSourceResponse(response, callback);
- });
- },
-
- _onSourceResponse: function (response, callback) {
- if (response.error) {
- callback(response);
- return response;
- }
-
- if (typeof response.source === "string") {
- callback(response);
- return response;
- }
-
- let { contentType, source } = response;
- if (source.type === "arrayBuffer") {
- let arrayBuffer = this._activeThread.threadArrayBuffer(source);
- return arrayBuffer.slice(0, arrayBuffer.length).then(function (resp) {
- if (resp.error) {
- callback(resp);
- return resp;
- }
- // Keeping str as a string, ArrayBuffer/Uint8Array will not survive
- // setIn/mergeIn operations.
- const str = atob(resp.encoded);
- let newResponse = {
- source: {
- binary: str,
- toString: () => "[wasm]",
- },
- contentType,
- };
- callback(newResponse);
- return newResponse;
- });
- }
-
- let longString = this._activeThread.threadLongString(source);
- return longString.substring(0, longString.length).then(function (resp) {
- if (resp.error) {
- callback(resp);
- return resp;
- }
-
- let newResponse = {
- source: resp.substring,
- contentType: contentType
- };
- callback(newResponse);
- return newResponse;
- });
- },
-
- /**
- * Request to set a breakpoint in the specified location.
- *
- * @param object location
- * The location and condition of the breakpoint in
- * the form of { line[, column, condition] }.
- * @param function onResponse
- * Called with the thread's response.
- */
- setBreakpoint: function ({ line, column, condition, noSliding }, onResponse = noop) {
- // A helper function that sets the breakpoint.
- let doSetBreakpoint = callback => {
- let root = this._client.mainRoot;
- let location = {
- line,
- column,
- };
-
- let packet = {
- to: this.actor,
- type: "setBreakpoint",
- location,
- condition,
- noSliding,
- };
-
- // Backwards compatibility: send the breakpoint request to the
- // thread if the server doesn't support Debugger.Source actors.
- if (!root.traits.debuggerSourceActors) {
- packet.to = this._activeThread.actor;
- packet.location.url = this.url;
- }
-
- return this._client.request(packet).then(response => {
- // Ignoring errors, since the user may be setting a breakpoint in a
- // dead script that will reappear on a page reload.
- let bpClient;
- if (response.actor) {
- bpClient = new BreakpointClient(
- this._client,
- this,
- response.actor,
- location,
- root.traits.conditionalBreakpoints ? condition : undefined
- );
- }
- onResponse(response, bpClient);
- if (callback) {
- callback();
- }
- return [response, bpClient];
- });
- };
-
- // If the debuggee is paused, just set the breakpoint.
- if (this._activeThread.paused) {
- return doSetBreakpoint();
- }
- // Otherwise, force a pause in order to set the breakpoint.
- return this._activeThread.interrupt().then(response => {
- if (response.error) {
- // Can't set the breakpoint if pausing failed.
- onResponse(response);
- return response;
- }
-
- const { type, why } = response;
- const cleanUp = type == "paused" && why.type == "interrupted"
- ? () => this._activeThread.resume()
- : noop;
-
- return doSetBreakpoint(cleanUp);
- });
- }
-};
-
-/**
- * Breakpoint clients are used to remove breakpoints that are no longer used.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param sourceClient SourceClient
- * The source where this breakpoint exists
- * @param actor string
- * The actor ID for this breakpoint.
- * @param location object
- * The location of the breakpoint. This is an object with two properties:
- * url and line.
- * @param condition string
- * The conditional expression of the breakpoint
- */
-function BreakpointClient(client, sourceClient, actor, location, condition) {
- this._client = client;
- this._actor = actor;
- this.location = location;
- this.location.actor = sourceClient.actor;
- this.location.url = sourceClient.url;
- this.source = sourceClient;
- this.request = this._client.request;
-
- // The condition property should only exist if it's a truthy value
- if (condition) {
- this.condition = condition;
- }
-}
-
-BreakpointClient.prototype = {
-
- _actor: null,
- get actor() {
- return this._actor;
- },
- get _transport() {
- return this._client._transport;
- },
-
- /**
- * Remove the breakpoint from the server.
- */
- remove: DebuggerClient.requester({
- type: "delete"
- }),
-
- /**
- * Determines if this breakpoint has a condition
- */
- hasCondition: function () {
- let root = this._client.mainRoot;
- // XXX bug 990137: We will remove support for client-side handling of
- // conditional breakpoints
- if (root.traits.conditionalBreakpoints) {
- return "condition" in this;
- }
- return "conditionalExpression" in this;
- },
-
- /**
- * Get the condition of this breakpoint. Currently we have to
- * support locally emulated conditional breakpoints until the
- * debugger servers are updated (see bug 990137). We used a
- * different property when moving it server-side to ensure that we
- * are testing the right code.
- */
- getCondition: function () {
- let root = this._client.mainRoot;
- if (root.traits.conditionalBreakpoints) {
- return this.condition;
- }
- return this.conditionalExpression;
- },
-
- /**
- * Set the condition of this breakpoint
- */
- setCondition: function (gThreadClient, condition) {
- let root = this._client.mainRoot;
- let deferred = promise.defer();
-
- if (root.traits.conditionalBreakpoints) {
- let info = {
- line: this.location.line,
- column: this.location.column,
- condition: condition
- };
-
- // Remove the current breakpoint and add a new one with the
- // condition.
- this.remove(response => {
- if (response && response.error) {
- deferred.reject(response);
- return;
- }
-
- this.source.setBreakpoint(info, (resp, newBreakpoint) => {
- if (resp && resp.error) {
- deferred.reject(resp);
- } else {
- deferred.resolve(newBreakpoint);
- }
- });
- });
- } else {
- // The property shouldn't even exist if the condition is blank
- if (condition === "") {
- delete this.conditionalExpression;
- } else {
- this.conditionalExpression = condition;
- }
- deferred.resolve(this);
- }
-
- return deferred.promise;
- }
-};
-
-eventSource(BreakpointClient.prototype);
-
-/**
- * Environment clients are used to manipulate the lexical environment actors.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param form Object
- * The form sent across the remote debugging protocol.
- */
-function EnvironmentClient(client, form) {
- this._client = client;
- this._form = form;
- this.request = this._client.request;
-}
-exports.EnvironmentClient = EnvironmentClient;
-
-EnvironmentClient.prototype = {
-
- get actor() {
- return this._form.actor;
- },
- get _transport() {
- return this._client._transport;
- },
-
- /**
- * Fetches the bindings introduced by this lexical environment.
- */
- getBindings: DebuggerClient.requester({
- type: "bindings"
- }),
-
- /**
- * Changes the value of the identifier whose name is name (a string) to that
- * represented by value (a grip).
- */
- assign: DebuggerClient.requester({
- type: "assign",
- name: arg(0),
- value: arg(1)
- })
-};
-
-eventSource(EnvironmentClient.prototype);
copy from devtools/shared/client/main.js
copy to devtools/shared/client/debugger-client.js
--- a/devtools/shared/client/main.js
+++ b/devtools/shared/client/debugger-client.js
@@ -1,212 +1,45 @@
-/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
-/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
-const { Ci, Cu } = require("chrome");
+const { Cu } = require("chrome");
+const promise = Cu.import("resource://devtools/shared/deprecated-sync-thenables.js", {}).Promise;
+
const DevToolsUtils = require("devtools/shared/DevToolsUtils");
const { getStack, callFunctionWithAsyncStack } = require("devtools/shared/platform/stack");
+const eventSource = require("./event-source");
+const {
+ ThreadStateTypes,
+ UnsolicitedNotifications,
+ UnsolicitedPauses,
+} = require("./constants");
-const promise = Cu.import("resource://devtools/shared/deprecated-sync-thenables.js", {}).Promise;
-
+loader.lazyRequireGetter(this, "Authentication", "devtools/shared/security/auth");
+loader.lazyRequireGetter(this, "DebuggerSocket", "devtools/shared/security/socket", true);
loader.lazyRequireGetter(this, "EventEmitter", "devtools/shared/event-emitter");
loader.lazyRequireGetter(this, "WebConsoleClient", "devtools/shared/webconsole/client", true);
-loader.lazyRequireGetter(this, "DebuggerSocket", "devtools/shared/security/socket", true);
-loader.lazyRequireGetter(this, "Authentication", "devtools/shared/security/auth");
+loader.lazyRequireGetter(this, "AddonClient", "devtools/shared/client/addon-client");
+loader.lazyRequireGetter(this, "RootClient", "devtools/shared/client/root-client");
+loader.lazyRequireGetter(this, "TabClient", "devtools/shared/client/tab-client");
+loader.lazyRequireGetter(this, "ThreadClient", "devtools/shared/client/thread-client");
+loader.lazyRequireGetter(this, "TraceClient", "devtools/shared/client/trace-client");
+loader.lazyRequireGetter(this, "WorkerClient", "devtools/shared/client/worker-client");
const noop = () => {};
/**
- * TODO: Get rid of this API in favor of EventTarget (bug 1042642)
- *
- * Add simple event notification to a prototype object. Any object that has
- * some use for event notifications or the observer pattern in general can be
- * augmented with the necessary facilities by passing its prototype to this
- * function.
- *
- * @param proto object
- * The prototype object that will be modified.
- */
-function eventSource(proto) {
- /**
- * Add a listener to the event source for a given event.
- *
- * @param name string
- * The event to listen for.
- * @param listener function
- * Called when the event is fired. If the same listener
- * is added more than once, it will be called once per
- * addListener call.
- */
- proto.addListener = function (name, listener) {
- if (typeof listener != "function") {
- throw TypeError("Listeners must be functions.");
- }
-
- if (!this._listeners) {
- this._listeners = {};
- }
-
- this._getListeners(name).push(listener);
- };
-
- /**
- * Add a listener to the event source for a given event. The
- * listener will be removed after it is called for the first time.
- *
- * @param name string
- * The event to listen for.
- * @param listener function
- * Called when the event is fired.
- */
- proto.addOneTimeListener = function (name, listener) {
- let l = (...args) => {
- this.removeListener(name, l);
- listener.apply(null, args);
- };
- this.addListener(name, l);
- };
-
- /**
- * Remove a listener from the event source previously added with
- * addListener().
- *
- * @param name string
- * The event name used during addListener to add the listener.
- * @param listener function
- * The callback to remove. If addListener was called multiple
- * times, all instances will be removed.
- */
- proto.removeListener = function (name, listener) {
- if (!this._listeners || (listener && !this._listeners[name])) {
- return;
- }
-
- if (!listener) {
- this._listeners[name] = [];
- } else {
- this._listeners[name] =
- this._listeners[name].filter(l => l != listener);
- }
- };
-
- /**
- * Returns the listeners for the specified event name. If none are defined it
- * initializes an empty list and returns that.
- *
- * @param name string
- * The event name.
- */
- proto._getListeners = function (name) {
- if (name in this._listeners) {
- return this._listeners[name];
- }
- this._listeners[name] = [];
- return this._listeners[name];
- };
-
- /**
- * Notify listeners of an event.
- *
- * @param name string
- * The event to fire.
- * @param arguments
- * All arguments will be passed along to the listeners,
- * including the name argument.
- */
- proto.emit = function () {
- if (!this._listeners) {
- return;
- }
-
- let name = arguments[0];
- let listeners = this._getListeners(name).slice(0);
-
- for (let listener of listeners) {
- try {
- listener.apply(null, arguments);
- } catch (e) {
- // Prevent a bad listener from interfering with the others.
- DevToolsUtils.reportException("notify event '" + name + "'", e);
- }
- }
- };
-}
-
-/**
- * Set of protocol messages that affect thread state, and the
- * state the actor is in after each message.
- */
-const ThreadStateTypes = {
- "paused": "paused",
- "resumed": "attached",
- "detached": "detached",
- "running": "attached"
-};
-
-/**
- * Set of protocol messages that are sent by the server without a prior request
- * by the client.
- */
-const UnsolicitedNotifications = {
- "consoleAPICall": "consoleAPICall",
- "eventNotification": "eventNotification",
- "fileActivity": "fileActivity",
- "lastPrivateContextExited": "lastPrivateContextExited",
- "logMessage": "logMessage",
- "networkEvent": "networkEvent",
- "networkEventUpdate": "networkEventUpdate",
- "newGlobal": "newGlobal",
- "newScript": "newScript",
- "tabDetached": "tabDetached",
- "tabListChanged": "tabListChanged",
- "reflowActivity": "reflowActivity",
- "addonListChanged": "addonListChanged",
- "workerListChanged": "workerListChanged",
- "serviceWorkerRegistrationListChanged": "serviceWorkerRegistrationList",
- "tabNavigated": "tabNavigated",
- "frameUpdate": "frameUpdate",
- "pageError": "pageError",
- "documentLoad": "documentLoad",
- "enteredFrame": "enteredFrame",
- "exitedFrame": "exitedFrame",
- "appOpen": "appOpen",
- "appClose": "appClose",
- "appInstall": "appInstall",
- "appUninstall": "appUninstall",
- "evaluationResult": "evaluationResult",
- "newSource": "newSource",
- "updatedSource": "updatedSource",
- "inspectObject": "inspectObject"
-};
-
-/**
- * Set of pause types that are sent by the server and not as an immediate
- * response to a client request.
- */
-const UnsolicitedPauses = {
- "resumeLimit": "resumeLimit",
- "debuggerStatement": "debuggerStatement",
- "breakpoint": "breakpoint",
- "DOMEvent": "DOMEvent",
- "watchpoint": "watchpoint",
- "exception": "exception"
-};
-
-/**
* Creates a client for the remote debugging protocol server. This client
* provides the means to communicate with the server and exchange the messages
* required by the protocol in a traditional JavaScript API.
*/
-const DebuggerClient = exports.DebuggerClient = function (transport) {
+function DebuggerClient(transport) {
this._transport = transport;
this._transport.hooks = this;
// Map actor ID to client instance for each actor type.
this._clients = new Map();
this._pendingRequests = new Map();
this._activeRequests = new Map();
@@ -221,17 +54,17 @@ const DebuggerClient = exports.DebuggerC
* As the first thing on the connection, expect a greeting packet from
* the connection's root actor.
*/
this.mainRoot = null;
this.expectReply("root", (packet) => {
this.mainRoot = new RootClient(this, packet);
this.emit("connected", packet.applicationType, packet.traits);
});
-};
+}
/**
* A declarative helper for defining methods that send requests to the server.
*
* @param packetSkeleton
* The form of the packet to send. Can specify fields to be filled from
* the parameters by using the |arg| function.
* @param before
@@ -286,16 +119,17 @@ DebuggerClient.requester = function (pac
return response;
}, "DebuggerClient.requester request callback"));
}, "DebuggerClient.requester");
};
function arg(pos) {
return new DebuggerClient.Argument(pos);
}
+exports.arg = arg;
DebuggerClient.Argument = function (position) {
this.position = position;
};
DebuggerClient.Argument.prototype.getArgument = function (params) {
if (!(this.position in params)) {
throw new Error("Bad index into params: " + this.position);
@@ -1316,2111 +1150,12 @@ class Request extends EventEmitter {
this.request = request;
}
get actor() {
return this.request.to || this.request.actor;
}
}
-/**
- * Creates a tab client for the remote debugging protocol server. This client
- * is a front to the tab actor created in the server side, hiding the protocol
- * details in a traditional JavaScript API.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param form object
- * The protocol form for this tab.
- */
-function TabClient(client, form) {
- this.client = client;
- this._actor = form.from;
- this._threadActor = form.threadActor;
- this.javascriptEnabled = form.javascriptEnabled;
- this.cacheDisabled = form.cacheDisabled;
- this.thread = null;
- this.request = this.client.request;
- this.traits = form.traits || {};
- this.events = ["workerListChanged"];
-}
-
-TabClient.prototype = {
- get actor() {
- return this._actor;
- },
- get _transport() {
- return this.client._transport;
- },
-
- /**
- * Attach to a thread actor.
- *
- * @param object options
- * Configuration options.
- * - useSourceMaps: whether to use source maps or not.
- * @param function onResponse
- * Called with the response packet and a ThreadClient
- * (which will be undefined on error).
- */
- attachThread: function (options = {}, onResponse = noop) {
- if (this.thread) {
- DevToolsUtils.executeSoon(() => onResponse({}, this.thread));
- return promise.resolve([{}, this.thread]);
- }
-
- let packet = {
- to: this._threadActor,
- type: "attach",
- options,
- };
- return this.request(packet).then(response => {
- if (!response.error) {
- this.thread = new ThreadClient(this, this._threadActor);
- this.client.registerClient(this.thread);
- }
- onResponse(response, this.thread);
- return [response, this.thread];
- });
- },
-
- /**
- * Detach the client from the tab actor.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- detach: DebuggerClient.requester({
- type: "detach"
- }, {
- before: function (packet) {
- if (this.thread) {
- this.thread.detach();
- }
- return packet;
- },
- after: function (response) {
- this.client.unregisterClient(this);
- return response;
- },
- }),
-
- /**
- * Bring the window to the front.
- */
- focus: DebuggerClient.requester({
- type: "focus"
- }, {}),
-
- /**
- * Reload the page in this tab.
- *
- * @param [optional] object options
- * An object with a `force` property indicating whether or not
- * this reload should skip the cache
- */
- reload: function (options = { force: false }) {
- return this._reload(options);
- },
- _reload: DebuggerClient.requester({
- type: "reload",
- options: arg(0)
- }),
-
- /**
- * Navigate to another URL.
- *
- * @param string url
- * The URL to navigate to.
- */
- navigateTo: DebuggerClient.requester({
- type: "navigateTo",
- url: arg(0)
- }),
-
- /**
- * Reconfigure the tab actor.
- *
- * @param object options
- * A dictionary object of the new options to use in the tab actor.
- * @param function onResponse
- * Called with the response packet.
- */
- reconfigure: DebuggerClient.requester({
- type: "reconfigure",
- options: arg(0)
- }),
-
- listWorkers: DebuggerClient.requester({
- type: "listWorkers"
- }),
-
- attachWorker: function (workerActor, onResponse) {
- return this.client.attachWorker(workerActor, onResponse);
- },
-};
-
-eventSource(TabClient.prototype);
-
-function WorkerClient(client, form) {
- this.client = client;
- this._actor = form.from;
- this._isClosed = false;
- this._url = form.url;
-
- this._onClose = this._onClose.bind(this);
-
- this.addListener("close", this._onClose);
-
- this.traits = {};
-}
-
-WorkerClient.prototype = {
- get _transport() {
- return this.client._transport;
- },
-
- get request() {
- return this.client.request;
- },
-
- get actor() {
- return this._actor;
- },
-
- get url() {
- return this._url;
- },
-
- get isClosed() {
- return this._isClosed;
- },
-
- detach: DebuggerClient.requester({ type: "detach" }, {
- after: function (response) {
- if (this.thread) {
- this.client.unregisterClient(this.thread);
- }
- this.client.unregisterClient(this);
- return response;
- },
- }),
-
- attachThread: function (options = {}, onResponse = noop) {
- if (this.thread) {
- let response = [{
- type: "connected",
- threadActor: this.thread._actor,
- consoleActor: this.consoleActor,
- }, this.thread];
- DevToolsUtils.executeSoon(() => onResponse(response));
- return response;
- }
-
- // The connect call on server doesn't attach the thread as of version 44.
- return this.request({
- to: this._actor,
- type: "connect",
- options,
- }).then(connectResponse => {
- if (connectResponse.error) {
- onResponse(connectResponse, null);
- return [connectResponse, null];
- }
-
- return this.request({
- to: connectResponse.threadActor,
- type: "attach",
- options,
- }).then(attachResponse => {
- if (attachResponse.error) {
- onResponse(attachResponse, null);
- }
-
- this.thread = new ThreadClient(this, connectResponse.threadActor);
- this.consoleActor = connectResponse.consoleActor;
- this.client.registerClient(this.thread);
-
- onResponse(connectResponse, this.thread);
- return [connectResponse, this.thread];
- });
- }, error => {
- onResponse(error, null);
- });
- },
-
- _onClose: function () {
- this.removeListener("close", this._onClose);
-
- if (this.thread) {
- this.client.unregisterClient(this.thread);
- }
- this.client.unregisterClient(this);
- this._isClosed = true;
- },
-
- reconfigure: function () {
- return Promise.resolve();
- },
-
- events: ["close"]
-};
-
-eventSource(WorkerClient.prototype);
-
-function AddonClient(client, actor) {
- this._client = client;
- this._actor = actor;
- this.request = this._client.request;
- this.events = [];
-}
-
-AddonClient.prototype = {
- get actor() {
- return this._actor;
- },
- get _transport() {
- return this._client._transport;
- },
-
- /**
- * Detach the client from the addon actor.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- detach: DebuggerClient.requester({
- type: "detach"
- }, {
- after: function (response) {
- if (this._client.activeAddon === this) {
- this._client.activeAddon = null;
- }
- this._client.unregisterClient(this);
- return response;
- },
- })
-};
-
-/**
- * A RootClient object represents a root actor on the server. Each
- * DebuggerClient keeps a RootClient instance representing the root actor
- * for the initial connection; DebuggerClient's 'listTabs' and
- * 'listChildProcesses' methods forward to that root actor.
- *
- * @param client object
- * The client connection to which this actor belongs.
- * @param greeting string
- * The greeting packet from the root actor we're to represent.
- *
- * Properties of a RootClient instance:
- *
- * @property actor string
- * The name of this child's root actor.
- * @property applicationType string
- * The application type, as given in the root actor's greeting packet.
- * @property traits object
- * The traits object, as given in the root actor's greeting packet.
- */
-function RootClient(client, greeting) {
- this._client = client;
- this.actor = greeting.from;
- this.applicationType = greeting.applicationType;
- this.traits = greeting.traits;
-}
-exports.RootClient = RootClient;
-
-RootClient.prototype = {
- constructor: RootClient,
-
- /**
- * Gets the "root" form, which lists all the global actors that affect the entire
- * browser. This can replace usages of `listTabs` that only wanted the global actors
- * and didn't actually care about tabs.
- */
- getRoot: DebuggerClient.requester({ type: "getRoot" }),
-
- /**
- * List the open tabs.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- listTabs: DebuggerClient.requester({ type: "listTabs" }),
-
- /**
- * List the installed addons.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- listAddons: DebuggerClient.requester({ type: "listAddons" }),
-
- /**
- * List the registered workers.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- listWorkers: DebuggerClient.requester({ type: "listWorkers" }),
-
- /**
- * List the registered service workers.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- listServiceWorkerRegistrations: DebuggerClient.requester({
- type: "listServiceWorkerRegistrations"
- }),
-
- /**
- * List the running processes.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- listProcesses: DebuggerClient.requester({ type: "listProcesses" }),
-
- /**
- * Fetch the TabActor for the currently selected tab, or for a specific
- * tab given as first parameter.
- *
- * @param [optional] object filter
- * A dictionary object with following optional attributes:
- * - outerWindowID: used to match tabs in parent process
- * - tabId: used to match tabs in child processes
- * - tab: a reference to xul:tab element
- * If nothing is specified, returns the actor for the currently
- * selected tab.
- */
- getTab: function (filter) {
- let packet = {
- to: this.actor,
- type: "getTab"
- };
-
- if (filter) {
- if (typeof (filter.outerWindowID) == "number") {
- packet.outerWindowID = filter.outerWindowID;
- } else if (typeof (filter.tabId) == "number") {
- packet.tabId = filter.tabId;
- } else if ("tab" in filter) {
- let browser = filter.tab.linkedBrowser;
- if (browser.frameLoader.tabParent) {
- // Tabs in child process
- packet.tabId = browser.frameLoader.tabParent.tabId;
- } else if (browser.outerWindowID) {
- // <xul:browser> tabs in parent process
- packet.outerWindowID = browser.outerWindowID;
- } else {
- // <iframe mozbrowser> tabs in parent process
- let windowUtils = browser.contentWindow
- .QueryInterface(Ci.nsIInterfaceRequestor)
- .getInterface(Ci.nsIDOMWindowUtils);
- packet.outerWindowID = windowUtils.outerWindowID;
- }
- } else {
- // Throw if a filter object have been passed but without
- // any clearly idenfified filter.
- throw new Error("Unsupported argument given to getTab request");
- }
- }
-
- return this.request(packet);
- },
-
- /**
- * Fetch the WindowActor for a specific window, like a browser window in
- * Firefox, but it can be used to reach any window in the process.
- *
- * @param number outerWindowID
- * The outerWindowID of the top level window you are looking for.
- */
- getWindow: function ({ outerWindowID }) {
- if (!outerWindowID) {
- throw new Error("Must specify outerWindowID");
- }
-
- let packet = {
- to: this.actor,
- type: "getWindow",
- outerWindowID,
- };
-
- return this.request(packet);
- },
-
- /**
- * Description of protocol's actors and methods.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- protocolDescription: DebuggerClient.requester({ type: "protocolDescription" }),
-
- /*
- * Methods constructed by DebuggerClient.requester require these forwards
- * on their 'this'.
- */
- get _transport() {
- return this._client._transport;
- },
- get request() {
- return this._client.request;
- }
+module.exports = {
+ arg,
+ DebuggerClient,
};
-
-/**
- * Creates a thread client for the remote debugging protocol server. This client
- * is a front to the thread actor created in the server side, hiding the
- * protocol details in a traditional JavaScript API.
- *
- * @param client DebuggerClient|TabClient
- * The parent of the thread (tab for tab-scoped debuggers, DebuggerClient
- * for chrome debuggers).
- * @param actor string
- * The actor ID for this thread.
- */
-function ThreadClient(client, actor) {
- this._parent = client;
- this.client = client instanceof DebuggerClient ? client : client.client;
- this._actor = actor;
- this._frameCache = [];
- this._scriptCache = {};
- this._pauseGrips = {};
- this._threadGrips = {};
- this.request = this.client.request;
-}
-
-ThreadClient.prototype = {
- _state: "paused",
- get state() {
- return this._state;
- },
- get paused() {
- return this._state === "paused";
- },
-
- _pauseOnExceptions: false,
- _ignoreCaughtExceptions: false,
- _pauseOnDOMEvents: null,
-
- _actor: null,
- get actor() {
- return this._actor;
- },
-
- get _transport() {
- return this.client._transport;
- },
-
- _assertPaused: function (command) {
- if (!this.paused) {
- throw Error(command + " command sent while not paused. Currently " + this._state);
- }
- },
-
- /**
- * Resume a paused thread. If the optional limit parameter is present, then
- * the thread will also pause when that limit is reached.
- *
- * @param [optional] object limit
- * An object with a type property set to the appropriate limit (next,
- * step, or finish) per the remote debugging protocol specification.
- * Use null to specify no limit.
- * @param function onResponse
- * Called with the response packet.
- */
- _doResume: DebuggerClient.requester({
- type: "resume",
- resumeLimit: arg(0)
- }, {
- before: function (packet) {
- this._assertPaused("resume");
-
- // Put the client in a tentative "resuming" state so we can prevent
- // further requests that should only be sent in the paused state.
- this._previousState = this._state;
- this._state = "resuming";
-
- if (this._pauseOnExceptions) {
- packet.pauseOnExceptions = this._pauseOnExceptions;
- }
- if (this._ignoreCaughtExceptions) {
- packet.ignoreCaughtExceptions = this._ignoreCaughtExceptions;
- }
- if (this._pauseOnDOMEvents) {
- packet.pauseOnDOMEvents = this._pauseOnDOMEvents;
- }
- return packet;
- },
- after: function (response) {
- if (response.error && this._state == "resuming") {
- // There was an error resuming, update the state to the new one
- // reported by the server, if given (only on wrongState), otherwise
- // reset back to the previous state.
- if (response.state) {
- this._state = ThreadStateTypes[response.state];
- } else {
- this._state = this._previousState;
- }
- }
- delete this._previousState;
- return response;
- },
- }),
-
- /**
- * Reconfigure the thread actor.
- *
- * @param object options
- * A dictionary object of the new options to use in the thread actor.
- * @param function onResponse
- * Called with the response packet.
- */
- reconfigure: DebuggerClient.requester({
- type: "reconfigure",
- options: arg(0)
- }),
-
- /**
- * Resume a paused thread.
- */
- resume: function (onResponse) {
- return this._doResume(null, onResponse);
- },
-
- /**
- * Resume then pause without stepping.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- resumeThenPause: function (onResponse) {
- return this._doResume({ type: "break" }, onResponse);
- },
-
- /**
- * Step over a function call.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- stepOver: function (onResponse) {
- return this._doResume({ type: "next" }, onResponse);
- },
-
- /**
- * Step into a function call.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- stepIn: function (onResponse) {
- return this._doResume({ type: "step" }, onResponse);
- },
-
- /**
- * Step out of a function call.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- stepOut: function (onResponse) {
- return this._doResume({ type: "finish" }, onResponse);
- },
-
- /**
- * Immediately interrupt a running thread.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- interrupt: function (onResponse) {
- return this._doInterrupt(null, onResponse);
- },
-
- /**
- * Pause execution right before the next JavaScript bytecode is executed.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- breakOnNext: function (onResponse) {
- return this._doInterrupt("onNext", onResponse);
- },
-
- /**
- * Interrupt a running thread.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- _doInterrupt: DebuggerClient.requester({
- type: "interrupt",
- when: arg(0)
- }),
-
- /**
- * Enable or disable pausing when an exception is thrown.
- *
- * @param boolean pauseOnExceptions
- * Enables pausing if true, disables otherwise.
- * @param boolean ignoreCaughtExceptions
- * Whether to ignore caught exceptions
- * @param function onResponse
- * Called with the response packet.
- */
- pauseOnExceptions: function (pauseOnExceptions,
- ignoreCaughtExceptions,
- onResponse = noop) {
- this._pauseOnExceptions = pauseOnExceptions;
- this._ignoreCaughtExceptions = ignoreCaughtExceptions;
-
- // Otherwise send the flag using a standard resume request.
- if (!this.paused) {
- return this.interrupt(response => {
- if (response.error) {
- // Can't continue if pausing failed.
- onResponse(response);
- return response;
- }
- return this.resume(onResponse);
- });
- }
-
- onResponse();
- return promise.resolve();
- },
-
- /**
- * Enable pausing when the specified DOM events are triggered. Disabling
- * pausing on an event can be realized by calling this method with the updated
- * array of events that doesn't contain it.
- *
- * @param array|string events
- * An array of strings, representing the DOM event types to pause on,
- * or "*" to pause on all DOM events. Pass an empty array to
- * completely disable pausing on DOM events.
- * @param function onResponse
- * Called with the response packet in a future turn of the event loop.
- */
- pauseOnDOMEvents: function (events, onResponse = noop) {
- this._pauseOnDOMEvents = events;
- // If the debuggee is paused, the value of the array will be communicated in
- // the next resumption. Otherwise we have to force a pause in order to send
- // the array.
- if (this.paused) {
- DevToolsUtils.executeSoon(() => onResponse({}));
- return {};
- }
- return this.interrupt(response => {
- // Can't continue if pausing failed.
- if (response.error) {
- onResponse(response);
- return response;
- }
- return this.resume(onResponse);
- });
- },
-
- /**
- * Send a clientEvaluate packet to the debuggee. Response
- * will be a resume packet.
- *
- * @param string frame
- * The actor ID of the frame where the evaluation should take place.
- * @param string expression
- * The expression that will be evaluated in the scope of the frame
- * above.
- * @param function onResponse
- * Called with the response packet.
- */
- eval: DebuggerClient.requester({
- type: "clientEvaluate",
- frame: arg(0),
- expression: arg(1)
- }, {
- before: function (packet) {
- this._assertPaused("eval");
- // Put the client in a tentative "resuming" state so we can prevent
- // further requests that should only be sent in the paused state.
- this._state = "resuming";
- return packet;
- },
- after: function (response) {
- if (response.error) {
- // There was an error resuming, back to paused state.
- this._state = "paused";
- }
- return response;
- },
- }),
-
- /**
- * Detach from the thread actor.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- detach: DebuggerClient.requester({
- type: "detach"
- }, {
- after: function (response) {
- this.client.unregisterClient(this);
- this._parent.thread = null;
- return response;
- },
- }),
-
- /**
- * Release multiple thread-lifetime object actors. If any pause-lifetime
- * actors are included in the request, a |notReleasable| error will return,
- * but all the thread-lifetime ones will have been released.
- *
- * @param array actors
- * An array with actor IDs to release.
- */
- releaseMany: DebuggerClient.requester({
- type: "releaseMany",
- actors: arg(0),
- }),
-
- /**
- * Promote multiple pause-lifetime object actors to thread-lifetime ones.
- *
- * @param array actors
- * An array with actor IDs to promote.
- */
- threadGrips: DebuggerClient.requester({
- type: "threadGrips",
- actors: arg(0)
- }),
-
- /**
- * Return the event listeners defined on the page.
- *
- * @param onResponse Function
- * Called with the thread's response.
- */
- eventListeners: DebuggerClient.requester({
- type: "eventListeners"
- }),
-
- /**
- * Request the loaded sources for the current thread.
- *
- * @param onResponse Function
- * Called with the thread's response.
- */
- getSources: DebuggerClient.requester({
- type: "sources"
- }),
-
- /**
- * Clear the thread's source script cache. A scriptscleared event
- * will be sent.
- */
- _clearScripts: function () {
- if (Object.keys(this._scriptCache).length > 0) {
- this._scriptCache = {};
- this.emit("scriptscleared");
- }
- },
-
- /**
- * Request frames from the callstack for the current thread.
- *
- * @param start integer
- * The number of the youngest stack frame to return (the youngest
- * frame is 0).
- * @param count integer
- * The maximum number of frames to return, or null to return all
- * frames.
- * @param onResponse function
- * Called with the thread's response.
- */
- getFrames: DebuggerClient.requester({
- type: "frames",
- start: arg(0),
- count: arg(1)
- }),
-
- /**
- * An array of cached frames. Clients can observe the framesadded and
- * framescleared event to keep up to date on changes to this cache,
- * and can fill it using the fillFrames method.
- */
- get cachedFrames() {
- return this._frameCache;
- },
-
- /**
- * true if there are more stack frames available on the server.
- */
- get moreFrames() {
- return this.paused && (!this._frameCache || this._frameCache.length == 0
- || !this._frameCache[this._frameCache.length - 1].oldest);
- },
-
- /**
- * Request the frame environment.
- *
- * @param frameId string
- */
- getEnvironment: function (frameId) {
- return this.request({ to: frameId, type: "getEnvironment" });
- },
-
- /**
- * Ensure that at least total stack frames have been loaded in the
- * ThreadClient's stack frame cache. A framesadded event will be
- * sent when the stack frame cache is updated.
- *
- * @param total number
- * The minimum number of stack frames to be included.
- * @param callback function
- * Optional callback function called when frames have been loaded
- * @returns true if a framesadded notification should be expected.
- */
- fillFrames: function (total, callback = noop) {
- this._assertPaused("fillFrames");
- if (this._frameCache.length >= total) {
- return false;
- }
-
- let numFrames = this._frameCache.length;
-
- this.getFrames(numFrames, total - numFrames, (response) => {
- if (response.error) {
- callback(response);
- return;
- }
-
- let threadGrips = DevToolsUtils.values(this._threadGrips);
-
- for (let i in response.frames) {
- let frame = response.frames[i];
- if (!frame.where.source) {
- // Older servers use urls instead, so we need to resolve
- // them to source actors
- for (let grip of threadGrips) {
- if (grip instanceof SourceClient && grip.url === frame.url) {
- frame.where.source = grip._form;
- }
- }
- }
-
- this._frameCache[frame.depth] = frame;
- }
-
- // If we got as many frames as we asked for, there might be more
- // frames available.
- this.emit("framesadded");
-
- callback(response);
- });
-
- return true;
- },
-
- /**
- * Clear the thread's stack frame cache. A framescleared event
- * will be sent.
- */
- _clearFrames: function () {
- if (this._frameCache.length > 0) {
- this._frameCache = [];
- this.emit("framescleared");
- }
- },
-
- /**
- * Return a ObjectClient object for the given object grip.
- *
- * @param grip object
- * A pause-lifetime object grip returned by the protocol.
- */
- pauseGrip: function (grip) {
- if (grip.actor in this._pauseGrips) {
- return this._pauseGrips[grip.actor];
- }
-
- let client = new ObjectClient(this.client, grip);
- this._pauseGrips[grip.actor] = client;
- return client;
- },
-
- /**
- * Get or create a long string client, checking the grip client cache if it
- * already exists.
- *
- * @param grip Object
- * The long string grip returned by the protocol.
- * @param gripCacheName String
- * The property name of the grip client cache to check for existing
- * clients in.
- */
- _longString: function (grip, gripCacheName) {
- if (grip.actor in this[gripCacheName]) {
- return this[gripCacheName][grip.actor];
- }
-
- let client = new LongStringClient(this.client, grip);
- this[gripCacheName][grip.actor] = client;
- return client;
- },
-
- /**
- * Return an instance of LongStringClient for the given long string grip that
- * is scoped to the current pause.
- *
- * @param grip Object
- * The long string grip returned by the protocol.
- */
- pauseLongString: function (grip) {
- return this._longString(grip, "_pauseGrips");
- },
-
- /**
- * Return an instance of LongStringClient for the given long string grip that
- * is scoped to the thread lifetime.
- *
- * @param grip Object
- * The long string grip returned by the protocol.
- */
- threadLongString: function (grip) {
- return this._longString(grip, "_threadGrips");
- },
-
- /**
- * Get or create an ArrayBuffer client, checking the grip client cache if it
- * already exists.
- *
- * @param grip Object
- * The ArrayBuffer grip returned by the protocol.
- * @param gripCacheName String
- * The property name of the grip client cache to check for existing
- * clients in.
- */
- _arrayBuffer: function (grip, gripCacheName) {
- if (grip.actor in this[gripCacheName]) {
- return this[gripCacheName][grip.actor];
- }
-
- let client = new ArrayBufferClient(this.client, grip);
- this[gripCacheName][grip.actor] = client;
- return client;
- },
-
- /**
- * Return an instance of ArrayBufferClient for the given ArrayBuffer grip that
- * is scoped to the thread lifetime.
- *
- * @param grip Object
- * The ArrayBuffer grip returned by the protocol.
- */
- threadArrayBuffer: function (grip) {
- return this._arrayBuffer(grip, "_threadGrips");
- },
-
- /**
- * Clear and invalidate all the grip clients from the given cache.
- *
- * @param gripCacheName
- * The property name of the grip cache we want to clear.
- */
- _clearObjectClients: function (gripCacheName) {
- for (let id in this[gripCacheName]) {
- this[gripCacheName][id].valid = false;
- }
- this[gripCacheName] = {};
- },
-
- /**
- * Invalidate pause-lifetime grip clients and clear the list of current grip
- * clients.
- */
- _clearPauseGrips: function () {
- this._clearObjectClients("_pauseGrips");
- },
-
- /**
- * Invalidate thread-lifetime grip clients and clear the list of current grip
- * clients.
- */
- _clearThreadGrips: function () {
- this._clearObjectClients("_threadGrips");
- },
-
- /**
- * Handle thread state change by doing necessary cleanup and notifying all
- * registered listeners.
- */
- _onThreadState: function (packet) {
- this._state = ThreadStateTypes[packet.type];
- // The debugger UI may not be initialized yet so we want to keep
- // the packet around so it knows what to pause state to display
- // when it's initialized
- this._lastPausePacket = packet.type === "resumed" ? null : packet;
- this._clearFrames();
- this._clearPauseGrips();
- packet.type === ThreadStateTypes.detached && this._clearThreadGrips();
- this.client._eventsEnabled && this.emit(packet.type, packet);
- },
-
- getLastPausePacket: function () {
- return this._lastPausePacket;
- },
-
- /**
- * Return an EnvironmentClient instance for the given environment actor form.
- */
- environment: function (form) {
- return new EnvironmentClient(this.client, form);
- },
-
- /**
- * Return an instance of SourceClient for the given source actor form.
- */
- source: function (form) {
- if (form.actor in this._threadGrips) {
- return this._threadGrips[form.actor];
- }
-
- this._threadGrips[form.actor] = new SourceClient(this, form);
- return this._threadGrips[form.actor];
- },
-
- /**
- * Request the prototype and own properties of mutlipleObjects.
- *
- * @param onResponse function
- * Called with the request's response.
- * @param actors [string]
- * List of actor ID of the queried objects.
- */
- getPrototypesAndProperties: DebuggerClient.requester({
- type: "prototypesAndProperties",
- actors: arg(0)
- }),
-
- events: ["newSource"]
-};
-
-eventSource(ThreadClient.prototype);
-
-/**
- * Creates a tracing profiler client for the remote debugging protocol
- * server. This client is a front to the trace actor created on the
- * server side, hiding the protocol details in a traditional
- * JavaScript API.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param actor string
- * The actor ID for this thread.
- */
-function TraceClient(client, actor) {
- this._client = client;
- this._actor = actor;
- this._activeTraces = new Set();
- this._waitingPackets = new Map();
- this._expectedPacket = 0;
- this.request = this._client.request;
- this.events = [];
-}
-
-TraceClient.prototype = {
- get actor() {
- return this._actor;
- },
- get tracing() {
- return this._activeTraces.size > 0;
- },
-
- get _transport() {
- return this._client._transport;
- },
-
- /**
- * Detach from the trace actor.
- */
- detach: DebuggerClient.requester({
- type: "detach"
- }, {
- after: function (response) {
- this._client.unregisterClient(this);
- return response;
- },
- }),
-
- /**
- * Start a new trace.
- *
- * @param trace [string]
- * An array of trace types to be recorded by the new trace.
- *
- * @param name string
- * The name of the new trace.
- *
- * @param onResponse function
- * Called with the request's response.
- */
- startTrace: DebuggerClient.requester({
- type: "startTrace",
- name: arg(1),
- trace: arg(0)
- }, {
- after: function (response) {
- if (response.error) {
- return response;
- }
-
- if (!this.tracing) {
- this._waitingPackets.clear();
- this._expectedPacket = 0;
- }
- this._activeTraces.add(response.name);
-
- return response;
- },
- }),
-
- /**
- * End a trace. If a name is provided, stop the named
- * trace. Otherwise, stop the most recently started trace.
- *
- * @param name string
- * The name of the trace to stop.
- *
- * @param onResponse function
- * Called with the request's response.
- */
- stopTrace: DebuggerClient.requester({
- type: "stopTrace",
- name: arg(0)
- }, {
- after: function (response) {
- if (response.error) {
- return response;
- }
-
- this._activeTraces.delete(response.name);
-
- return response;
- },
- })
-};
-
-/**
- * Grip clients are used to retrieve information about the relevant object.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param grip object
- * A pause-lifetime object grip returned by the protocol.
- */
-function ObjectClient(client, grip) {
- this._grip = grip;
- this._client = client;
- this.request = this._client.request;
-}
-exports.ObjectClient = ObjectClient;
-
-ObjectClient.prototype = {
- get actor() {
- return this._grip.actor;
- },
- get _transport() {
- return this._client._transport;
- },
-
- valid: true,
-
- get isFrozen() {
- return this._grip.frozen;
- },
- get isSealed() {
- return this._grip.sealed;
- },
- get isExtensible() {
- return this._grip.extensible;
- },
-
- getDefinitionSite: DebuggerClient.requester({
- type: "definitionSite"
- }, {
- before: function (packet) {
- if (this._grip.class != "Function") {
- throw new Error("getDefinitionSite is only valid for function grips.");
- }
- return packet;
- }
- }),
-
- /**
- * Request the names of a function's formal parameters.
- *
- * @param onResponse function
- * Called with an object of the form:
- * { parameterNames:[<parameterName>, ...] }
- * where each <parameterName> is the name of a parameter.
- */
- getParameterNames: DebuggerClient.requester({
- type: "parameterNames"
- }, {
- before: function (packet) {
- if (this._grip.class !== "Function") {
- throw new Error("getParameterNames is only valid for function grips.");
- }
- return packet;
- },
- }),
-
- /**
- * Request the names of the properties defined on the object and not its
- * prototype.
- *
- * @param onResponse function Called with the request's response.
- */
- getOwnPropertyNames: DebuggerClient.requester({
- type: "ownPropertyNames"
- }),
-
- /**
- * Request the prototype and own properties of the object.
- *
- * @param onResponse function Called with the request's response.
- */
- getPrototypeAndProperties: DebuggerClient.requester({
- type: "prototypeAndProperties"
- }),
-
- /**
- * Request a PropertyIteratorClient instance to ease listing
- * properties for this object.
- *
- * @param options Object
- * A dictionary object with various boolean attributes:
- * - ignoreIndexedProperties Boolean
- * If true, filters out Array items.
- * e.g. properties names between `0` and `object.length`.
- * - ignoreNonIndexedProperties Boolean
- * If true, filters out items that aren't array items
- * e.g. properties names that are not a number between `0`
- * and `object.length`.
- * - sort Boolean
- * If true, the iterator will sort the properties by name
- * before dispatching them.
- * @param onResponse function Called with the client instance.
- */
- enumProperties: DebuggerClient.requester({
- type: "enumProperties",
- options: arg(0)
- }, {
- after: function (response) {
- if (response.iterator) {
- return { iterator: new PropertyIteratorClient(this._client, response.iterator) };
- }
- return response;
- },
- }),
-
- /**
- * Request a PropertyIteratorClient instance to enumerate entries in a
- * Map/Set-like object.
- *
- * @param onResponse function Called with the request's response.
- */
- enumEntries: DebuggerClient.requester({
- type: "enumEntries"
- }, {
- before: function (packet) {
- if (!["Map", "WeakMap", "Set", "WeakSet"].includes(this._grip.class)) {
- throw new Error("enumEntries is only valid for Map/Set-like grips.");
- }
- return packet;
- },
- after: function (response) {
- if (response.iterator) {
- return {
- iterator: new PropertyIteratorClient(this._client, response.iterator)
- };
- }
- return response;
- }
- }),
-
- /**
- * Request a SymbolIteratorClient instance to enumerate symbols in an object.
- *
- * @param onResponse function Called with the request's response.
- */
- enumSymbols: DebuggerClient.requester({
- type: "enumSymbols"
- }, {
- before: function (packet) {
- if (this._grip.type !== "object") {
- throw new Error("enumSymbols is only valid for objects grips.");
- }
- return packet;
- },
- after: function (response) {
- if (response.iterator) {
- return {
- iterator: new SymbolIteratorClient(this._client, response.iterator)
- };
- }
- return response;
- }
- }),
-
- /**
- * Request the property descriptor of the object's specified property.
- *
- * @param name string The name of the requested property.
- * @param onResponse function Called with the request's response.
- */
- getProperty: DebuggerClient.requester({
- type: "property",
- name: arg(0)
- }),
-
- /**
- * Request the prototype of the object.
- *
- * @param onResponse function Called with the request's response.
- */
- getPrototype: DebuggerClient.requester({
- type: "prototype"
- }),
-
- /**
- * Request the display string of the object.
- *
- * @param onResponse function Called with the request's response.
- */
- getDisplayString: DebuggerClient.requester({
- type: "displayString"
- }),
-
- /**
- * Request the scope of the object.
- *
- * @param onResponse function Called with the request's response.
- */
- getScope: DebuggerClient.requester({
- type: "scope"
- }, {
- before: function (packet) {
- if (this._grip.class !== "Function") {
- throw new Error("scope is only valid for function grips.");
- }
- return packet;
- },
- }),
-
- /**
- * Request the promises directly depending on the current promise.
- */
- getDependentPromises: DebuggerClient.requester({
- type: "dependentPromises"
- }, {
- before: function (packet) {
- if (this._grip.class !== "Promise") {
- throw new Error("getDependentPromises is only valid for promise " +
- "grips.");
- }
- return packet;
- }
- }),
-
- /**
- * Request the stack to the promise's allocation point.
- */
- getPromiseAllocationStack: DebuggerClient.requester({
- type: "allocationStack"
- }, {
- before: function (packet) {
- if (this._grip.class !== "Promise") {
- throw new Error("getAllocationStack is only valid for promise grips.");
- }
- return packet;
- }
- }),
-
- /**
- * Request the stack to the promise's fulfillment point.
- */
- getPromiseFulfillmentStack: DebuggerClient.requester({
- type: "fulfillmentStack"
- }, {
- before: function (packet) {
- if (this._grip.class !== "Promise") {
- throw new Error("getPromiseFulfillmentStack is only valid for " +
- "promise grips.");
- }
- return packet;
- }
- }),
-
- /**
- * Request the stack to the promise's rejection point.
- */
- getPromiseRejectionStack: DebuggerClient.requester({
- type: "rejectionStack"
- }, {
- before: function (packet) {
- if (this._grip.class !== "Promise") {
- throw new Error("getPromiseRejectionStack is only valid for " +
- "promise grips.");
- }
- return packet;
- }
- })
-};
-
-/**
- * A PropertyIteratorClient provides a way to access to property names and
- * values of an object efficiently, slice by slice.
- * Note that the properties can be sorted in the backend,
- * this is controled while creating the PropertyIteratorClient
- * from ObjectClient.enumProperties.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param grip Object
- * A PropertyIteratorActor grip returned by the protocol via
- * TabActor.enumProperties request.
- */
-function PropertyIteratorClient(client, grip) {
- this._grip = grip;
- this._client = client;
- this.request = this._client.request;
-}
-
-PropertyIteratorClient.prototype = {
- get actor() {
- return this._grip.actor;
- },
-
- /**
- * Get the total number of properties available in the iterator.
- */
- get count() {
- return this._grip.count;
- },
-
- /**
- * Get one or more property names that correspond to the positions in the
- * indexes parameter.
- *
- * @param indexes Array
- * An array of property indexes.
- * @param callback Function
- * The function called when we receive the property names.
- */
- names: DebuggerClient.requester({
- type: "names",
- indexes: arg(0)
- }, {}),
-
- /**
- * Get a set of following property value(s).
- *
- * @param start Number
- * The index of the first property to fetch.
- * @param count Number
- * The number of properties to fetch.
- * @param callback Function
- * The function called when we receive the property values.
- */
- slice: DebuggerClient.requester({
- type: "slice",
- start: arg(0),
- count: arg(1)
- }, {}),
-
- /**
- * Get all the property values.
- *
- * @param callback Function
- * The function called when we receive the property values.
- */
- all: DebuggerClient.requester({
- type: "all"
- }, {}),
-};
-
-/**
- * A SymbolIteratorClient provides a way to access to symbols
- * of an object efficiently, slice by slice.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param grip Object
- * A SymbolIteratorActor grip returned by the protocol via
- * TabActor.enumSymbols request.
- */
-function SymbolIteratorClient(client, grip) {
- this._grip = grip;
- this._client = client;
- this.request = this._client.request;
-}
-
-SymbolIteratorClient.prototype = {
- get actor() {
- return this._grip.actor;
- },
-
- /**
- * Get the total number of symbols available in the iterator.
- */
- get count() {
- return this._grip.count;
- },
-
- /**
- * Get a set of following symbols.
- *
- * @param start Number
- * The index of the first symbol to fetch.
- * @param count Number
- * The number of symbols to fetch.
- * @param callback Function
- * The function called when we receive the symbols.
- */
- slice: DebuggerClient.requester({
- type: "slice",
- start: arg(0),
- count: arg(1)
- }, {}),
-
- /**
- * Get all the symbols.
- *
- * @param callback Function
- * The function called when we receive the symbols.
- */
- all: DebuggerClient.requester({
- type: "all"
- }, {}),
-};
-
-/**
- * A ArrayBufferClient provides a way to access ArrayBuffer from the
- * debugger server.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param grip Object
- * A pause-lifetime ArrayBuffer grip returned by the protocol.
- */
-function ArrayBufferClient(client, grip) {
- this._grip = grip;
- this._client = client;
- this.request = this._client.request;
-}
-ArrayBufferClient.prototype = {
- get actor() {
- return this._grip.actor;
- },
- get length() {
- return this._grip.length;
- },
- get _transport() {
- return this._client._transport;
- },
-
- valid: true,
-
- slice: DebuggerClient.requester({
- type: "slice",
- start: arg(0),
- count: arg(1)
- }),
-};
-
-/**
- * A LongStringClient provides a way to access "very long" strings from the
- * debugger server.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param grip Object
- * A pause-lifetime long string grip returned by the protocol.
- */
-function LongStringClient(client, grip) {
- this._grip = grip;
- this._client = client;
- this.request = this._client.request;
-}
-exports.LongStringClient = LongStringClient;
-
-LongStringClient.prototype = {
- get actor() {
- return this._grip.actor;
- },
- get length() {
- return this._grip.length;
- },
- get initial() {
- return this._grip.initial;
- },
- get _transport() {
- return this._client._transport;
- },
-
- valid: true,
-
- /**
- * Get the substring of this LongString from start to end.
- *
- * @param start Number
- * The starting index.
- * @param end Number
- * The ending index.
- * @param callback Function
- * The function called when we receive the substring.
- */
- substring: DebuggerClient.requester({
- type: "substring",
- start: arg(0),
- end: arg(1)
- }),
-};
-
-/**
- * A SourceClient provides a way to access the source text of a script.
- *
- * @param client ThreadClient
- * The thread client parent.
- * @param form Object
- * The form sent across the remote debugging protocol.
- */
-function SourceClient(client, form) {
- this._form = form;
- this._isBlackBoxed = form.isBlackBoxed;
- this._isPrettyPrinted = form.isPrettyPrinted;
- this._activeThread = client;
- this._client = client.client;
-}
-
-SourceClient.prototype = {
- get _transport() {
- return this._client._transport;
- },
- get isBlackBoxed() {
- return this._isBlackBoxed;
- },
- get isPrettyPrinted() {
- return this._isPrettyPrinted;
- },
- get actor() {
- return this._form.actor;
- },
- get request() {
- return this._client.request;
- },
- get url() {
- return this._form.url;
- },
-
- /**
- * Black box this SourceClient's source.
- *
- * @param callback Function
- * The callback function called when we receive the response from the server.
- */
- blackBox: DebuggerClient.requester({
- type: "blackbox"
- }, {
- after: function (response) {
- if (!response.error) {
- this._isBlackBoxed = true;
- if (this._activeThread) {
- this._activeThread.emit("blackboxchange", this);
- }
- }
- return response;
- }
- }),
-
- /**
- * Un-black box this SourceClient's source.
- *
- * @param callback Function
- * The callback function called when we receive the response from the server.
- */
- unblackBox: DebuggerClient.requester({
- type: "unblackbox"
- }, {
- after: function (response) {
- if (!response.error) {
- this._isBlackBoxed = false;
- if (this._activeThread) {
- this._activeThread.emit("blackboxchange", this);
- }
- }
- return response;
- }
- }),
-
- /**
- * Get Executable Lines from a source
- *
- * @param callback Function
- * The callback function called when we receive the response from the server.
- */
- getExecutableLines: function (cb = noop) {
- let packet = {
- to: this._form.actor,
- type: "getExecutableLines"
- };
-
- return this._client.request(packet).then(res => {
- cb(res.lines);
- return res.lines;
- });
- },
-
- /**
- * Get a long string grip for this SourceClient's source.
- */
- source: function (callback = noop) {
- let packet = {
- to: this._form.actor,
- type: "source"
- };
- return this._client.request(packet).then(response => {
- return this._onSourceResponse(response, callback);
- });
- },
-
- /**
- * Pretty print this source's text.
- */
- prettyPrint: function (indent, callback = noop) {
- const packet = {
- to: this._form.actor,
- type: "prettyPrint",
- indent
- };
- return this._client.request(packet).then(response => {
- if (!response.error) {
- this._isPrettyPrinted = true;
- this._activeThread._clearFrames();
- this._activeThread.emit("prettyprintchange", this);
- }
- return this._onSourceResponse(response, callback);
- });
- },
-
- /**
- * Stop pretty printing this source's text.
- */
- disablePrettyPrint: function (callback = noop) {
- const packet = {
- to: this._form.actor,
- type: "disablePrettyPrint"
- };
- return this._client.request(packet).then(response => {
- if (!response.error) {
- this._isPrettyPrinted = false;
- this._activeThread._clearFrames();
- this._activeThread.emit("prettyprintchange", this);
- }
- return this._onSourceResponse(response, callback);
- });
- },
-
- _onSourceResponse: function (response, callback) {
- if (response.error) {
- callback(response);
- return response;
- }
-
- if (typeof response.source === "string") {
- callback(response);
- return response;
- }
-
- let { contentType, source } = response;
- if (source.type === "arrayBuffer") {
- let arrayBuffer = this._activeThread.threadArrayBuffer(source);
- return arrayBuffer.slice(0, arrayBuffer.length).then(function (resp) {
- if (resp.error) {
- callback(resp);
- return resp;
- }
- // Keeping str as a string, ArrayBuffer/Uint8Array will not survive
- // setIn/mergeIn operations.
- const str = atob(resp.encoded);
- let newResponse = {
- source: {
- binary: str,
- toString: () => "[wasm]",
- },
- contentType,
- };
- callback(newResponse);
- return newResponse;
- });
- }
-
- let longString = this._activeThread.threadLongString(source);
- return longString.substring(0, longString.length).then(function (resp) {
- if (resp.error) {
- callback(resp);
- return resp;
- }
-
- let newResponse = {
- source: resp.substring,
- contentType: contentType
- };
- callback(newResponse);
- return newResponse;
- });
- },
-
- /**
- * Request to set a breakpoint in the specified location.
- *
- * @param object location
- * The location and condition of the breakpoint in
- * the form of { line[, column, condition] }.
- * @param function onResponse
- * Called with the thread's response.
- */
- setBreakpoint: function ({ line, column, condition, noSliding }, onResponse = noop) {
- // A helper function that sets the breakpoint.
- let doSetBreakpoint = callback => {
- let root = this._client.mainRoot;
- let location = {
- line,
- column,
- };
-
- let packet = {
- to: this.actor,
- type: "setBreakpoint",
- location,
- condition,
- noSliding,
- };
-
- // Backwards compatibility: send the breakpoint request to the
- // thread if the server doesn't support Debugger.Source actors.
- if (!root.traits.debuggerSourceActors) {
- packet.to = this._activeThread.actor;
- packet.location.url = this.url;
- }
-
- return this._client.request(packet).then(response => {
- // Ignoring errors, since the user may be setting a breakpoint in a
- // dead script that will reappear on a page reload.
- let bpClient;
- if (response.actor) {
- bpClient = new BreakpointClient(
- this._client,
- this,
- response.actor,
- location,
- root.traits.conditionalBreakpoints ? condition : undefined
- );
- }
- onResponse(response, bpClient);
- if (callback) {
- callback();
- }
- return [response, bpClient];
- });
- };
-
- // If the debuggee is paused, just set the breakpoint.
- if (this._activeThread.paused) {
- return doSetBreakpoint();
- }
- // Otherwise, force a pause in order to set the breakpoint.
- return this._activeThread.interrupt().then(response => {
- if (response.error) {
- // Can't set the breakpoint if pausing failed.
- onResponse(response);
- return response;
- }
-
- const { type, why } = response;
- const cleanUp = type == "paused" && why.type == "interrupted"
- ? () => this._activeThread.resume()
- : noop;
-
- return doSetBreakpoint(cleanUp);
- });
- }
-};
-
-/**
- * Breakpoint clients are used to remove breakpoints that are no longer used.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param sourceClient SourceClient
- * The source where this breakpoint exists
- * @param actor string
- * The actor ID for this breakpoint.
- * @param location object
- * The location of the breakpoint. This is an object with two properties:
- * url and line.
- * @param condition string
- * The conditional expression of the breakpoint
- */
-function BreakpointClient(client, sourceClient, actor, location, condition) {
- this._client = client;
- this._actor = actor;
- this.location = location;
- this.location.actor = sourceClient.actor;
- this.location.url = sourceClient.url;
- this.source = sourceClient;
- this.request = this._client.request;
-
- // The condition property should only exist if it's a truthy value
- if (condition) {
- this.condition = condition;
- }
-}
-
-BreakpointClient.prototype = {
-
- _actor: null,
- get actor() {
- return this._actor;
- },
- get _transport() {
- return this._client._transport;
- },
-
- /**
- * Remove the breakpoint from the server.
- */
- remove: DebuggerClient.requester({
- type: "delete"
- }),
-
- /**
- * Determines if this breakpoint has a condition
- */
- hasCondition: function () {
- let root = this._client.mainRoot;
- // XXX bug 990137: We will remove support for client-side handling of
- // conditional breakpoints
- if (root.traits.conditionalBreakpoints) {
- return "condition" in this;
- }
- return "conditionalExpression" in this;
- },
-
- /**
- * Get the condition of this breakpoint. Currently we have to
- * support locally emulated conditional breakpoints until the
- * debugger servers are updated (see bug 990137). We used a
- * different property when moving it server-side to ensure that we
- * are testing the right code.
- */
- getCondition: function () {
- let root = this._client.mainRoot;
- if (root.traits.conditionalBreakpoints) {
- return this.condition;
- }
- return this.conditionalExpression;
- },
-
- /**
- * Set the condition of this breakpoint
- */
- setCondition: function (gThreadClient, condition) {
- let root = this._client.mainRoot;
- let deferred = promise.defer();
-
- if (root.traits.conditionalBreakpoints) {
- let info = {
- line: this.location.line,
- column: this.location.column,
- condition: condition
- };
-
- // Remove the current breakpoint and add a new one with the
- // condition.
- this.remove(response => {
- if (response && response.error) {
- deferred.reject(response);
- return;
- }
-
- this.source.setBreakpoint(info, (resp, newBreakpoint) => {
- if (resp && resp.error) {
- deferred.reject(resp);
- } else {
- deferred.resolve(newBreakpoint);
- }
- });
- });
- } else {
- // The property shouldn't even exist if the condition is blank
- if (condition === "") {
- delete this.conditionalExpression;
- } else {
- this.conditionalExpression = condition;
- }
- deferred.resolve(this);
- }
-
- return deferred.promise;
- }
-};
-
-eventSource(BreakpointClient.prototype);
-
-/**
- * Environment clients are used to manipulate the lexical environment actors.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param form Object
- * The form sent across the remote debugging protocol.
- */
-function EnvironmentClient(client, form) {
- this._client = client;
- this._form = form;
- this.request = this._client.request;
-}
-exports.EnvironmentClient = EnvironmentClient;
-
-EnvironmentClient.prototype = {
-
- get actor() {
- return this._form.actor;
- },
- get _transport() {
- return this._client._transport;
- },
-
- /**
- * Fetches the bindings introduced by this lexical environment.
- */
- getBindings: DebuggerClient.requester({
- type: "bindings"
- }),
-
- /**
- * Changes the value of the identifier whose name is name (a string) to that
- * represented by value (a grip).
- */
- assign: DebuggerClient.requester({
- type: "assign",
- name: arg(0),
- value: arg(1)
- })
-};
-
-eventSource(EnvironmentClient.prototype);
copy from devtools/shared/client/main.js
copy to devtools/shared/client/environment-client.js
--- a/devtools/shared/client/main.js
+++ b/devtools/shared/client/environment-client.js
@@ -1,3390 +1,16 @@
-/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
-/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
-const { Ci, Cu } = require("chrome");
-const DevToolsUtils = require("devtools/shared/DevToolsUtils");
-const { getStack, callFunctionWithAsyncStack } = require("devtools/shared/platform/stack");
-
-const promise = Cu.import("resource://devtools/shared/deprecated-sync-thenables.js", {}).Promise;
-
-loader.lazyRequireGetter(this, "EventEmitter", "devtools/shared/event-emitter");
-loader.lazyRequireGetter(this, "WebConsoleClient", "devtools/shared/webconsole/client", true);
-loader.lazyRequireGetter(this, "DebuggerSocket", "devtools/shared/security/socket", true);
-loader.lazyRequireGetter(this, "Authentication", "devtools/shared/security/auth");
-
-const noop = () => {};
-
-/**
- * TODO: Get rid of this API in favor of EventTarget (bug 1042642)
- *
- * Add simple event notification to a prototype object. Any object that has
- * some use for event notifications or the observer pattern in general can be
- * augmented with the necessary facilities by passing its prototype to this
- * function.
- *
- * @param proto object
- * The prototype object that will be modified.
- */
-function eventSource(proto) {
- /**
- * Add a listener to the event source for a given event.
- *
- * @param name string
- * The event to listen for.
- * @param listener function
- * Called when the event is fired. If the same listener
- * is added more than once, it will be called once per
- * addListener call.
- */
- proto.addListener = function (name, listener) {
- if (typeof listener != "function") {
- throw TypeError("Listeners must be functions.");
- }
-
- if (!this._listeners) {
- this._listeners = {};
- }
-
- this._getListeners(name).push(listener);
- };
-
- /**
- * Add a listener to the event source for a given event. The
- * listener will be removed after it is called for the first time.
- *
- * @param name string
- * The event to listen for.
- * @param listener function
- * Called when the event is fired.
- */
- proto.addOneTimeListener = function (name, listener) {
- let l = (...args) => {
- this.removeListener(name, l);
- listener.apply(null, args);
- };
- this.addListener(name, l);
- };
-
- /**
- * Remove a listener from the event source previously added with
- * addListener().
- *
- * @param name string
- * The event name used during addListener to add the listener.
- * @param listener function
- * The callback to remove. If addListener was called multiple
- * times, all instances will be removed.
- */
- proto.removeListener = function (name, listener) {
- if (!this._listeners || (listener && !this._listeners[name])) {
- return;
- }
-
- if (!listener) {
- this._listeners[name] = [];
- } else {
- this._listeners[name] =
- this._listeners[name].filter(l => l != listener);
- }
- };
-
- /**
- * Returns the listeners for the specified event name. If none are defined it
- * initializes an empty list and returns that.
- *
- * @param name string
- * The event name.
- */
- proto._getListeners = function (name) {
- if (name in this._listeners) {
- return this._listeners[name];
- }
- this._listeners[name] = [];
- return this._listeners[name];
- };
-
- /**
- * Notify listeners of an event.
- *
- * @param name string
- * The event to fire.
- * @param arguments
- * All arguments will be passed along to the listeners,
- * including the name argument.
- */
- proto.emit = function () {
- if (!this._listeners) {
- return;
- }
-
- let name = arguments[0];
- let listeners = this._getListeners(name).slice(0);
-
- for (let listener of listeners) {
- try {
- listener.apply(null, arguments);
- } catch (e) {
- // Prevent a bad listener from interfering with the others.
- DevToolsUtils.reportException("notify event '" + name + "'", e);
- }
- }
- };
-}
-
-/**
- * Set of protocol messages that affect thread state, and the
- * state the actor is in after each message.
- */
-const ThreadStateTypes = {
- "paused": "paused",
- "resumed": "attached",
- "detached": "detached",
- "running": "attached"
-};
-
-/**
- * Set of protocol messages that are sent by the server without a prior request
- * by the client.
- */
-const UnsolicitedNotifications = {
- "consoleAPICall": "consoleAPICall",
- "eventNotification": "eventNotification",
- "fileActivity": "fileActivity",
- "lastPrivateContextExited": "lastPrivateContextExited",
- "logMessage": "logMessage",
- "networkEvent": "networkEvent",
- "networkEventUpdate": "networkEventUpdate",
- "newGlobal": "newGlobal",
- "newScript": "newScript",
- "tabDetached": "tabDetached",
- "tabListChanged": "tabListChanged",
- "reflowActivity": "reflowActivity",
- "addonListChanged": "addonListChanged",
- "workerListChanged": "workerListChanged",
- "serviceWorkerRegistrationListChanged": "serviceWorkerRegistrationList",
- "tabNavigated": "tabNavigated",
- "frameUpdate": "frameUpdate",
- "pageError": "pageError",
- "documentLoad": "documentLoad",
- "enteredFrame": "enteredFrame",
- "exitedFrame": "exitedFrame",
- "appOpen": "appOpen",
- "appClose": "appClose",
- "appInstall": "appInstall",
- "appUninstall": "appUninstall",
- "evaluationResult": "evaluationResult",
- "newSource": "newSource",
- "updatedSource": "updatedSource",
- "inspectObject": "inspectObject"
-};
-
-/**
- * Set of pause types that are sent by the server and not as an immediate
- * response to a client request.
- */
-const UnsolicitedPauses = {
- "resumeLimit": "resumeLimit",
- "debuggerStatement": "debuggerStatement",
- "breakpoint": "breakpoint",
- "DOMEvent": "DOMEvent",
- "watchpoint": "watchpoint",
- "exception": "exception"
-};
-
-/**
- * Creates a client for the remote debugging protocol server. This client
- * provides the means to communicate with the server and exchange the messages
- * required by the protocol in a traditional JavaScript API.
- */
-const DebuggerClient = exports.DebuggerClient = function (transport) {
- this._transport = transport;
- this._transport.hooks = this;
-
- // Map actor ID to client instance for each actor type.
- this._clients = new Map();
-
- this._pendingRequests = new Map();
- this._activeRequests = new Map();
- this._eventsEnabled = true;
-
- this.traits = {};
-
- this.request = this.request.bind(this);
- this.localTransport = this._transport.onOutputStreamReady === undefined;
-
- /*
- * As the first thing on the connection, expect a greeting packet from
- * the connection's root actor.
- */
- this.mainRoot = null;
- this.expectReply("root", (packet) => {
- this.mainRoot = new RootClient(this, packet);
- this.emit("connected", packet.applicationType, packet.traits);
- });
-};
-
-/**
- * A declarative helper for defining methods that send requests to the server.
- *
- * @param packetSkeleton
- * The form of the packet to send. Can specify fields to be filled from
- * the parameters by using the |arg| function.
- * @param before
- * The function to call before sending the packet. Is passed the packet,
- * and the return value is used as the new packet. The |this| context is
- * the instance of the client object we are defining a method for.
- * @param after
- * The function to call after the response is received. It is passed the
- * response, and the return value is considered the new response that
- * will be passed to the callback. The |this| context is the instance of
- * the client object we are defining a method for.
- * @return Request
- * The `Request` object that is a Promise object and resolves once
- * we receive the response. (See request method for more details)
- */
-DebuggerClient.requester = function (packetSkeleton, config = {}) {
- let { before, after } = config;
- return DevToolsUtils.makeInfallible(function (...args) {
- let outgoingPacket = {
- to: packetSkeleton.to || this.actor
- };
-
- let maxPosition = -1;
- for (let k of Object.keys(packetSkeleton)) {
- if (packetSkeleton[k] instanceof DebuggerClient.Argument) {
- let { position } = packetSkeleton[k];
- outgoingPacket[k] = packetSkeleton[k].getArgument(args);
- maxPosition = Math.max(position, maxPosition);
- } else {
- outgoingPacket[k] = packetSkeleton[k];
- }
- }
-
- if (before) {
- outgoingPacket = before.call(this, outgoingPacket);
- }
-
- return this.request(outgoingPacket, DevToolsUtils.makeInfallible((response) => {
- if (after) {
- let { from } = response;
- response = after.call(this, response);
- if (!response.from) {
- response.from = from;
- }
- }
-
- // The callback is always the last parameter.
- let thisCallback = args[maxPosition + 1];
- if (thisCallback) {
- thisCallback(response);
- }
- return response;
- }, "DebuggerClient.requester request callback"));
- }, "DebuggerClient.requester");
-};
-
-function arg(pos) {
- return new DebuggerClient.Argument(pos);
-}
-
-DebuggerClient.Argument = function (position) {
- this.position = position;
-};
-
-DebuggerClient.Argument.prototype.getArgument = function (params) {
- if (!(this.position in params)) {
- throw new Error("Bad index into params: " + this.position);
- }
- return params[this.position];
-};
-
-// Expose these to save callers the trouble of importing DebuggerSocket
-DebuggerClient.socketConnect = function (options) {
- // Defined here instead of just copying the function to allow lazy-load
- return DebuggerSocket.connect(options);
-};
-DevToolsUtils.defineLazyGetter(DebuggerClient, "Authenticators", () => {
- return Authentication.Authenticators;
-});
-DevToolsUtils.defineLazyGetter(DebuggerClient, "AuthenticationResult", () => {
- return Authentication.AuthenticationResult;
-});
-
-DebuggerClient.prototype = {
- /**
- * Connect to the server and start exchanging protocol messages.
- *
- * @param onConnected function
- * If specified, will be called when the greeting packet is
- * received from the debugging server.
- *
- * @return Promise
- * Resolves once connected with an array whose first element
- * is the application type, by default "browser", and the second
- * element is the traits object (help figure out the features
- * and behaviors of the server we connect to. See RootActor).
- */
- connect: function (onConnected) {
- let deferred = promise.defer();
- this.emit("connect");
-
- // Also emit the event on the |DebuggerClient| object (not on the instance),
- // so it's possible to track all instances.
- EventEmitter.emit(DebuggerClient, "connect", this);
-
- this.addOneTimeListener("connected", (name, applicationType, traits) => {
- this.traits = traits;
- if (onConnected) {
- onConnected(applicationType, traits);
- }
- deferred.resolve([applicationType, traits]);
- });
-
- this._transport.ready();
- return deferred.promise;
- },
-
- /**
- * Shut down communication with the debugging server.
- *
- * @param onClosed function
- * If specified, will be called when the debugging connection
- * has been closed. This parameter is deprecated - please use
- * the returned Promise.
- * @return Promise
- * Resolves after the underlying transport is closed.
- */
- close: function (onClosed) {
- let deferred = promise.defer();
- if (onClosed) {
- deferred.promise.then(onClosed);
- }
-
- // Disable detach event notifications, because event handlers will be in a
- // cleared scope by the time they run.
- this._eventsEnabled = false;
-
- let cleanup = () => {
- this._transport.close();
- this._transport = null;
- };
-
- // If the connection is already closed,
- // there is no need to detach client
- // as we won't be able to send any message.
- if (this._closed) {
- cleanup();
- deferred.resolve();
- return deferred.promise;
- }
-
- this.addOneTimeListener("closed", deferred.resolve);
-
- // Call each client's `detach` method by calling
- // lastly registered ones first to give a chance
- // to detach child clients first.
- let clients = [...this._clients.values()];
- this._clients.clear();
- const detachClients = () => {
- let client = clients.pop();
- if (!client) {
- // All clients detached.
- cleanup();
- return;
- }
- if (client.detach) {
- client.detach(detachClients);
- return;
- }
- detachClients();
- };
- detachClients();
-
- return deferred.promise;
- },
-
- /*
- * This function exists only to preserve DebuggerClient's interface;
- * new code should say 'client.mainRoot.listTabs()'.
- */
- listTabs: function (onResponse) {
- return this.mainRoot.listTabs(onResponse);
- },
-
- /*
- * This function exists only to preserve DebuggerClient's interface;
- * new code should say 'client.mainRoot.listAddons()'.
- */
- listAddons: function (onResponse) {
- return this.mainRoot.listAddons(onResponse);
- },
-
- getTab: function (filter) {
- return this.mainRoot.getTab(filter);
- },
-
- /**
- * Attach to a tab actor.
- *
- * @param string tabActor
- * The actor ID for the tab to attach.
- * @param function onResponse
- * Called with the response packet and a TabClient
- * (which will be undefined on error).
- */
- attachTab: function (tabActor, onResponse = noop) {
- if (this._clients.has(tabActor)) {
- let cachedTab = this._clients.get(tabActor);
- let cachedResponse = {
- cacheDisabled: cachedTab.cacheDisabled,
- javascriptEnabled: cachedTab.javascriptEnabled,
- traits: cachedTab.traits,
- };
- DevToolsUtils.executeSoon(() => onResponse(cachedResponse, cachedTab));
- return promise.resolve([cachedResponse, cachedTab]);
- }
-
- let packet = {
- to: tabActor,
- type: "attach"
- };
- return this.request(packet).then(response => {
- let tabClient;
- if (!response.error) {
- tabClient = new TabClient(this, response);
- this.registerClient(tabClient);
- }
- onResponse(response, tabClient);
- return [response, tabClient];
- });
- },
-
- attachWorker: function (workerActor, onResponse = noop) {
- let workerClient = this._clients.get(workerActor);
- if (workerClient !== undefined) {
- let response = {
- from: workerClient.actor,
- type: "attached",
- url: workerClient.url
- };
- DevToolsUtils.executeSoon(() => onResponse(response, workerClient));
- return promise.resolve([response, workerClient]);
- }
-
- return this.request({ to: workerActor, type: "attach" }).then(response => {
- if (response.error) {
- onResponse(response, null);
- return [response, null];
- }
-
- workerClient = new WorkerClient(this, response);
- this.registerClient(workerClient);
- onResponse(response, workerClient);
- return [response, workerClient];
- });
- },
-
- /**
- * Attach to an addon actor.
- *
- * @param string addonActor
- * The actor ID for the addon to attach.
- * @param function onResponse
- * Called with the response packet and a AddonClient
- * (which will be undefined on error).
- */
- attachAddon: function (addonActor, onResponse = noop) {
- let packet = {
- to: addonActor,
- type: "attach"
- };
- return this.request(packet).then(response => {
- let addonClient;
- if (!response.error) {
- addonClient = new AddonClient(this, addonActor);
- this.registerClient(addonClient);
- this.activeAddon = addonClient;
- }
- onResponse(response, addonClient);
- return [response, addonClient];
- });
- },
-
- /**
- * Attach to a Web Console actor.
- *
- * @param string consoleActor
- * The ID for the console actor to attach to.
- * @param array listeners
- * The console listeners you want to start.
- * @param function onResponse
- * Called with the response packet and a WebConsoleClient
- * instance (which will be undefined on error).
- */
- attachConsole:
- function (consoleActor, listeners, onResponse = noop) {
- let packet = {
- to: consoleActor,
- type: "startListeners",
- listeners: listeners,
- };
-
- return this.request(packet).then(response => {
- let consoleClient;
- if (!response.error) {
- if (this._clients.has(consoleActor)) {
- consoleClient = this._clients.get(consoleActor);
- } else {
- consoleClient = new WebConsoleClient(this, response);
- this.registerClient(consoleClient);
- }
- }
- onResponse(response, consoleClient);
- return [response, consoleClient];
- });
- },
-
- /**
- * Attach to a global-scoped thread actor for chrome debugging.
- *
- * @param string threadActor
- * The actor ID for the thread to attach.
- * @param function onResponse
- * Called with the response packet and a ThreadClient
- * (which will be undefined on error).
- * @param object options
- * Configuration options.
- * - useSourceMaps: whether to use source maps or not.
- */
- attachThread: function (threadActor, onResponse = noop, options = {}) {
- if (this._clients.has(threadActor)) {
- let client = this._clients.get(threadActor);
- DevToolsUtils.executeSoon(() => onResponse({}, client));
- return promise.resolve([{}, client]);
- }
-
- let packet = {
- to: threadActor,
- type: "attach",
- options,
- };
- return this.request(packet).then(response => {
- let threadClient;
- if (!response.error) {
- threadClient = new ThreadClient(this, threadActor);
- this.registerClient(threadClient);
- }
- onResponse(response, threadClient);
- return [response, threadClient];
- });
- },
-
- /**
- * Attach to a trace actor.
- *
- * @param string traceActor
- * The actor ID for the tracer to attach.
- * @param function onResponse
- * Called with the response packet and a TraceClient
- * (which will be undefined on error).
- */
- attachTracer: function (traceActor, onResponse = noop) {
- if (this._clients.has(traceActor)) {
- let client = this._clients.get(traceActor);
- DevToolsUtils.executeSoon(() => onResponse({}, client));
- return promise.resolve([{}, client]);
- }
-
- let packet = {
- to: traceActor,
- type: "attach"
- };
- return this.request(packet).then(response => {
- let traceClient;
- if (!response.error) {
- traceClient = new TraceClient(this, traceActor);
- this.registerClient(traceClient);
- }
- onResponse(response, traceClient);
- return [response, traceClient];
- });
- },
-
- /**
- * Fetch the ChromeActor for the main process or ChildProcessActor for a
- * a given child process ID.
- *
- * @param number id
- * The ID for the process to attach (returned by `listProcesses`).
- * Connected to the main process if omitted, or is 0.
- */
- getProcess: function (id) {
- let packet = {
- to: "root",
- type: "getProcess"
- };
- if (typeof (id) == "number") {
- packet.id = id;
- }
- return this.request(packet);
- },
-
- /**
- * Release an object actor.
- *
- * @param string actor
- * The actor ID to send the request to.
- * @param onResponse function
- * If specified, will be called with the response packet when
- * debugging server responds.
- */
- release: DebuggerClient.requester({
- to: arg(0),
- type: "release"
- }),
-
- /**
- * Send a request to the debugging server.
- *
- * @param packet object
- * A JSON packet to send to the debugging server.
- * @param onResponse function
- * If specified, will be called with the JSON response packet when
- * debugging server responds.
- * @return Request
- * This object emits a number of events to allow you to respond to
- * different parts of the request lifecycle.
- * It is also a Promise object, with a `then` method, that is resolved
- * whenever a JSON or a Bulk response is received; and is rejected
- * if the response is an error.
- * Note: This return value can be ignored if you are using JSON alone,
- * because the callback provided in |onResponse| will be bound to the
- * "json-reply" event automatically.
- *
- * Events emitted:
- * * json-reply: The server replied with a JSON packet, which is
- * passed as event data.
- * * bulk-reply: The server replied with bulk data, which you can read
- * using the event data object containing:
- * * actor: Name of actor that received the packet
- * * type: Name of actor's method that was called on receipt
- * * length: Size of the data to be read
- * * stream: This input stream should only be used directly if you
- * can ensure that you will read exactly |length| bytes
- * and will not close the stream when reading is complete
- * * done: If you use the stream directly (instead of |copyTo|
- * below), you must signal completion by resolving /
- * rejecting this deferred. If it's rejected, the
- * transport will be closed. If an Error is supplied as a
- * rejection value, it will be logged via |dumpn|. If you
- * do use |copyTo|, resolving is taken care of for you
- * when copying completes.
- * * copyTo: A helper function for getting your data out of the
- * stream that meets the stream handling requirements
- * above, and has the following signature:
- * @param output nsIAsyncOutputStream
- * The stream to copy to.
- * @return Promise
- * The promise is resolved when copying completes or
- * rejected if any (unexpected) errors occur.
- * This object also emits "progress" events for each chunk
- * that is copied. See stream-utils.js.
- */
- request: function (packet, onResponse) {
- if (!this.mainRoot) {
- throw Error("Have not yet received a hello packet from the server.");
- }
- let type = packet.type || "";
- if (!packet.to) {
- throw Error("'" + type + "' request packet has no destination.");
- }
-
- // The onResponse callback might modify the response, so we need to call
- // it and resolve the promise with its result if it's truthy.
- const safeOnResponse = response => {
- if (!onResponse) {
- return response;
- }
- return onResponse(response) || response;
- };
-
- if (this._closed) {
- let msg = "'" + type + "' request packet to " +
- "'" + packet.to + "' " +
- "can't be sent as the connection is closed.";
- let resp = { error: "connectionClosed", message: msg };
- return promise.reject(safeOnResponse(resp));
- }
-
- let request = new Request(packet);
- request.format = "json";
- request.stack = getStack();
-
- // Implement a Promise like API on the returned object
- // that resolves/rejects on request response
- let deferred = promise.defer();
- function listenerJson(resp) {
- removeRequestListeners();
- if (resp.error) {
- deferred.reject(safeOnResponse(resp));
- } else {
- deferred.resolve(safeOnResponse(resp));
- }
- }
- function listenerBulk(resp) {
- removeRequestListeners();
- deferred.resolve(safeOnResponse(resp));
- }
-
- const removeRequestListeners = () => {
- request.off("json-reply", listenerJson);
- request.off("bulk-reply", listenerBulk);
- };
-
- request.on("json-reply", listenerJson);
- request.on("bulk-reply", listenerBulk);
-
- this._sendOrQueueRequest(request);
- request.then = deferred.promise.then.bind(deferred.promise);
-
- return request;
- },
-
- /**
- * Transmit streaming data via a bulk request.
- *
- * This method initiates the bulk send process by queuing up the header data.
- * The caller receives eventual access to a stream for writing.
- *
- * Since this opens up more options for how the server might respond (it could
- * send back either JSON or bulk data), and the returned Request object emits
- * events for different stages of the request process that you may want to
- * react to.
- *
- * @param request Object
- * This is modeled after the format of JSON packets above, but does not
- * actually contain the data, but is instead just a routing header:
- * * actor: Name of actor that will receive the packet
- * * type: Name of actor's method that should be called on receipt
- * * length: Size of the data to be sent
- * @return Request
- * This object emits a number of events to allow you to respond to
- * different parts of the request lifecycle.
- *
- * Events emitted:
- * * bulk-send-ready: Ready to send bulk data to the server, using the
- * event data object containing:
- * * stream: This output stream should only be used directly if
- * you can ensure that you will write exactly |length|
- * bytes and will not close the stream when writing is
- * complete
- * * done: If you use the stream directly (instead of |copyFrom|
- * below), you must signal completion by resolving /
- * rejecting this deferred. If it's rejected, the
- * transport will be closed. If an Error is supplied as
- * a rejection value, it will be logged via |dumpn|. If
- * you do use |copyFrom|, resolving is taken care of for
- * you when copying completes.
- * * copyFrom: A helper function for getting your data onto the
- * stream that meets the stream handling requirements
- * above, and has the following signature:
- * @param input nsIAsyncInputStream
- * The stream to copy from.
- * @return Promise
- * The promise is resolved when copying completes or
- * rejected if any (unexpected) errors occur.
- * This object also emits "progress" events for each chunk
- * that is copied. See stream-utils.js.
- * * json-reply: The server replied with a JSON packet, which is
- * passed as event data.
- * * bulk-reply: The server replied with bulk data, which you can read
- * using the event data object containing:
- * * actor: Name of actor that received the packet
- * * type: Name of actor's method that was called on receipt
- * * length: Size of the data to be read
- * * stream: This input stream should only be used directly if you
- * can ensure that you will read exactly |length| bytes
- * and will not close the stream when reading is complete
- * * done: If you use the stream directly (instead of |copyTo|
- * below), you must signal completion by resolving /
- * rejecting this deferred. If it's rejected, the
- * transport will be closed. If an Error is supplied as a
- * rejection value, it will be logged via |dumpn|. If you
- * do use |copyTo|, resolving is taken care of for you
- * when copying completes.
- * * copyTo: A helper function for getting your data out of the
- * stream that meets the stream handling requirements
- * above, and has the following signature:
- * @param output nsIAsyncOutputStream
- * The stream to copy to.
- * @return Promise
- * The promise is resolved when copying completes or
- * rejected if any (unexpected) errors occur.
- * This object also emits "progress" events for each chunk
- * that is copied. See stream-utils.js.
- */
- startBulkRequest: function (request) {
- if (!this.traits.bulk) {
- throw Error("Server doesn't support bulk transfers");
- }
- if (!this.mainRoot) {
- throw Error("Have not yet received a hello packet from the server.");
- }
- if (!request.type) {
- throw Error("Bulk packet is missing the required 'type' field.");
- }
- if (!request.actor) {
- throw Error("'" + request.type + "' bulk packet has no destination.");
- }
- if (!request.length) {
- throw Error("'" + request.type + "' bulk packet has no length.");
- }
-
- request = new Request(request);
- request.format = "bulk";
-
- this._sendOrQueueRequest(request);
-
- return request;
- },
-
- /**
- * If a new request can be sent immediately, do so. Otherwise, queue it.
- */
- _sendOrQueueRequest(request) {
- let actor = request.actor;
- if (!this._activeRequests.has(actor)) {
- this._sendRequest(request);
- } else {
- this._queueRequest(request);
- }
- },
-
- /**
- * Send a request.
- * @throws Error if there is already an active request in flight for the same
- * actor.
- */
- _sendRequest(request) {
- let actor = request.actor;
- this.expectReply(actor, request);
-
- if (request.format === "json") {
- this._transport.send(request.request);
- return;
- }
-
- this._transport.startBulkSend(request.request).then((...args) => {
- request.emit("bulk-send-ready", ...args);
- });
- },
-
- /**
- * Queue a request to be sent later. Queues are only drained when an in
- * flight request to a given actor completes.
- */
- _queueRequest(request) {
- let actor = request.actor;
- let queue = this._pendingRequests.get(actor) || [];
- queue.push(request);
- this._pendingRequests.set(actor, queue);
- },
-
- /**
- * Attempt the next request to a given actor (if any).
- */
- _attemptNextRequest(actor) {
- if (this._activeRequests.has(actor)) {
- return;
- }
- let queue = this._pendingRequests.get(actor);
- if (!queue) {
- return;
- }
- let request = queue.shift();
- if (queue.length === 0) {
- this._pendingRequests.delete(actor);
- }
- this._sendRequest(request);
- },
-
- /**
- * Arrange to hand the next reply from |actor| to the handler bound to
- * |request|.
- *
- * DebuggerClient.prototype.request / startBulkRequest usually takes care of
- * establishing the handler for a given request, but in rare cases (well,
- * greetings from new root actors, is the only case at the moment) we must be
- * prepared for a "reply" that doesn't correspond to any request we sent.
- */
- expectReply: function (actor, request) {
- if (this._activeRequests.has(actor)) {
- throw Error("clashing handlers for next reply from " + actor);
- }
-
- // If a handler is passed directly (as it is with the handler for the root
- // actor greeting), create a dummy request to bind this to.
- if (typeof request === "function") {
- let handler = request;
- request = new Request();
- request.on("json-reply", handler);
- }
-
- this._activeRequests.set(actor, request);
- },
-
- // Transport hooks.
-
- /**
- * Called by DebuggerTransport to dispatch incoming packets as appropriate.
- *
- * @param packet object
- * The incoming packet.
- */
- onPacket: function (packet) {
- if (!packet.from) {
- DevToolsUtils.reportException(
- "onPacket",
- new Error("Server did not specify an actor, dropping packet: " +
- JSON.stringify(packet)));
- return;
- }
-
- // If we have a registered Front for this actor, let it handle the packet
- // and skip all the rest of this unpleasantness.
- let front = this.getActor(packet.from);
- if (front) {
- front.onPacket(packet);
- return;
- }
-
- // Check for "forwardingCancelled" here instead of using a client to handle it.
- // This is necessary because we might receive this event while the client is closing,
- // and the clients have already been removed by that point.
- if (this.mainRoot &&
- packet.from == this.mainRoot.actor &&
- packet.type == "forwardingCancelled") {
- this.purgeRequests(packet.prefix);
- return;
- }
-
- if (this._clients.has(packet.from) && packet.type) {
- let client = this._clients.get(packet.from);
- let type = packet.type;
- if (client.events.indexOf(type) != -1) {
- client.emit(type, packet);
- // we ignore the rest, as the client is expected to handle this packet.
- return;
- }
- }
-
- let activeRequest;
- // See if we have a handler function waiting for a reply from this
- // actor. (Don't count unsolicited notifications or pauses as
- // replies.)
- if (this._activeRequests.has(packet.from) &&
- !(packet.type in UnsolicitedNotifications) &&
- !(packet.type == ThreadStateTypes.paused &&
- packet.why.type in UnsolicitedPauses)) {
- activeRequest = this._activeRequests.get(packet.from);
- this._activeRequests.delete(packet.from);
- }
-
- // If there is a subsequent request for the same actor, hand it off to the
- // transport. Delivery of packets on the other end is always async, even
- // in the local transport case.
- this._attemptNextRequest(packet.from);
-
- // Packets that indicate thread state changes get special treatment.
- if (packet.type in ThreadStateTypes &&
- this._clients.has(packet.from) &&
- typeof this._clients.get(packet.from)._onThreadState == "function") {
- this._clients.get(packet.from)._onThreadState(packet);
- }
-
- // TODO: Bug 1151156 - Remove once Gecko 40 is on b2g-stable.
- if (!this.traits.noNeedToFakeResumptionOnNavigation) {
- // On navigation the server resumes, so the client must resume as well.
- // We achieve that by generating a fake resumption packet that triggers
- // the client's thread state change listeners.
- if (packet.type == UnsolicitedNotifications.tabNavigated &&
- this._clients.has(packet.from) &&
- this._clients.get(packet.from).thread) {
- let thread = this._clients.get(packet.from).thread;
- let resumption = { from: thread._actor, type: "resumed" };
- thread._onThreadState(resumption);
- }
- }
-
- // Only try to notify listeners on events, not responses to requests
- // that lack a packet type.
- if (packet.type) {
- this.emit(packet.type, packet);
- }
-
- if (activeRequest) {
- let emitReply = () => activeRequest.emit("json-reply", packet);
- if (activeRequest.stack) {
- callFunctionWithAsyncStack(emitReply, activeRequest.stack,
- "DevTools RDP");
- } else {
- emitReply();
- }
- }
- },
-
- /**
- * Called by the DebuggerTransport to dispatch incoming bulk packets as
- * appropriate.
- *
- * @param packet object
- * The incoming packet, which contains:
- * * actor: Name of actor that will receive the packet
- * * type: Name of actor's method that should be called on receipt
- * * length: Size of the data to be read
- * * stream: This input stream should only be used directly if you can
- * ensure that you will read exactly |length| bytes and will
- * not close the stream when reading is complete
- * * done: If you use the stream directly (instead of |copyTo|
- * below), you must signal completion by resolving /
- * rejecting this deferred. If it's rejected, the transport
- * will be closed. If an Error is supplied as a rejection
- * value, it will be logged via |dumpn|. If you do use
- * |copyTo|, resolving is taken care of for you when copying
- * completes.
- * * copyTo: A helper function for getting your data out of the stream
- * that meets the stream handling requirements above, and has
- * the following signature:
- * @param output nsIAsyncOutputStream
- * The stream to copy to.
- * @return Promise
- * The promise is resolved when copying completes or rejected
- * if any (unexpected) errors occur.
- * This object also emits "progress" events for each chunk
- * that is copied. See stream-utils.js.
- */
- onBulkPacket: function (packet) {
- let { actor } = packet;
-
- if (!actor) {
- DevToolsUtils.reportException(
- "onBulkPacket",
- new Error("Server did not specify an actor, dropping bulk packet: " +
- JSON.stringify(packet)));
- return;
- }
-
- // See if we have a handler function waiting for a reply from this
- // actor.
- if (!this._activeRequests.has(actor)) {
- return;
- }
-
- let activeRequest = this._activeRequests.get(actor);
- this._activeRequests.delete(actor);
-
- // If there is a subsequent request for the same actor, hand it off to the
- // transport. Delivery of packets on the other end is always async, even
- // in the local transport case.
- this._attemptNextRequest(actor);
-
- activeRequest.emit("bulk-reply", packet);
- },
-
- /**
- * Called by DebuggerTransport when the underlying stream is closed.
- *
- * @param status nsresult
- * The status code that corresponds to the reason for closing
- * the stream.
- */
- onClosed: function () {
- this._closed = true;
- this.emit("closed");
-
- this.purgeRequests();
-
- // The |_pools| array on the client-side currently is used only by
- // protocol.js to store active fronts, mirroring the actor pools found in
- // the server. So, read all usages of "pool" as "protocol.js front".
- //
- // In the normal case where we shutdown cleanly, the toolbox tells each tool
- // to close, and they each call |destroy| on any fronts they were using.
- // When |destroy| or |cleanup| is called on a protocol.js front, it also
- // removes itself from the |_pools| array. Once the toolbox has shutdown,
- // the connection is closed, and we reach here. All fronts (should have
- // been) |destroy|ed, so |_pools| should empty.
- //
- // If the connection instead aborts unexpectedly, we may end up here with
- // all fronts used during the life of the connection. So, we call |cleanup|
- // on them clear their state, reject pending requests, and remove themselves
- // from |_pools|. This saves the toolbox from hanging indefinitely, in case
- // it waits for some server response before shutdown that will now never
- // arrive.
- for (let pool of this._pools) {
- pool.cleanup();
- }
- },
-
- /**
- * Purge pending and active requests in this client.
- *
- * @param prefix string (optional)
- * If a prefix is given, only requests for actor IDs that start with the prefix
- * will be cleaned up. This is useful when forwarding of a portion of requests
- * is cancelled on the server.
- */
- purgeRequests(prefix = "") {
- let reject = function (type, request) {
- // Server can send packets on its own and client only pass a callback
- // to expectReply, so that there is no request object.
- let msg;
- if (request.request) {
- msg = "'" + request.request.type + "' " + type + " request packet" +
- " to '" + request.actor + "' " +
- "can't be sent as the connection just closed.";
- } else {
- msg = "server side packet can't be received as the connection just closed.";
- }
- let packet = { error: "connectionClosed", message: msg };
- request.emit("json-reply", packet);
- };
-
- let pendingRequestsToReject = [];
- this._pendingRequests.forEach((requests, actor) => {
- if (!actor.startsWith(prefix)) {
- return;
- }
- this._pendingRequests.delete(actor);
- pendingRequestsToReject = pendingRequestsToReject.concat(requests);
- });
- pendingRequestsToReject.forEach(request => reject("pending", request));
-
- let activeRequestsToReject = [];
- this._activeRequests.forEach((request, actor) => {
- if (!actor.startsWith(prefix)) {
- return;
- }
- this._activeRequests.delete(actor);
- activeRequestsToReject = activeRequestsToReject.concat(request);
- });
- activeRequestsToReject.forEach(request => reject("active", request));
- },
-
- /**
- * Search for all requests in process for this client, including those made via
- * protocol.js and wait all of them to complete. Since the requests seen when this is
- * first called may in turn trigger more requests, we keep recursing through this
- * function until there is no more activity.
- *
- * This is a fairly heavy weight process, so it's only meant to be used in tests.
- *
- * @return Promise
- * Resolved when all requests have settled.
- */
- waitForRequestsToSettle() {
- let requests = [];
-
- // Gather all pending and active requests in this client
- // The request object supports a Promise API for completion (it has .then())
- this._pendingRequests.forEach(requestsForActor => {
- // Each value is an array of pending requests
- requests = requests.concat(requestsForActor);
- });
- this._activeRequests.forEach(requestForActor => {
- // Each value is a single active request
- requests = requests.concat(requestForActor);
- });
-
- // protocol.js
- // Use a Set because some fronts (like domwalker) seem to have multiple parents.
- let fronts = new Set();
- let poolsToVisit = [...this._pools];
-
- // With protocol.js, each front can potentially have it's own pools containing child
- // fronts, forming a tree. Descend through all the pools to locate all child fronts.
- while (poolsToVisit.length) {
- let pool = poolsToVisit.shift();
- fronts.add(pool);
- for (let child of pool.poolChildren()) {
- poolsToVisit.push(child);
- }
- }
-
- // For each front, wait for its requests to settle
- for (let front of fronts) {
- if (front.hasRequests()) {
- requests.push(front.waitForRequestsToSettle());
- }
- }
-
- // Abort early if there are no requests
- if (!requests.length) {
- return Promise.resolve();
- }
-
- return DevToolsUtils.settleAll(requests).catch(() => {
- // One of the requests might have failed, but ignore that situation here and pipe
- // both success and failure through the same path. The important part is just that
- // we waited.
- }).then(() => {
- // Repeat, more requests may have started in response to those we just waited for
- return this.waitForRequestsToSettle();
- });
- },
-
- registerClient: function (client) {
- let actorID = client.actor;
- if (!actorID) {
- throw new Error("DebuggerServer.registerClient expects " +
- "a client instance with an `actor` attribute.");
- }
- if (!Array.isArray(client.events)) {
- throw new Error("DebuggerServer.registerClient expects " +
- "a client instance with an `events` attribute " +
- "that is an array.");
- }
- if (client.events.length > 0 && typeof (client.emit) != "function") {
- throw new Error("DebuggerServer.registerClient expects " +
- "a client instance with non-empty `events` array to" +
- "have an `emit` function.");
- }
- if (this._clients.has(actorID)) {
- throw new Error("DebuggerServer.registerClient already registered " +
- "a client for this actor.");
- }
- this._clients.set(actorID, client);
- },
-
- unregisterClient: function (client) {
- let actorID = client.actor;
- if (!actorID) {
- throw new Error("DebuggerServer.unregisterClient expects " +
- "a Client instance with a `actor` attribute.");
- }
- this._clients.delete(actorID);
- },
-
- /**
- * Actor lifetime management, echos the server's actor pools.
- */
- __pools: null,
- get _pools() {
- if (this.__pools) {
- return this.__pools;
- }
- this.__pools = new Set();
- return this.__pools;
- },
-
- addActorPool: function (pool) {
- this._pools.add(pool);
- },
- removeActorPool: function (pool) {
- this._pools.delete(pool);
- },
- getActor: function (actorID) {
- let pool = this.poolFor(actorID);
- return pool ? pool.get(actorID) : null;
- },
-
- poolFor: function (actorID) {
- for (let pool of this._pools) {
- if (pool.has(actorID)) {
- return pool;
- }
- }
- return null;
- },
-
- /**
- * Currently attached addon.
- */
- activeAddon: null
-};
-
-eventSource(DebuggerClient.prototype);
-
-class Request extends EventEmitter {
- constructor(request) {
- super();
- this.request = request;
- }
-
- get actor() {
- return this.request.to || this.request.actor;
- }
-}
-
-/**
- * Creates a tab client for the remote debugging protocol server. This client
- * is a front to the tab actor created in the server side, hiding the protocol
- * details in a traditional JavaScript API.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param form object
- * The protocol form for this tab.
- */
-function TabClient(client, form) {
- this.client = client;
- this._actor = form.from;
- this._threadActor = form.threadActor;
- this.javascriptEnabled = form.javascriptEnabled;
- this.cacheDisabled = form.cacheDisabled;
- this.thread = null;
- this.request = this.client.request;
- this.traits = form.traits || {};
- this.events = ["workerListChanged"];
-}
-
-TabClient.prototype = {
- get actor() {
- return this._actor;
- },
- get _transport() {
- return this.client._transport;
- },
-
- /**
- * Attach to a thread actor.
- *
- * @param object options
- * Configuration options.
- * - useSourceMaps: whether to use source maps or not.
- * @param function onResponse
- * Called with the response packet and a ThreadClient
- * (which will be undefined on error).
- */
- attachThread: function (options = {}, onResponse = noop) {
- if (this.thread) {
- DevToolsUtils.executeSoon(() => onResponse({}, this.thread));
- return promise.resolve([{}, this.thread]);
- }
-
- let packet = {
- to: this._threadActor,
- type: "attach",
- options,
- };
- return this.request(packet).then(response => {
- if (!response.error) {
- this.thread = new ThreadClient(this, this._threadActor);
- this.client.registerClient(this.thread);
- }
- onResponse(response, this.thread);
- return [response, this.thread];
- });
- },
-
- /**
- * Detach the client from the tab actor.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- detach: DebuggerClient.requester({
- type: "detach"
- }, {
- before: function (packet) {
- if (this.thread) {
- this.thread.detach();
- }
- return packet;
- },
- after: function (response) {
- this.client.unregisterClient(this);
- return response;
- },
- }),
-
- /**
- * Bring the window to the front.
- */
- focus: DebuggerClient.requester({
- type: "focus"
- }, {}),
-
- /**
- * Reload the page in this tab.
- *
- * @param [optional] object options
- * An object with a `force` property indicating whether or not
- * this reload should skip the cache
- */
- reload: function (options = { force: false }) {
- return this._reload(options);
- },
- _reload: DebuggerClient.requester({
- type: "reload",
- options: arg(0)
- }),
-
- /**
- * Navigate to another URL.
- *
- * @param string url
- * The URL to navigate to.
- */
- navigateTo: DebuggerClient.requester({
- type: "navigateTo",
- url: arg(0)
- }),
-
- /**
- * Reconfigure the tab actor.
- *
- * @param object options
- * A dictionary object of the new options to use in the tab actor.
- * @param function onResponse
- * Called with the response packet.
- */
- reconfigure: DebuggerClient.requester({
- type: "reconfigure",
- options: arg(0)
- }),
-
- listWorkers: DebuggerClient.requester({
- type: "listWorkers"
- }),
-
- attachWorker: function (workerActor, onResponse) {
- return this.client.attachWorker(workerActor, onResponse);
- },
-};
-
-eventSource(TabClient.prototype);
-
-function WorkerClient(client, form) {
- this.client = client;
- this._actor = form.from;
- this._isClosed = false;
- this._url = form.url;
-
- this._onClose = this._onClose.bind(this);
-
- this.addListener("close", this._onClose);
-
- this.traits = {};
-}
-
-WorkerClient.prototype = {
- get _transport() {
- return this.client._transport;
- },
-
- get request() {
- return this.client.request;
- },
-
- get actor() {
- return this._actor;
- },
-
- get url() {
- return this._url;
- },
-
- get isClosed() {
- return this._isClosed;
- },
-
- detach: DebuggerClient.requester({ type: "detach" }, {
- after: function (response) {
- if (this.thread) {
- this.client.unregisterClient(this.thread);
- }
- this.client.unregisterClient(this);
- return response;
- },
- }),
-
- attachThread: function (options = {}, onResponse = noop) {
- if (this.thread) {
- let response = [{
- type: "connected",
- threadActor: this.thread._actor,
- consoleActor: this.consoleActor,
- }, this.thread];
- DevToolsUtils.executeSoon(() => onResponse(response));
- return response;
- }
-
- // The connect call on server doesn't attach the thread as of version 44.
- return this.request({
- to: this._actor,
- type: "connect",
- options,
- }).then(connectResponse => {
- if (connectResponse.error) {
- onResponse(connectResponse, null);
- return [connectResponse, null];
- }
-
- return this.request({
- to: connectResponse.threadActor,
- type: "attach",
- options,
- }).then(attachResponse => {
- if (attachResponse.error) {
- onResponse(attachResponse, null);
- }
-
- this.thread = new ThreadClient(this, connectResponse.threadActor);
- this.consoleActor = connectResponse.consoleActor;
- this.client.registerClient(this.thread);
-
- onResponse(connectResponse, this.thread);
- return [connectResponse, this.thread];
- });
- }, error => {
- onResponse(error, null);
- });
- },
-
- _onClose: function () {
- this.removeListener("close", this._onClose);
-
- if (this.thread) {
- this.client.unregisterClient(this.thread);
- }
- this.client.unregisterClient(this);
- this._isClosed = true;
- },
-
- reconfigure: function () {
- return Promise.resolve();
- },
-
- events: ["close"]
-};
-
-eventSource(WorkerClient.prototype);
-
-function AddonClient(client, actor) {
- this._client = client;
- this._actor = actor;
- this.request = this._client.request;
- this.events = [];
-}
-
-AddonClient.prototype = {
- get actor() {
- return this._actor;
- },
- get _transport() {
- return this._client._transport;
- },
-
- /**
- * Detach the client from the addon actor.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- detach: DebuggerClient.requester({
- type: "detach"
- }, {
- after: function (response) {
- if (this._client.activeAddon === this) {
- this._client.activeAddon = null;
- }
- this._client.unregisterClient(this);
- return response;
- },
- })
-};
-
-/**
- * A RootClient object represents a root actor on the server. Each
- * DebuggerClient keeps a RootClient instance representing the root actor
- * for the initial connection; DebuggerClient's 'listTabs' and
- * 'listChildProcesses' methods forward to that root actor.
- *
- * @param client object
- * The client connection to which this actor belongs.
- * @param greeting string
- * The greeting packet from the root actor we're to represent.
- *
- * Properties of a RootClient instance:
- *
- * @property actor string
- * The name of this child's root actor.
- * @property applicationType string
- * The application type, as given in the root actor's greeting packet.
- * @property traits object
- * The traits object, as given in the root actor's greeting packet.
- */
-function RootClient(client, greeting) {
- this._client = client;
- this.actor = greeting.from;
- this.applicationType = greeting.applicationType;
- this.traits = greeting.traits;
-}
-exports.RootClient = RootClient;
-
-RootClient.prototype = {
- constructor: RootClient,
-
- /**
- * Gets the "root" form, which lists all the global actors that affect the entire
- * browser. This can replace usages of `listTabs` that only wanted the global actors
- * and didn't actually care about tabs.
- */
- getRoot: DebuggerClient.requester({ type: "getRoot" }),
-
- /**
- * List the open tabs.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- listTabs: DebuggerClient.requester({ type: "listTabs" }),
-
- /**
- * List the installed addons.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- listAddons: DebuggerClient.requester({ type: "listAddons" }),
-
- /**
- * List the registered workers.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- listWorkers: DebuggerClient.requester({ type: "listWorkers" }),
-
- /**
- * List the registered service workers.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- listServiceWorkerRegistrations: DebuggerClient.requester({
- type: "listServiceWorkerRegistrations"
- }),
-
- /**
- * List the running processes.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- listProcesses: DebuggerClient.requester({ type: "listProcesses" }),
-
- /**
- * Fetch the TabActor for the currently selected tab, or for a specific
- * tab given as first parameter.
- *
- * @param [optional] object filter
- * A dictionary object with following optional attributes:
- * - outerWindowID: used to match tabs in parent process
- * - tabId: used to match tabs in child processes
- * - tab: a reference to xul:tab element
- * If nothing is specified, returns the actor for the currently
- * selected tab.
- */
- getTab: function (filter) {
- let packet = {
- to: this.actor,
- type: "getTab"
- };
-
- if (filter) {
- if (typeof (filter.outerWindowID) == "number") {
- packet.outerWindowID = filter.outerWindowID;
- } else if (typeof (filter.tabId) == "number") {
- packet.tabId = filter.tabId;
- } else if ("tab" in filter) {
- let browser = filter.tab.linkedBrowser;
- if (browser.frameLoader.tabParent) {
- // Tabs in child process
- packet.tabId = browser.frameLoader.tabParent.tabId;
- } else if (browser.outerWindowID) {
- // <xul:browser> tabs in parent process
- packet.outerWindowID = browser.outerWindowID;
- } else {
- // <iframe mozbrowser> tabs in parent process
- let windowUtils = browser.contentWindow
- .QueryInterface(Ci.nsIInterfaceRequestor)
- .getInterface(Ci.nsIDOMWindowUtils);
- packet.outerWindowID = windowUtils.outerWindowID;
- }
- } else {
- // Throw if a filter object have been passed but without
- // any clearly idenfified filter.
- throw new Error("Unsupported argument given to getTab request");
- }
- }
-
- return this.request(packet);
- },
-
- /**
- * Fetch the WindowActor for a specific window, like a browser window in
- * Firefox, but it can be used to reach any window in the process.
- *
- * @param number outerWindowID
- * The outerWindowID of the top level window you are looking for.
- */
- getWindow: function ({ outerWindowID }) {
- if (!outerWindowID) {
- throw new Error("Must specify outerWindowID");
- }
-
- let packet = {
- to: this.actor,
- type: "getWindow",
- outerWindowID,
- };
-
- return this.request(packet);
- },
-
- /**
- * Description of protocol's actors and methods.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- protocolDescription: DebuggerClient.requester({ type: "protocolDescription" }),
-
- /*
- * Methods constructed by DebuggerClient.requester require these forwards
- * on their 'this'.
- */
- get _transport() {
- return this._client._transport;
- },
- get request() {
- return this._client.request;
- }
-};
-
-/**
- * Creates a thread client for the remote debugging protocol server. This client
- * is a front to the thread actor created in the server side, hiding the
- * protocol details in a traditional JavaScript API.
- *
- * @param client DebuggerClient|TabClient
- * The parent of the thread (tab for tab-scoped debuggers, DebuggerClient
- * for chrome debuggers).
- * @param actor string
- * The actor ID for this thread.
- */
-function ThreadClient(client, actor) {
- this._parent = client;
- this.client = client instanceof DebuggerClient ? client : client.client;
- this._actor = actor;
- this._frameCache = [];
- this._scriptCache = {};
- this._pauseGrips = {};
- this._threadGrips = {};
- this.request = this.client.request;
-}
-
-ThreadClient.prototype = {
- _state: "paused",
- get state() {
- return this._state;
- },
- get paused() {
- return this._state === "paused";
- },
-
- _pauseOnExceptions: false,
- _ignoreCaughtExceptions: false,
- _pauseOnDOMEvents: null,
-
- _actor: null,
- get actor() {
- return this._actor;
- },
-
- get _transport() {
- return this.client._transport;
- },
-
- _assertPaused: function (command) {
- if (!this.paused) {
- throw Error(command + " command sent while not paused. Currently " + this._state);
- }
- },
-
- /**
- * Resume a paused thread. If the optional limit parameter is present, then
- * the thread will also pause when that limit is reached.
- *
- * @param [optional] object limit
- * An object with a type property set to the appropriate limit (next,
- * step, or finish) per the remote debugging protocol specification.
- * Use null to specify no limit.
- * @param function onResponse
- * Called with the response packet.
- */
- _doResume: DebuggerClient.requester({
- type: "resume",
- resumeLimit: arg(0)
- }, {
- before: function (packet) {
- this._assertPaused("resume");
-
- // Put the client in a tentative "resuming" state so we can prevent
- // further requests that should only be sent in the paused state.
- this._previousState = this._state;
- this._state = "resuming";
-
- if (this._pauseOnExceptions) {
- packet.pauseOnExceptions = this._pauseOnExceptions;
- }
- if (this._ignoreCaughtExceptions) {
- packet.ignoreCaughtExceptions = this._ignoreCaughtExceptions;
- }
- if (this._pauseOnDOMEvents) {
- packet.pauseOnDOMEvents = this._pauseOnDOMEvents;
- }
- return packet;
- },
- after: function (response) {
- if (response.error && this._state == "resuming") {
- // There was an error resuming, update the state to the new one
- // reported by the server, if given (only on wrongState), otherwise
- // reset back to the previous state.
- if (response.state) {
- this._state = ThreadStateTypes[response.state];
- } else {
- this._state = this._previousState;
- }
- }
- delete this._previousState;
- return response;
- },
- }),
-
- /**
- * Reconfigure the thread actor.
- *
- * @param object options
- * A dictionary object of the new options to use in the thread actor.
- * @param function onResponse
- * Called with the response packet.
- */
- reconfigure: DebuggerClient.requester({
- type: "reconfigure",
- options: arg(0)
- }),
-
- /**
- * Resume a paused thread.
- */
- resume: function (onResponse) {
- return this._doResume(null, onResponse);
- },
-
- /**
- * Resume then pause without stepping.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- resumeThenPause: function (onResponse) {
- return this._doResume({ type: "break" }, onResponse);
- },
-
- /**
- * Step over a function call.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- stepOver: function (onResponse) {
- return this._doResume({ type: "next" }, onResponse);
- },
-
- /**
- * Step into a function call.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- stepIn: function (onResponse) {
- return this._doResume({ type: "step" }, onResponse);
- },
-
- /**
- * Step out of a function call.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- stepOut: function (onResponse) {
- return this._doResume({ type: "finish" }, onResponse);
- },
-
- /**
- * Immediately interrupt a running thread.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- interrupt: function (onResponse) {
- return this._doInterrupt(null, onResponse);
- },
-
- /**
- * Pause execution right before the next JavaScript bytecode is executed.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- breakOnNext: function (onResponse) {
- return this._doInterrupt("onNext", onResponse);
- },
-
- /**
- * Interrupt a running thread.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- _doInterrupt: DebuggerClient.requester({
- type: "interrupt",
- when: arg(0)
- }),
-
- /**
- * Enable or disable pausing when an exception is thrown.
- *
- * @param boolean pauseOnExceptions
- * Enables pausing if true, disables otherwise.
- * @param boolean ignoreCaughtExceptions
- * Whether to ignore caught exceptions
- * @param function onResponse
- * Called with the response packet.
- */
- pauseOnExceptions: function (pauseOnExceptions,
- ignoreCaughtExceptions,
- onResponse = noop) {
- this._pauseOnExceptions = pauseOnExceptions;
- this._ignoreCaughtExceptions = ignoreCaughtExceptions;
-
- // Otherwise send the flag using a standard resume request.
- if (!this.paused) {
- return this.interrupt(response => {
- if (response.error) {
- // Can't continue if pausing failed.
- onResponse(response);
- return response;
- }
- return this.resume(onResponse);
- });
- }
-
- onResponse();
- return promise.resolve();
- },
-
- /**
- * Enable pausing when the specified DOM events are triggered. Disabling
- * pausing on an event can be realized by calling this method with the updated
- * array of events that doesn't contain it.
- *
- * @param array|string events
- * An array of strings, representing the DOM event types to pause on,
- * or "*" to pause on all DOM events. Pass an empty array to
- * completely disable pausing on DOM events.
- * @param function onResponse
- * Called with the response packet in a future turn of the event loop.
- */
- pauseOnDOMEvents: function (events, onResponse = noop) {
- this._pauseOnDOMEvents = events;
- // If the debuggee is paused, the value of the array will be communicated in
- // the next resumption. Otherwise we have to force a pause in order to send
- // the array.
- if (this.paused) {
- DevToolsUtils.executeSoon(() => onResponse({}));
- return {};
- }
- return this.interrupt(response => {
- // Can't continue if pausing failed.
- if (response.error) {
- onResponse(response);
- return response;
- }
- return this.resume(onResponse);
- });
- },
-
- /**
- * Send a clientEvaluate packet to the debuggee. Response
- * will be a resume packet.
- *
- * @param string frame
- * The actor ID of the frame where the evaluation should take place.
- * @param string expression
- * The expression that will be evaluated in the scope of the frame
- * above.
- * @param function onResponse
- * Called with the response packet.
- */
- eval: DebuggerClient.requester({
- type: "clientEvaluate",
- frame: arg(0),
- expression: arg(1)
- }, {
- before: function (packet) {
- this._assertPaused("eval");
- // Put the client in a tentative "resuming" state so we can prevent
- // further requests that should only be sent in the paused state.
- this._state = "resuming";
- return packet;
- },
- after: function (response) {
- if (response.error) {
- // There was an error resuming, back to paused state.
- this._state = "paused";
- }
- return response;
- },
- }),
-
- /**
- * Detach from the thread actor.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- detach: DebuggerClient.requester({
- type: "detach"
- }, {
- after: function (response) {
- this.client.unregisterClient(this);
- this._parent.thread = null;
- return response;
- },
- }),
-
- /**
- * Release multiple thread-lifetime object actors. If any pause-lifetime
- * actors are included in the request, a |notReleasable| error will return,
- * but all the thread-lifetime ones will have been released.
- *
- * @param array actors
- * An array with actor IDs to release.
- */
- releaseMany: DebuggerClient.requester({
- type: "releaseMany",
- actors: arg(0),
- }),
-
- /**
- * Promote multiple pause-lifetime object actors to thread-lifetime ones.
- *
- * @param array actors
- * An array with actor IDs to promote.
- */
- threadGrips: DebuggerClient.requester({
- type: "threadGrips",
- actors: arg(0)
- }),
-
- /**
- * Return the event listeners defined on the page.
- *
- * @param onResponse Function
- * Called with the thread's response.
- */
- eventListeners: DebuggerClient.requester({
- type: "eventListeners"
- }),
-
- /**
- * Request the loaded sources for the current thread.
- *
- * @param onResponse Function
- * Called with the thread's response.
- */
- getSources: DebuggerClient.requester({
- type: "sources"
- }),
-
- /**
- * Clear the thread's source script cache. A scriptscleared event
- * will be sent.
- */
- _clearScripts: function () {
- if (Object.keys(this._scriptCache).length > 0) {
- this._scriptCache = {};
- this.emit("scriptscleared");
- }
- },
-
- /**
- * Request frames from the callstack for the current thread.
- *
- * @param start integer
- * The number of the youngest stack frame to return (the youngest
- * frame is 0).
- * @param count integer
- * The maximum number of frames to return, or null to return all
- * frames.
- * @param onResponse function
- * Called with the thread's response.
- */
- getFrames: DebuggerClient.requester({
- type: "frames",
- start: arg(0),
- count: arg(1)
- }),
-
- /**
- * An array of cached frames. Clients can observe the framesadded and
- * framescleared event to keep up to date on changes to this cache,
- * and can fill it using the fillFrames method.
- */
- get cachedFrames() {
- return this._frameCache;
- },
-
- /**
- * true if there are more stack frames available on the server.
- */
- get moreFrames() {
- return this.paused && (!this._frameCache || this._frameCache.length == 0
- || !this._frameCache[this._frameCache.length - 1].oldest);
- },
-
- /**
- * Request the frame environment.
- *
- * @param frameId string
- */
- getEnvironment: function (frameId) {
- return this.request({ to: frameId, type: "getEnvironment" });
- },
-
- /**
- * Ensure that at least total stack frames have been loaded in the
- * ThreadClient's stack frame cache. A framesadded event will be
- * sent when the stack frame cache is updated.
- *
- * @param total number
- * The minimum number of stack frames to be included.
- * @param callback function
- * Optional callback function called when frames have been loaded
- * @returns true if a framesadded notification should be expected.
- */
- fillFrames: function (total, callback = noop) {
- this._assertPaused("fillFrames");
- if (this._frameCache.length >= total) {
- return false;
- }
-
- let numFrames = this._frameCache.length;
-
- this.getFrames(numFrames, total - numFrames, (response) => {
- if (response.error) {
- callback(response);
- return;
- }
-
- let threadGrips = DevToolsUtils.values(this._threadGrips);
-
- for (let i in response.frames) {
- let frame = response.frames[i];
- if (!frame.where.source) {
- // Older servers use urls instead, so we need to resolve
- // them to source actors
- for (let grip of threadGrips) {
- if (grip instanceof SourceClient && grip.url === frame.url) {
- frame.where.source = grip._form;
- }
- }
- }
-
- this._frameCache[frame.depth] = frame;
- }
-
- // If we got as many frames as we asked for, there might be more
- // frames available.
- this.emit("framesadded");
-
- callback(response);
- });
-
- return true;
- },
-
- /**
- * Clear the thread's stack frame cache. A framescleared event
- * will be sent.
- */
- _clearFrames: function () {
- if (this._frameCache.length > 0) {
- this._frameCache = [];
- this.emit("framescleared");
- }
- },
-
- /**
- * Return a ObjectClient object for the given object grip.
- *
- * @param grip object
- * A pause-lifetime object grip returned by the protocol.
- */
- pauseGrip: function (grip) {
- if (grip.actor in this._pauseGrips) {
- return this._pauseGrips[grip.actor];
- }
-
- let client = new ObjectClient(this.client, grip);
- this._pauseGrips[grip.actor] = client;
- return client;
- },
-
- /**
- * Get or create a long string client, checking the grip client cache if it
- * already exists.
- *
- * @param grip Object
- * The long string grip returned by the protocol.
- * @param gripCacheName String
- * The property name of the grip client cache to check for existing
- * clients in.
- */
- _longString: function (grip, gripCacheName) {
- if (grip.actor in this[gripCacheName]) {
- return this[gripCacheName][grip.actor];
- }
-
- let client = new LongStringClient(this.client, grip);
- this[gripCacheName][grip.actor] = client;
- return client;
- },
-
- /**
- * Return an instance of LongStringClient for the given long string grip that
- * is scoped to the current pause.
- *
- * @param grip Object
- * The long string grip returned by the protocol.
- */
- pauseLongString: function (grip) {
- return this._longString(grip, "_pauseGrips");
- },
-
- /**
- * Return an instance of LongStringClient for the given long string grip that
- * is scoped to the thread lifetime.
- *
- * @param grip Object
- * The long string grip returned by the protocol.
- */
- threadLongString: function (grip) {
- return this._longString(grip, "_threadGrips");
- },
-
- /**
- * Get or create an ArrayBuffer client, checking the grip client cache if it
- * already exists.
- *
- * @param grip Object
- * The ArrayBuffer grip returned by the protocol.
- * @param gripCacheName String
- * The property name of the grip client cache to check for existing
- * clients in.
- */
- _arrayBuffer: function (grip, gripCacheName) {
- if (grip.actor in this[gripCacheName]) {
- return this[gripCacheName][grip.actor];
- }
-
- let client = new ArrayBufferClient(this.client, grip);
- this[gripCacheName][grip.actor] = client;
- return client;
- },
-
- /**
- * Return an instance of ArrayBufferClient for the given ArrayBuffer grip that
- * is scoped to the thread lifetime.
- *
- * @param grip Object
- * The ArrayBuffer grip returned by the protocol.
- */
- threadArrayBuffer: function (grip) {
- return this._arrayBuffer(grip, "_threadGrips");
- },
-
- /**
- * Clear and invalidate all the grip clients from the given cache.
- *
- * @param gripCacheName
- * The property name of the grip cache we want to clear.
- */
- _clearObjectClients: function (gripCacheName) {
- for (let id in this[gripCacheName]) {
- this[gripCacheName][id].valid = false;
- }
- this[gripCacheName] = {};
- },
-
- /**
- * Invalidate pause-lifetime grip clients and clear the list of current grip
- * clients.
- */
- _clearPauseGrips: function () {
- this._clearObjectClients("_pauseGrips");
- },
-
- /**
- * Invalidate thread-lifetime grip clients and clear the list of current grip
- * clients.
- */
- _clearThreadGrips: function () {
- this._clearObjectClients("_threadGrips");
- },
-
- /**
- * Handle thread state change by doing necessary cleanup and notifying all
- * registered listeners.
- */
- _onThreadState: function (packet) {
- this._state = ThreadStateTypes[packet.type];
- // The debugger UI may not be initialized yet so we want to keep
- // the packet around so it knows what to pause state to display
- // when it's initialized
- this._lastPausePacket = packet.type === "resumed" ? null : packet;
- this._clearFrames();
- this._clearPauseGrips();
- packet.type === ThreadStateTypes.detached && this._clearThreadGrips();
- this.client._eventsEnabled && this.emit(packet.type, packet);
- },
-
- getLastPausePacket: function () {
- return this._lastPausePacket;
- },
-
- /**
- * Return an EnvironmentClient instance for the given environment actor form.
- */
- environment: function (form) {
- return new EnvironmentClient(this.client, form);
- },
-
- /**
- * Return an instance of SourceClient for the given source actor form.
- */
- source: function (form) {
- if (form.actor in this._threadGrips) {
- return this._threadGrips[form.actor];
- }
-
- this._threadGrips[form.actor] = new SourceClient(this, form);
- return this._threadGrips[form.actor];
- },
-
- /**
- * Request the prototype and own properties of mutlipleObjects.
- *
- * @param onResponse function
- * Called with the request's response.
- * @param actors [string]
- * List of actor ID of the queried objects.
- */
- getPrototypesAndProperties: DebuggerClient.requester({
- type: "prototypesAndProperties",
- actors: arg(0)
- }),
-
- events: ["newSource"]
-};
-
-eventSource(ThreadClient.prototype);
-
-/**
- * Creates a tracing profiler client for the remote debugging protocol
- * server. This client is a front to the trace actor created on the
- * server side, hiding the protocol details in a traditional
- * JavaScript API.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param actor string
- * The actor ID for this thread.
- */
-function TraceClient(client, actor) {
- this._client = client;
- this._actor = actor;
- this._activeTraces = new Set();
- this._waitingPackets = new Map();
- this._expectedPacket = 0;
- this.request = this._client.request;
- this.events = [];
-}
-
-TraceClient.prototype = {
- get actor() {
- return this._actor;
- },
- get tracing() {
- return this._activeTraces.size > 0;
- },
-
- get _transport() {
- return this._client._transport;
- },
-
- /**
- * Detach from the trace actor.
- */
- detach: DebuggerClient.requester({
- type: "detach"
- }, {
- after: function (response) {
- this._client.unregisterClient(this);
- return response;
- },
- }),
-
- /**
- * Start a new trace.
- *
- * @param trace [string]
- * An array of trace types to be recorded by the new trace.
- *
- * @param name string
- * The name of the new trace.
- *
- * @param onResponse function
- * Called with the request's response.
- */
- startTrace: DebuggerClient.requester({
- type: "startTrace",
- name: arg(1),
- trace: arg(0)
- }, {
- after: function (response) {
- if (response.error) {
- return response;
- }
-
- if (!this.tracing) {
- this._waitingPackets.clear();
- this._expectedPacket = 0;
- }
- this._activeTraces.add(response.name);
-
- return response;
- },
- }),
-
- /**
- * End a trace. If a name is provided, stop the named
- * trace. Otherwise, stop the most recently started trace.
- *
- * @param name string
- * The name of the trace to stop.
- *
- * @param onResponse function
- * Called with the request's response.
- */
- stopTrace: DebuggerClient.requester({
- type: "stopTrace",
- name: arg(0)
- }, {
- after: function (response) {
- if (response.error) {
- return response;
- }
-
- this._activeTraces.delete(response.name);
-
- return response;
- },
- })
-};
-
-/**
- * Grip clients are used to retrieve information about the relevant object.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param grip object
- * A pause-lifetime object grip returned by the protocol.
- */
-function ObjectClient(client, grip) {
- this._grip = grip;
- this._client = client;
- this.request = this._client.request;
-}
-exports.ObjectClient = ObjectClient;
-
-ObjectClient.prototype = {
- get actor() {
- return this._grip.actor;
- },
- get _transport() {
- return this._client._transport;
- },
-
- valid: true,
-
- get isFrozen() {
- return this._grip.frozen;
- },
- get isSealed() {
- return this._grip.sealed;
- },
- get isExtensible() {
- return this._grip.extensible;
- },
-
- getDefinitionSite: DebuggerClient.requester({
- type: "definitionSite"
- }, {
- before: function (packet) {
- if (this._grip.class != "Function") {
- throw new Error("getDefinitionSite is only valid for function grips.");
- }
- return packet;
- }
- }),
-
- /**
- * Request the names of a function's formal parameters.
- *
- * @param onResponse function
- * Called with an object of the form:
- * { parameterNames:[<parameterName>, ...] }
- * where each <parameterName> is the name of a parameter.
- */
- getParameterNames: DebuggerClient.requester({
- type: "parameterNames"
- }, {
- before: function (packet) {
- if (this._grip.class !== "Function") {
- throw new Error("getParameterNames is only valid for function grips.");
- }
- return packet;
- },
- }),
-
- /**
- * Request the names of the properties defined on the object and not its
- * prototype.
- *
- * @param onResponse function Called with the request's response.
- */
- getOwnPropertyNames: DebuggerClient.requester({
- type: "ownPropertyNames"
- }),
-
- /**
- * Request the prototype and own properties of the object.
- *
- * @param onResponse function Called with the request's response.
- */
- getPrototypeAndProperties: DebuggerClient.requester({
- type: "prototypeAndProperties"
- }),
-
- /**
- * Request a PropertyIteratorClient instance to ease listing
- * properties for this object.
- *
- * @param options Object
- * A dictionary object with various boolean attributes:
- * - ignoreIndexedProperties Boolean
- * If true, filters out Array items.
- * e.g. properties names between `0` and `object.length`.
- * - ignoreNonIndexedProperties Boolean
- * If true, filters out items that aren't array items
- * e.g. properties names that are not a number between `0`
- * and `object.length`.
- * - sort Boolean
- * If true, the iterator will sort the properties by name
- * before dispatching them.
- * @param onResponse function Called with the client instance.
- */
- enumProperties: DebuggerClient.requester({
- type: "enumProperties",
- options: arg(0)
- }, {
- after: function (response) {
- if (response.iterator) {
- return { iterator: new PropertyIteratorClient(this._client, response.iterator) };
- }
- return response;
- },
- }),
-
- /**
- * Request a PropertyIteratorClient instance to enumerate entries in a
- * Map/Set-like object.
- *
- * @param onResponse function Called with the request's response.
- */
- enumEntries: DebuggerClient.requester({
- type: "enumEntries"
- }, {
- before: function (packet) {
- if (!["Map", "WeakMap", "Set", "WeakSet"].includes(this._grip.class)) {
- throw new Error("enumEntries is only valid for Map/Set-like grips.");
- }
- return packet;
- },
- after: function (response) {
- if (response.iterator) {
- return {
- iterator: new PropertyIteratorClient(this._client, response.iterator)
- };
- }
- return response;
- }
- }),
-
- /**
- * Request a SymbolIteratorClient instance to enumerate symbols in an object.
- *
- * @param onResponse function Called with the request's response.
- */
- enumSymbols: DebuggerClient.requester({
- type: "enumSymbols"
- }, {
- before: function (packet) {
- if (this._grip.type !== "object") {
- throw new Error("enumSymbols is only valid for objects grips.");
- }
- return packet;
- },
- after: function (response) {
- if (response.iterator) {
- return {
- iterator: new SymbolIteratorClient(this._client, response.iterator)
- };
- }
- return response;
- }
- }),
-
- /**
- * Request the property descriptor of the object's specified property.
- *
- * @param name string The name of the requested property.
- * @param onResponse function Called with the request's response.
- */
- getProperty: DebuggerClient.requester({
- type: "property",
- name: arg(0)
- }),
-
- /**
- * Request the prototype of the object.
- *
- * @param onResponse function Called with the request's response.
- */
- getPrototype: DebuggerClient.requester({
- type: "prototype"
- }),
-
- /**
- * Request the display string of the object.
- *
- * @param onResponse function Called with the request's response.
- */
- getDisplayString: DebuggerClient.requester({
- type: "displayString"
- }),
-
- /**
- * Request the scope of the object.
- *
- * @param onResponse function Called with the request's response.
- */
- getScope: DebuggerClient.requester({
- type: "scope"
- }, {
- before: function (packet) {
- if (this._grip.class !== "Function") {
- throw new Error("scope is only valid for function grips.");
- }
- return packet;
- },
- }),
-
- /**
- * Request the promises directly depending on the current promise.
- */
- getDependentPromises: DebuggerClient.requester({
- type: "dependentPromises"
- }, {
- before: function (packet) {
- if (this._grip.class !== "Promise") {
- throw new Error("getDependentPromises is only valid for promise " +
- "grips.");
- }
- return packet;
- }
- }),
-
- /**
- * Request the stack to the promise's allocation point.
- */
- getPromiseAllocationStack: DebuggerClient.requester({
- type: "allocationStack"
- }, {
- before: function (packet) {
- if (this._grip.class !== "Promise") {
- throw new Error("getAllocationStack is only valid for promise grips.");
- }
- return packet;
- }
- }),
-
- /**
- * Request the stack to the promise's fulfillment point.
- */
- getPromiseFulfillmentStack: DebuggerClient.requester({
- type: "fulfillmentStack"
- }, {
- before: function (packet) {
- if (this._grip.class !== "Promise") {
- throw new Error("getPromiseFulfillmentStack is only valid for " +
- "promise grips.");
- }
- return packet;
- }
- }),
-
- /**
- * Request the stack to the promise's rejection point.
- */
- getPromiseRejectionStack: DebuggerClient.requester({
- type: "rejectionStack"
- }, {
- before: function (packet) {
- if (this._grip.class !== "Promise") {
- throw new Error("getPromiseRejectionStack is only valid for " +
- "promise grips.");
- }
- return packet;
- }
- })
-};
-
-/**
- * A PropertyIteratorClient provides a way to access to property names and
- * values of an object efficiently, slice by slice.
- * Note that the properties can be sorted in the backend,
- * this is controled while creating the PropertyIteratorClient
- * from ObjectClient.enumProperties.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param grip Object
- * A PropertyIteratorActor grip returned by the protocol via
- * TabActor.enumProperties request.
- */
-function PropertyIteratorClient(client, grip) {
- this._grip = grip;
- this._client = client;
- this.request = this._client.request;
-}
-
-PropertyIteratorClient.prototype = {
- get actor() {
- return this._grip.actor;
- },
-
- /**
- * Get the total number of properties available in the iterator.
- */
- get count() {
- return this._grip.count;
- },
-
- /**
- * Get one or more property names that correspond to the positions in the
- * indexes parameter.
- *
- * @param indexes Array
- * An array of property indexes.
- * @param callback Function
- * The function called when we receive the property names.
- */
- names: DebuggerClient.requester({
- type: "names",
- indexes: arg(0)
- }, {}),
-
- /**
- * Get a set of following property value(s).
- *
- * @param start Number
- * The index of the first property to fetch.
- * @param count Number
- * The number of properties to fetch.
- * @param callback Function
- * The function called when we receive the property values.
- */
- slice: DebuggerClient.requester({
- type: "slice",
- start: arg(0),
- count: arg(1)
- }, {}),
-
- /**
- * Get all the property values.
- *
- * @param callback Function
- * The function called when we receive the property values.
- */
- all: DebuggerClient.requester({
- type: "all"
- }, {}),
-};
-
-/**
- * A SymbolIteratorClient provides a way to access to symbols
- * of an object efficiently, slice by slice.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param grip Object
- * A SymbolIteratorActor grip returned by the protocol via
- * TabActor.enumSymbols request.
- */
-function SymbolIteratorClient(client, grip) {
- this._grip = grip;
- this._client = client;
- this.request = this._client.request;
-}
-
-SymbolIteratorClient.prototype = {
- get actor() {
- return this._grip.actor;
- },
-
- /**
- * Get the total number of symbols available in the iterator.
- */
- get count() {
- return this._grip.count;
- },
-
- /**
- * Get a set of following symbols.
- *
- * @param start Number
- * The index of the first symbol to fetch.
- * @param count Number
- * The number of symbols to fetch.
- * @param callback Function
- * The function called when we receive the symbols.
- */
- slice: DebuggerClient.requester({
- type: "slice",
- start: arg(0),
- count: arg(1)
- }, {}),
-
- /**
- * Get all the symbols.
- *
- * @param callback Function
- * The function called when we receive the symbols.
- */
- all: DebuggerClient.requester({
- type: "all"
- }, {}),
-};
-
-/**
- * A ArrayBufferClient provides a way to access ArrayBuffer from the
- * debugger server.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param grip Object
- * A pause-lifetime ArrayBuffer grip returned by the protocol.
- */
-function ArrayBufferClient(client, grip) {
- this._grip = grip;
- this._client = client;
- this.request = this._client.request;
-}
-ArrayBufferClient.prototype = {
- get actor() {
- return this._grip.actor;
- },
- get length() {
- return this._grip.length;
- },
- get _transport() {
- return this._client._transport;
- },
-
- valid: true,
-
- slice: DebuggerClient.requester({
- type: "slice",
- start: arg(0),
- count: arg(1)
- }),
-};
-
-/**
- * A LongStringClient provides a way to access "very long" strings from the
- * debugger server.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param grip Object
- * A pause-lifetime long string grip returned by the protocol.
- */
-function LongStringClient(client, grip) {
- this._grip = grip;
- this._client = client;
- this.request = this._client.request;
-}
-exports.LongStringClient = LongStringClient;
-
-LongStringClient.prototype = {
- get actor() {
- return this._grip.actor;
- },
- get length() {
- return this._grip.length;
- },
- get initial() {
- return this._grip.initial;
- },
- get _transport() {
- return this._client._transport;
- },
-
- valid: true,
-
- /**
- * Get the substring of this LongString from start to end.
- *
- * @param start Number
- * The starting index.
- * @param end Number
- * The ending index.
- * @param callback Function
- * The function called when we receive the substring.
- */
- substring: DebuggerClient.requester({
- type: "substring",
- start: arg(0),
- end: arg(1)
- }),
-};
-
-/**
- * A SourceClient provides a way to access the source text of a script.
- *
- * @param client ThreadClient
- * The thread client parent.
- * @param form Object
- * The form sent across the remote debugging protocol.
- */
-function SourceClient(client, form) {
- this._form = form;
- this._isBlackBoxed = form.isBlackBoxed;
- this._isPrettyPrinted = form.isPrettyPrinted;
- this._activeThread = client;
- this._client = client.client;
-}
-
-SourceClient.prototype = {
- get _transport() {
- return this._client._transport;
- },
- get isBlackBoxed() {
- return this._isBlackBoxed;
- },
- get isPrettyPrinted() {
- return this._isPrettyPrinted;
- },
- get actor() {
- return this._form.actor;
- },
- get request() {
- return this._client.request;
- },
- get url() {
- return this._form.url;
- },
-
- /**
- * Black box this SourceClient's source.
- *
- * @param callback Function
- * The callback function called when we receive the response from the server.
- */
- blackBox: DebuggerClient.requester({
- type: "blackbox"
- }, {
- after: function (response) {
- if (!response.error) {
- this._isBlackBoxed = true;
- if (this._activeThread) {
- this._activeThread.emit("blackboxchange", this);
- }
- }
- return response;
- }
- }),
-
- /**
- * Un-black box this SourceClient's source.
- *
- * @param callback Function
- * The callback function called when we receive the response from the server.
- */
- unblackBox: DebuggerClient.requester({
- type: "unblackbox"
- }, {
- after: function (response) {
- if (!response.error) {
- this._isBlackBoxed = false;
- if (this._activeThread) {
- this._activeThread.emit("blackboxchange", this);
- }
- }
- return response;
- }
- }),
-
- /**
- * Get Executable Lines from a source
- *
- * @param callback Function
- * The callback function called when we receive the response from the server.
- */
- getExecutableLines: function (cb = noop) {
- let packet = {
- to: this._form.actor,
- type: "getExecutableLines"
- };
-
- return this._client.request(packet).then(res => {
- cb(res.lines);
- return res.lines;
- });
- },
-
- /**
- * Get a long string grip for this SourceClient's source.
- */
- source: function (callback = noop) {
- let packet = {
- to: this._form.actor,
- type: "source"
- };
- return this._client.request(packet).then(response => {
- return this._onSourceResponse(response, callback);
- });
- },
-
- /**
- * Pretty print this source's text.
- */
- prettyPrint: function (indent, callback = noop) {
- const packet = {
- to: this._form.actor,
- type: "prettyPrint",
- indent
- };
- return this._client.request(packet).then(response => {
- if (!response.error) {
- this._isPrettyPrinted = true;
- this._activeThread._clearFrames();
- this._activeThread.emit("prettyprintchange", this);
- }
- return this._onSourceResponse(response, callback);
- });
- },
-
- /**
- * Stop pretty printing this source's text.
- */
- disablePrettyPrint: function (callback = noop) {
- const packet = {
- to: this._form.actor,
- type: "disablePrettyPrint"
- };
- return this._client.request(packet).then(response => {
- if (!response.error) {
- this._isPrettyPrinted = false;
- this._activeThread._clearFrames();
- this._activeThread.emit("prettyprintchange", this);
- }
- return this._onSourceResponse(response, callback);
- });
- },
-
- _onSourceResponse: function (response, callback) {
- if (response.error) {
- callback(response);
- return response;
- }
-
- if (typeof response.source === "string") {
- callback(response);
- return response;
- }
-
- let { contentType, source } = response;
- if (source.type === "arrayBuffer") {
- let arrayBuffer = this._activeThread.threadArrayBuffer(source);
- return arrayBuffer.slice(0, arrayBuffer.length).then(function (resp) {
- if (resp.error) {
- callback(resp);
- return resp;
- }
- // Keeping str as a string, ArrayBuffer/Uint8Array will not survive
- // setIn/mergeIn operations.
- const str = atob(resp.encoded);
- let newResponse = {
- source: {
- binary: str,
- toString: () => "[wasm]",
- },
- contentType,
- };
- callback(newResponse);
- return newResponse;
- });
- }
-
- let longString = this._activeThread.threadLongString(source);
- return longString.substring(0, longString.length).then(function (resp) {
- if (resp.error) {
- callback(resp);
- return resp;
- }
-
- let newResponse = {
- source: resp.substring,
- contentType: contentType
- };
- callback(newResponse);
- return newResponse;
- });
- },
-
- /**
- * Request to set a breakpoint in the specified location.
- *
- * @param object location
- * The location and condition of the breakpoint in
- * the form of { line[, column, condition] }.
- * @param function onResponse
- * Called with the thread's response.
- */
- setBreakpoint: function ({ line, column, condition, noSliding }, onResponse = noop) {
- // A helper function that sets the breakpoint.
- let doSetBreakpoint = callback => {
- let root = this._client.mainRoot;
- let location = {
- line,
- column,
- };
-
- let packet = {
- to: this.actor,
- type: "setBreakpoint",
- location,
- condition,
- noSliding,
- };
-
- // Backwards compatibility: send the breakpoint request to the
- // thread if the server doesn't support Debugger.Source actors.
- if (!root.traits.debuggerSourceActors) {
- packet.to = this._activeThread.actor;
- packet.location.url = this.url;
- }
-
- return this._client.request(packet).then(response => {
- // Ignoring errors, since the user may be setting a breakpoint in a
- // dead script that will reappear on a page reload.
- let bpClient;
- if (response.actor) {
- bpClient = new BreakpointClient(
- this._client,
- this,
- response.actor,
- location,
- root.traits.conditionalBreakpoints ? condition : undefined
- );
- }
- onResponse(response, bpClient);
- if (callback) {
- callback();
- }
- return [response, bpClient];
- });
- };
-
- // If the debuggee is paused, just set the breakpoint.
- if (this._activeThread.paused) {
- return doSetBreakpoint();
- }
- // Otherwise, force a pause in order to set the breakpoint.
- return this._activeThread.interrupt().then(response => {
- if (response.error) {
- // Can't set the breakpoint if pausing failed.
- onResponse(response);
- return response;
- }
-
- const { type, why } = response;
- const cleanUp = type == "paused" && why.type == "interrupted"
- ? () => this._activeThread.resume()
- : noop;
-
- return doSetBreakpoint(cleanUp);
- });
- }
-};
-
-/**
- * Breakpoint clients are used to remove breakpoints that are no longer used.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param sourceClient SourceClient
- * The source where this breakpoint exists
- * @param actor string
- * The actor ID for this breakpoint.
- * @param location object
- * The location of the breakpoint. This is an object with two properties:
- * url and line.
- * @param condition string
- * The conditional expression of the breakpoint
- */
-function BreakpointClient(client, sourceClient, actor, location, condition) {
- this._client = client;
- this._actor = actor;
- this.location = location;
- this.location.actor = sourceClient.actor;
- this.location.url = sourceClient.url;
- this.source = sourceClient;
- this.request = this._client.request;
-
- // The condition property should only exist if it's a truthy value
- if (condition) {
- this.condition = condition;
- }
-}
-
-BreakpointClient.prototype = {
-
- _actor: null,
- get actor() {
- return this._actor;
- },
- get _transport() {
- return this._client._transport;
- },
-
- /**
- * Remove the breakpoint from the server.
- */
- remove: DebuggerClient.requester({
- type: "delete"
- }),
-
- /**
- * Determines if this breakpoint has a condition
- */
- hasCondition: function () {
- let root = this._client.mainRoot;
- // XXX bug 990137: We will remove support for client-side handling of
- // conditional breakpoints
- if (root.traits.conditionalBreakpoints) {
- return "condition" in this;
- }
- return "conditionalExpression" in this;
- },
-
- /**
- * Get the condition of this breakpoint. Currently we have to
- * support locally emulated conditional breakpoints until the
- * debugger servers are updated (see bug 990137). We used a
- * different property when moving it server-side to ensure that we
- * are testing the right code.
- */
- getCondition: function () {
- let root = this._client.mainRoot;
- if (root.traits.conditionalBreakpoints) {
- return this.condition;
- }
- return this.conditionalExpression;
- },
-
- /**
- * Set the condition of this breakpoint
- */
- setCondition: function (gThreadClient, condition) {
- let root = this._client.mainRoot;
- let deferred = promise.defer();
-
- if (root.traits.conditionalBreakpoints) {
- let info = {
- line: this.location.line,
- column: this.location.column,
- condition: condition
- };
-
- // Remove the current breakpoint and add a new one with the
- // condition.
- this.remove(response => {
- if (response && response.error) {
- deferred.reject(response);
- return;
- }
-
- this.source.setBreakpoint(info, (resp, newBreakpoint) => {
- if (resp && resp.error) {
- deferred.reject(resp);
- } else {
- deferred.resolve(newBreakpoint);
- }
- });
- });
- } else {
- // The property shouldn't even exist if the condition is blank
- if (condition === "") {
- delete this.conditionalExpression;
- } else {
- this.conditionalExpression = condition;
- }
- deferred.resolve(this);
- }
-
- return deferred.promise;
- }
-};
-
-eventSource(BreakpointClient.prototype);
+const {arg, DebuggerClient} = require("./debugger-client");
+const eventSource = require("./event-source");
/**
* Environment clients are used to manipulate the lexical environment actors.
*
* @param client DebuggerClient
* The debugger client parent.
* @param form Object
* The form sent across the remote debugging protocol.
@@ -3419,8 +45,10 @@ EnvironmentClient.prototype = {
assign: DebuggerClient.requester({
type: "assign",
name: arg(0),
value: arg(1)
})
};
eventSource(EnvironmentClient.prototype);
+
+module.exports = EnvironmentClient;
copy from devtools/shared/client/main.js
copy to devtools/shared/client/event-source.js
--- a/devtools/shared/client/main.js
+++ b/devtools/shared/client/event-source.js
@@ -1,28 +1,15 @@
-/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
-/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
-const { Ci, Cu } = require("chrome");
const DevToolsUtils = require("devtools/shared/DevToolsUtils");
-const { getStack, callFunctionWithAsyncStack } = require("devtools/shared/platform/stack");
-
-const promise = Cu.import("resource://devtools/shared/deprecated-sync-thenables.js", {}).Promise;
-
-loader.lazyRequireGetter(this, "EventEmitter", "devtools/shared/event-emitter");
-loader.lazyRequireGetter(this, "WebConsoleClient", "devtools/shared/webconsole/client", true);
-loader.lazyRequireGetter(this, "DebuggerSocket", "devtools/shared/security/socket", true);
-loader.lazyRequireGetter(this, "Authentication", "devtools/shared/security/auth");
-
-const noop = () => {};
/**
* TODO: Get rid of this API in favor of EventTarget (bug 1042642)
*
* Add simple event notification to a prototype object. Any object that has
* some use for event notifications or the observer pattern in general can be
* augmented with the necessary facilities by passing its prototype to this
* function.
@@ -131,3296 +118,9 @@ function eventSource(proto) {
} catch (e) {
// Prevent a bad listener from interfering with the others.
DevToolsUtils.reportException("notify event '" + name + "'", e);
}
}
};
}
-/**
- * Set of protocol messages that affect thread state, and the
- * state the actor is in after each message.
- */
-const ThreadStateTypes = {
- "paused": "paused",
- "resumed": "attached",
- "detached": "detached",
- "running": "attached"
-};
-
-/**
- * Set of protocol messages that are sent by the server without a prior request
- * by the client.
- */
-const UnsolicitedNotifications = {
- "consoleAPICall": "consoleAPICall",
- "eventNotification": "eventNotification",
- "fileActivity": "fileActivity",
- "lastPrivateContextExited": "lastPrivateContextExited",
- "logMessage": "logMessage",
- "networkEvent": "networkEvent",
- "networkEventUpdate": "networkEventUpdate",
- "newGlobal": "newGlobal",
- "newScript": "newScript",
- "tabDetached": "tabDetached",
- "tabListChanged": "tabListChanged",
- "reflowActivity": "reflowActivity",
- "addonListChanged": "addonListChanged",
- "workerListChanged": "workerListChanged",
- "serviceWorkerRegistrationListChanged": "serviceWorkerRegistrationList",
- "tabNavigated": "tabNavigated",
- "frameUpdate": "frameUpdate",
- "pageError": "pageError",
- "documentLoad": "documentLoad",
- "enteredFrame": "enteredFrame",
- "exitedFrame": "exitedFrame",
- "appOpen": "appOpen",
- "appClose": "appClose",
- "appInstall": "appInstall",
- "appUninstall": "appUninstall",
- "evaluationResult": "evaluationResult",
- "newSource": "newSource",
- "updatedSource": "updatedSource",
- "inspectObject": "inspectObject"
-};
-
-/**
- * Set of pause types that are sent by the server and not as an immediate
- * response to a client request.
- */
-const UnsolicitedPauses = {
- "resumeLimit": "resumeLimit",
- "debuggerStatement": "debuggerStatement",
- "breakpoint": "breakpoint",
- "DOMEvent": "DOMEvent",
- "watchpoint": "watchpoint",
- "exception": "exception"
-};
-
-/**
- * Creates a client for the remote debugging protocol server. This client
- * provides the means to communicate with the server and exchange the messages
- * required by the protocol in a traditional JavaScript API.
- */
-const DebuggerClient = exports.DebuggerClient = function (transport) {
- this._transport = transport;
- this._transport.hooks = this;
-
- // Map actor ID to client instance for each actor type.
- this._clients = new Map();
-
- this._pendingRequests = new Map();
- this._activeRequests = new Map();
- this._eventsEnabled = true;
-
- this.traits = {};
-
- this.request = this.request.bind(this);
- this.localTransport = this._transport.onOutputStreamReady === undefined;
-
- /*
- * As the first thing on the connection, expect a greeting packet from
- * the connection's root actor.
- */
- this.mainRoot = null;
- this.expectReply("root", (packet) => {
- this.mainRoot = new RootClient(this, packet);
- this.emit("connected", packet.applicationType, packet.traits);
- });
-};
-
-/**
- * A declarative helper for defining methods that send requests to the server.
- *
- * @param packetSkeleton
- * The form of the packet to send. Can specify fields to be filled from
- * the parameters by using the |arg| function.
- * @param before
- * The function to call before sending the packet. Is passed the packet,
- * and the return value is used as the new packet. The |this| context is
- * the instance of the client object we are defining a method for.
- * @param after
- * The function to call after the response is received. It is passed the
- * response, and the return value is considered the new response that
- * will be passed to the callback. The |this| context is the instance of
- * the client object we are defining a method for.
- * @return Request
- * The `Request` object that is a Promise object and resolves once
- * we receive the response. (See request method for more details)
- */
-DebuggerClient.requester = function (packetSkeleton, config = {}) {
- let { before, after } = config;
- return DevToolsUtils.makeInfallible(function (...args) {
- let outgoingPacket = {
- to: packetSkeleton.to || this.actor
- };
-
- let maxPosition = -1;
- for (let k of Object.keys(packetSkeleton)) {
- if (packetSkeleton[k] instanceof DebuggerClient.Argument) {
- let { position } = packetSkeleton[k];
- outgoingPacket[k] = packetSkeleton[k].getArgument(args);
- maxPosition = Math.max(position, maxPosition);
- } else {
- outgoingPacket[k] = packetSkeleton[k];
- }
- }
-
- if (before) {
- outgoingPacket = before.call(this, outgoingPacket);
- }
-
- return this.request(outgoingPacket, DevToolsUtils.makeInfallible((response) => {
- if (after) {
- let { from } = response;
- response = after.call(this, response);
- if (!response.from) {
- response.from = from;
- }
- }
-
- // The callback is always the last parameter.
- let thisCallback = args[maxPosition + 1];
- if (thisCallback) {
- thisCallback(response);
- }
- return response;
- }, "DebuggerClient.requester request callback"));
- }, "DebuggerClient.requester");
-};
-
-function arg(pos) {
- return new DebuggerClient.Argument(pos);
-}
-
-DebuggerClient.Argument = function (position) {
- this.position = position;
-};
-
-DebuggerClient.Argument.prototype.getArgument = function (params) {
- if (!(this.position in params)) {
- throw new Error("Bad index into params: " + this.position);
- }
- return params[this.position];
-};
-
-// Expose these to save callers the trouble of importing DebuggerSocket
-DebuggerClient.socketConnect = function (options) {
- // Defined here instead of just copying the function to allow lazy-load
- return DebuggerSocket.connect(options);
-};
-DevToolsUtils.defineLazyGetter(DebuggerClient, "Authenticators", () => {
- return Authentication.Authenticators;
-});
-DevToolsUtils.defineLazyGetter(DebuggerClient, "AuthenticationResult", () => {
- return Authentication.AuthenticationResult;
-});
-
-DebuggerClient.prototype = {
- /**
- * Connect to the server and start exchanging protocol messages.
- *
- * @param onConnected function
- * If specified, will be called when the greeting packet is
- * received from the debugging server.
- *
- * @return Promise
- * Resolves once connected with an array whose first element
- * is the application type, by default "browser", and the second
- * element is the traits object (help figure out the features
- * and behaviors of the server we connect to. See RootActor).
- */
- connect: function (onConnected) {
- let deferred = promise.defer();
- this.emit("connect");
-
- // Also emit the event on the |DebuggerClient| object (not on the instance),
- // so it's possible to track all instances.
- EventEmitter.emit(DebuggerClient, "connect", this);
-
- this.addOneTimeListener("connected", (name, applicationType, traits) => {
- this.traits = traits;
- if (onConnected) {
- onConnected(applicationType, traits);
- }
- deferred.resolve([applicationType, traits]);
- });
-
- this._transport.ready();
- return deferred.promise;
- },
-
- /**
- * Shut down communication with the debugging server.
- *
- * @param onClosed function
- * If specified, will be called when the debugging connection
- * has been closed. This parameter is deprecated - please use
- * the returned Promise.
- * @return Promise
- * Resolves after the underlying transport is closed.
- */
- close: function (onClosed) {
- let deferred = promise.defer();
- if (onClosed) {
- deferred.promise.then(onClosed);
- }
-
- // Disable detach event notifications, because event handlers will be in a
- // cleared scope by the time they run.
- this._eventsEnabled = false;
-
- let cleanup = () => {
- this._transport.close();
- this._transport = null;
- };
-
- // If the connection is already closed,
- // there is no need to detach client
- // as we won't be able to send any message.
- if (this._closed) {
- cleanup();
- deferred.resolve();
- return deferred.promise;
- }
-
- this.addOneTimeListener("closed", deferred.resolve);
-
- // Call each client's `detach` method by calling
- // lastly registered ones first to give a chance
- // to detach child clients first.
- let clients = [...this._clients.values()];
- this._clients.clear();
- const detachClients = () => {
- let client = clients.pop();
- if (!client) {
- // All clients detached.
- cleanup();
- return;
- }
- if (client.detach) {
- client.detach(detachClients);
- return;
- }
- detachClients();
- };
- detachClients();
-
- return deferred.promise;
- },
-
- /*
- * This function exists only to preserve DebuggerClient's interface;
- * new code should say 'client.mainRoot.listTabs()'.
- */
- listTabs: function (onResponse) {
- return this.mainRoot.listTabs(onResponse);
- },
-
- /*
- * This function exists only to preserve DebuggerClient's interface;
- * new code should say 'client.mainRoot.listAddons()'.
- */
- listAddons: function (onResponse) {
- return this.mainRoot.listAddons(onResponse);
- },
-
- getTab: function (filter) {
- return this.mainRoot.getTab(filter);
- },
-
- /**
- * Attach to a tab actor.
- *
- * @param string tabActor
- * The actor ID for the tab to attach.
- * @param function onResponse
- * Called with the response packet and a TabClient
- * (which will be undefined on error).
- */
- attachTab: function (tabActor, onResponse = noop) {
- if (this._clients.has(tabActor)) {
- let cachedTab = this._clients.get(tabActor);
- let cachedResponse = {
- cacheDisabled: cachedTab.cacheDisabled,
- javascriptEnabled: cachedTab.javascriptEnabled,
- traits: cachedTab.traits,
- };
- DevToolsUtils.executeSoon(() => onResponse(cachedResponse, cachedTab));
- return promise.resolve([cachedResponse, cachedTab]);
- }
-
- let packet = {
- to: tabActor,
- type: "attach"
- };
- return this.request(packet).then(response => {
- let tabClient;
- if (!response.error) {
- tabClient = new TabClient(this, response);
- this.registerClient(tabClient);
- }
- onResponse(response, tabClient);
- return [response, tabClient];
- });
- },
-
- attachWorker: function (workerActor, onResponse = noop) {
- let workerClient = this._clients.get(workerActor);
- if (workerClient !== undefined) {
- let response = {
- from: workerClient.actor,
- type: "attached",
- url: workerClient.url
- };
- DevToolsUtils.executeSoon(() => onResponse(response, workerClient));
- return promise.resolve([response, workerClient]);
- }
-
- return this.request({ to: workerActor, type: "attach" }).then(response => {
- if (response.error) {
- onResponse(response, null);
- return [response, null];
- }
-
- workerClient = new WorkerClient(this, response);
- this.registerClient(workerClient);
- onResponse(response, workerClient);
- return [response, workerClient];
- });
- },
-
- /**
- * Attach to an addon actor.
- *
- * @param string addonActor
- * The actor ID for the addon to attach.
- * @param function onResponse
- * Called with the response packet and a AddonClient
- * (which will be undefined on error).
- */
- attachAddon: function (addonActor, onResponse = noop) {
- let packet = {
- to: addonActor,
- type: "attach"
- };
- return this.request(packet).then(response => {
- let addonClient;
- if (!response.error) {
- addonClient = new AddonClient(this, addonActor);
- this.registerClient(addonClient);
- this.activeAddon = addonClient;
- }
- onResponse(response, addonClient);
- return [response, addonClient];
- });
- },
-
- /**
- * Attach to a Web Console actor.
- *
- * @param string consoleActor
- * The ID for the console actor to attach to.
- * @param array listeners
- * The console listeners you want to start.
- * @param function onResponse
- * Called with the response packet and a WebConsoleClient
- * instance (which will be undefined on error).
- */
- attachConsole:
- function (consoleActor, listeners, onResponse = noop) {
- let packet = {
- to: consoleActor,
- type: "startListeners",
- listeners: listeners,
- };
-
- return this.request(packet).then(response => {
- let consoleClient;
- if (!response.error) {
- if (this._clients.has(consoleActor)) {
- consoleClient = this._clients.get(consoleActor);
- } else {
- consoleClient = new WebConsoleClient(this, response);
- this.registerClient(consoleClient);
- }
- }
- onResponse(response, consoleClient);
- return [response, consoleClient];
- });
- },
-
- /**
- * Attach to a global-scoped thread actor for chrome debugging.
- *
- * @param string threadActor
- * The actor ID for the thread to attach.
- * @param function onResponse
- * Called with the response packet and a ThreadClient
- * (which will be undefined on error).
- * @param object options
- * Configuration options.
- * - useSourceMaps: whether to use source maps or not.
- */
- attachThread: function (threadActor, onResponse = noop, options = {}) {
- if (this._clients.has(threadActor)) {
- let client = this._clients.get(threadActor);
- DevToolsUtils.executeSoon(() => onResponse({}, client));
- return promise.resolve([{}, client]);
- }
-
- let packet = {
- to: threadActor,
- type: "attach",
- options,
- };
- return this.request(packet).then(response => {
- let threadClient;
- if (!response.error) {
- threadClient = new ThreadClient(this, threadActor);
- this.registerClient(threadClient);
- }
- onResponse(response, threadClient);
- return [response, threadClient];
- });
- },
-
- /**
- * Attach to a trace actor.
- *
- * @param string traceActor
- * The actor ID for the tracer to attach.
- * @param function onResponse
- * Called with the response packet and a TraceClient
- * (which will be undefined on error).
- */
- attachTracer: function (traceActor, onResponse = noop) {
- if (this._clients.has(traceActor)) {
- let client = this._clients.get(traceActor);
- DevToolsUtils.executeSoon(() => onResponse({}, client));
- return promise.resolve([{}, client]);
- }
-
- let packet = {
- to: traceActor,
- type: "attach"
- };
- return this.request(packet).then(response => {
- let traceClient;
- if (!response.error) {
- traceClient = new TraceClient(this, traceActor);
- this.registerClient(traceClient);
- }
- onResponse(response, traceClient);
- return [response, traceClient];
- });
- },
-
- /**
- * Fetch the ChromeActor for the main process or ChildProcessActor for a
- * a given child process ID.
- *
- * @param number id
- * The ID for the process to attach (returned by `listProcesses`).
- * Connected to the main process if omitted, or is 0.
- */
- getProcess: function (id) {
- let packet = {
- to: "root",
- type: "getProcess"
- };
- if (typeof (id) == "number") {
- packet.id = id;
- }
- return this.request(packet);
- },
-
- /**
- * Release an object actor.
- *
- * @param string actor
- * The actor ID to send the request to.
- * @param onResponse function
- * If specified, will be called with the response packet when
- * debugging server responds.
- */
- release: DebuggerClient.requester({
- to: arg(0),
- type: "release"
- }),
-
- /**
- * Send a request to the debugging server.
- *
- * @param packet object
- * A JSON packet to send to the debugging server.
- * @param onResponse function
- * If specified, will be called with the JSON response packet when
- * debugging server responds.
- * @return Request
- * This object emits a number of events to allow you to respond to
- * different parts of the request lifecycle.
- * It is also a Promise object, with a `then` method, that is resolved
- * whenever a JSON or a Bulk response is received; and is rejected
- * if the response is an error.
- * Note: This return value can be ignored if you are using JSON alone,
- * because the callback provided in |onResponse| will be bound to the
- * "json-reply" event automatically.
- *
- * Events emitted:
- * * json-reply: The server replied with a JSON packet, which is
- * passed as event data.
- * * bulk-reply: The server replied with bulk data, which you can read
- * using the event data object containing:
- * * actor: Name of actor that received the packet
- * * type: Name of actor's method that was called on receipt
- * * length: Size of the data to be read
- * * stream: This input stream should only be used directly if you
- * can ensure that you will read exactly |length| bytes
- * and will not close the stream when reading is complete
- * * done: If you use the stream directly (instead of |copyTo|
- * below), you must signal completion by resolving /
- * rejecting this deferred. If it's rejected, the
- * transport will be closed. If an Error is supplied as a
- * rejection value, it will be logged via |dumpn|. If you
- * do use |copyTo|, resolving is taken care of for you
- * when copying completes.
- * * copyTo: A helper function for getting your data out of the
- * stream that meets the stream handling requirements
- * above, and has the following signature:
- * @param output nsIAsyncOutputStream
- * The stream to copy to.
- * @return Promise
- * The promise is resolved when copying completes or
- * rejected if any (unexpected) errors occur.
- * This object also emits "progress" events for each chunk
- * that is copied. See stream-utils.js.
- */
- request: function (packet, onResponse) {
- if (!this.mainRoot) {
- throw Error("Have not yet received a hello packet from the server.");
- }
- let type = packet.type || "";
- if (!packet.to) {
- throw Error("'" + type + "' request packet has no destination.");
- }
-
- // The onResponse callback might modify the response, so we need to call
- // it and resolve the promise with its result if it's truthy.
- const safeOnResponse = response => {
- if (!onResponse) {
- return response;
- }
- return onResponse(response) || response;
- };
-
- if (this._closed) {
- let msg = "'" + type + "' request packet to " +
- "'" + packet.to + "' " +
- "can't be sent as the connection is closed.";
- let resp = { error: "connectionClosed", message: msg };
- return promise.reject(safeOnResponse(resp));
- }
-
- let request = new Request(packet);
- request.format = "json";
- request.stack = getStack();
-
- // Implement a Promise like API on the returned object
- // that resolves/rejects on request response
- let deferred = promise.defer();
- function listenerJson(resp) {
- removeRequestListeners();
- if (resp.error) {
- deferred.reject(safeOnResponse(resp));
- } else {
- deferred.resolve(safeOnResponse(resp));
- }
- }
- function listenerBulk(resp) {
- removeRequestListeners();
- deferred.resolve(safeOnResponse(resp));
- }
-
- const removeRequestListeners = () => {
- request.off("json-reply", listenerJson);
- request.off("bulk-reply", listenerBulk);
- };
-
- request.on("json-reply", listenerJson);
- request.on("bulk-reply", listenerBulk);
-
- this._sendOrQueueRequest(request);
- request.then = deferred.promise.then.bind(deferred.promise);
-
- return request;
- },
-
- /**
- * Transmit streaming data via a bulk request.
- *
- * This method initiates the bulk send process by queuing up the header data.
- * The caller receives eventual access to a stream for writing.
- *
- * Since this opens up more options for how the server might respond (it could
- * send back either JSON or bulk data), and the returned Request object emits
- * events for different stages of the request process that you may want to
- * react to.
- *
- * @param request Object
- * This is modeled after the format of JSON packets above, but does not
- * actually contain the data, but is instead just a routing header:
- * * actor: Name of actor that will receive the packet
- * * type: Name of actor's method that should be called on receipt
- * * length: Size of the data to be sent
- * @return Request
- * This object emits a number of events to allow you to respond to
- * different parts of the request lifecycle.
- *
- * Events emitted:
- * * bulk-send-ready: Ready to send bulk data to the server, using the
- * event data object containing:
- * * stream: This output stream should only be used directly if
- * you can ensure that you will write exactly |length|
- * bytes and will not close the stream when writing is
- * complete
- * * done: If you use the stream directly (instead of |copyFrom|
- * below), you must signal completion by resolving /
- * rejecting this deferred. If it's rejected, the
- * transport will be closed. If an Error is supplied as
- * a rejection value, it will be logged via |dumpn|. If
- * you do use |copyFrom|, resolving is taken care of for
- * you when copying completes.
- * * copyFrom: A helper function for getting your data onto the
- * stream that meets the stream handling requirements
- * above, and has the following signature:
- * @param input nsIAsyncInputStream
- * The stream to copy from.
- * @return Promise
- * The promise is resolved when copying completes or
- * rejected if any (unexpected) errors occur.
- * This object also emits "progress" events for each chunk
- * that is copied. See stream-utils.js.
- * * json-reply: The server replied with a JSON packet, which is
- * passed as event data.
- * * bulk-reply: The server replied with bulk data, which you can read
- * using the event data object containing:
- * * actor: Name of actor that received the packet
- * * type: Name of actor's method that was called on receipt
- * * length: Size of the data to be read
- * * stream: This input stream should only be used directly if you
- * can ensure that you will read exactly |length| bytes
- * and will not close the stream when reading is complete
- * * done: If you use the stream directly (instead of |copyTo|
- * below), you must signal completion by resolving /
- * rejecting this deferred. If it's rejected, the
- * transport will be closed. If an Error is supplied as a
- * rejection value, it will be logged via |dumpn|. If you
- * do use |copyTo|, resolving is taken care of for you
- * when copying completes.
- * * copyTo: A helper function for getting your data out of the
- * stream that meets the stream handling requirements
- * above, and has the following signature:
- * @param output nsIAsyncOutputStream
- * The stream to copy to.
- * @return Promise
- * The promise is resolved when copying completes or
- * rejected if any (unexpected) errors occur.
- * This object also emits "progress" events for each chunk
- * that is copied. See stream-utils.js.
- */
- startBulkRequest: function (request) {
- if (!this.traits.bulk) {
- throw Error("Server doesn't support bulk transfers");
- }
- if (!this.mainRoot) {
- throw Error("Have not yet received a hello packet from the server.");
- }
- if (!request.type) {
- throw Error("Bulk packet is missing the required 'type' field.");
- }
- if (!request.actor) {
- throw Error("'" + request.type + "' bulk packet has no destination.");
- }
- if (!request.length) {
- throw Error("'" + request.type + "' bulk packet has no length.");
- }
-
- request = new Request(request);
- request.format = "bulk";
-
- this._sendOrQueueRequest(request);
-
- return request;
- },
-
- /**
- * If a new request can be sent immediately, do so. Otherwise, queue it.
- */
- _sendOrQueueRequest(request) {
- let actor = request.actor;
- if (!this._activeRequests.has(actor)) {
- this._sendRequest(request);
- } else {
- this._queueRequest(request);
- }
- },
-
- /**
- * Send a request.
- * @throws Error if there is already an active request in flight for the same
- * actor.
- */
- _sendRequest(request) {
- let actor = request.actor;
- this.expectReply(actor, request);
-
- if (request.format === "json") {
- this._transport.send(request.request);
- return;
- }
-
- this._transport.startBulkSend(request.request).then((...args) => {
- request.emit("bulk-send-ready", ...args);
- });
- },
-
- /**
- * Queue a request to be sent later. Queues are only drained when an in
- * flight request to a given actor completes.
- */
- _queueRequest(request) {
- let actor = request.actor;
- let queue = this._pendingRequests.get(actor) || [];
- queue.push(request);
- this._pendingRequests.set(actor, queue);
- },
-
- /**
- * Attempt the next request to a given actor (if any).
- */
- _attemptNextRequest(actor) {
- if (this._activeRequests.has(actor)) {
- return;
- }
- let queue = this._pendingRequests.get(actor);
- if (!queue) {
- return;
- }
- let request = queue.shift();
- if (queue.length === 0) {
- this._pendingRequests.delete(actor);
- }
- this._sendRequest(request);
- },
-
- /**
- * Arrange to hand the next reply from |actor| to the handler bound to
- * |request|.
- *
- * DebuggerClient.prototype.request / startBulkRequest usually takes care of
- * establishing the handler for a given request, but in rare cases (well,
- * greetings from new root actors, is the only case at the moment) we must be
- * prepared for a "reply" that doesn't correspond to any request we sent.
- */
- expectReply: function (actor, request) {
- if (this._activeRequests.has(actor)) {
- throw Error("clashing handlers for next reply from " + actor);
- }
-
- // If a handler is passed directly (as it is with the handler for the root
- // actor greeting), create a dummy request to bind this to.
- if (typeof request === "function") {
- let handler = request;
- request = new Request();
- request.on("json-reply", handler);
- }
-
- this._activeRequests.set(actor, request);
- },
-
- // Transport hooks.
-
- /**
- * Called by DebuggerTransport to dispatch incoming packets as appropriate.
- *
- * @param packet object
- * The incoming packet.
- */
- onPacket: function (packet) {
- if (!packet.from) {
- DevToolsUtils.reportException(
- "onPacket",
- new Error("Server did not specify an actor, dropping packet: " +
- JSON.stringify(packet)));
- return;
- }
-
- // If we have a registered Front for this actor, let it handle the packet
- // and skip all the rest of this unpleasantness.
- let front = this.getActor(packet.from);
- if (front) {
- front.onPacket(packet);
- return;
- }
-
- // Check for "forwardingCancelled" here instead of using a client to handle it.
- // This is necessary because we might receive this event while the client is closing,
- // and the clients have already been removed by that point.
- if (this.mainRoot &&
- packet.from == this.mainRoot.actor &&
- packet.type == "forwardingCancelled") {
- this.purgeRequests(packet.prefix);
- return;
- }
-
- if (this._clients.has(packet.from) && packet.type) {
- let client = this._clients.get(packet.from);
- let type = packet.type;
- if (client.events.indexOf(type) != -1) {
- client.emit(type, packet);
- // we ignore the rest, as the client is expected to handle this packet.
- return;
- }
- }
-
- let activeRequest;
- // See if we have a handler function waiting for a reply from this
- // actor. (Don't count unsolicited notifications or pauses as
- // replies.)
- if (this._activeRequests.has(packet.from) &&
- !(packet.type in UnsolicitedNotifications) &&
- !(packet.type == ThreadStateTypes.paused &&
- packet.why.type in UnsolicitedPauses)) {
- activeRequest = this._activeRequests.get(packet.from);
- this._activeRequests.delete(packet.from);
- }
-
- // If there is a subsequent request for the same actor, hand it off to the
- // transport. Delivery of packets on the other end is always async, even
- // in the local transport case.
- this._attemptNextRequest(packet.from);
-
- // Packets that indicate thread state changes get special treatment.
- if (packet.type in ThreadStateTypes &&
- this._clients.has(packet.from) &&
- typeof this._clients.get(packet.from)._onThreadState == "function") {
- this._clients.get(packet.from)._onThreadState(packet);
- }
-
- // TODO: Bug 1151156 - Remove once Gecko 40 is on b2g-stable.
- if (!this.traits.noNeedToFakeResumptionOnNavigation) {
- // On navigation the server resumes, so the client must resume as well.
- // We achieve that by generating a fake resumption packet that triggers
- // the client's thread state change listeners.
- if (packet.type == UnsolicitedNotifications.tabNavigated &&
- this._clients.has(packet.from) &&
- this._clients.get(packet.from).thread) {
- let thread = this._clients.get(packet.from).thread;
- let resumption = { from: thread._actor, type: "resumed" };
- thread._onThreadState(resumption);
- }
- }
-
- // Only try to notify listeners on events, not responses to requests
- // that lack a packet type.
- if (packet.type) {
- this.emit(packet.type, packet);
- }
-
- if (activeRequest) {
- let emitReply = () => activeRequest.emit("json-reply", packet);
- if (activeRequest.stack) {
- callFunctionWithAsyncStack(emitReply, activeRequest.stack,
- "DevTools RDP");
- } else {
- emitReply();
- }
- }
- },
-
- /**
- * Called by the DebuggerTransport to dispatch incoming bulk packets as
- * appropriate.
- *
- * @param packet object
- * The incoming packet, which contains:
- * * actor: Name of actor that will receive the packet
- * * type: Name of actor's method that should be called on receipt
- * * length: Size of the data to be read
- * * stream: This input stream should only be used directly if you can
- * ensure that you will read exactly |length| bytes and will
- * not close the stream when reading is complete
- * * done: If you use the stream directly (instead of |copyTo|
- * below), you must signal completion by resolving /
- * rejecting this deferred. If it's rejected, the transport
- * will be closed. If an Error is supplied as a rejection
- * value, it will be logged via |dumpn|. If you do use
- * |copyTo|, resolving is taken care of for you when copying
- * completes.
- * * copyTo: A helper function for getting your data out of the stream
- * that meets the stream handling requirements above, and has
- * the following signature:
- * @param output nsIAsyncOutputStream
- * The stream to copy to.
- * @return Promise
- * The promise is resolved when copying completes or rejected
- * if any (unexpected) errors occur.
- * This object also emits "progress" events for each chunk
- * that is copied. See stream-utils.js.
- */
- onBulkPacket: function (packet) {
- let { actor } = packet;
-
- if (!actor) {
- DevToolsUtils.reportException(
- "onBulkPacket",
- new Error("Server did not specify an actor, dropping bulk packet: " +
- JSON.stringify(packet)));
- return;
- }
-
- // See if we have a handler function waiting for a reply from this
- // actor.
- if (!this._activeRequests.has(actor)) {
- return;
- }
-
- let activeRequest = this._activeRequests.get(actor);
- this._activeRequests.delete(actor);
-
- // If there is a subsequent request for the same actor, hand it off to the
- // transport. Delivery of packets on the other end is always async, even
- // in the local transport case.
- this._attemptNextRequest(actor);
-
- activeRequest.emit("bulk-reply", packet);
- },
-
- /**
- * Called by DebuggerTransport when the underlying stream is closed.
- *
- * @param status nsresult
- * The status code that corresponds to the reason for closing
- * the stream.
- */
- onClosed: function () {
- this._closed = true;
- this.emit("closed");
-
- this.purgeRequests();
-
- // The |_pools| array on the client-side currently is used only by
- // protocol.js to store active fronts, mirroring the actor pools found in
- // the server. So, read all usages of "pool" as "protocol.js front".
- //
- // In the normal case where we shutdown cleanly, the toolbox tells each tool
- // to close, and they each call |destroy| on any fronts they were using.
- // When |destroy| or |cleanup| is called on a protocol.js front, it also
- // removes itself from the |_pools| array. Once the toolbox has shutdown,
- // the connection is closed, and we reach here. All fronts (should have
- // been) |destroy|ed, so |_pools| should empty.
- //
- // If the connection instead aborts unexpectedly, we may end up here with
- // all fronts used during the life of the connection. So, we call |cleanup|
- // on them clear their state, reject pending requests, and remove themselves
- // from |_pools|. This saves the toolbox from hanging indefinitely, in case
- // it waits for some server response before shutdown that will now never
- // arrive.
- for (let pool of this._pools) {
- pool.cleanup();
- }
- },
-
- /**
- * Purge pending and active requests in this client.
- *
- * @param prefix string (optional)
- * If a prefix is given, only requests for actor IDs that start with the prefix
- * will be cleaned up. This is useful when forwarding of a portion of requests
- * is cancelled on the server.
- */
- purgeRequests(prefix = "") {
- let reject = function (type, request) {
- // Server can send packets on its own and client only pass a callback
- // to expectReply, so that there is no request object.
- let msg;
- if (request.request) {
- msg = "'" + request.request.type + "' " + type + " request packet" +
- " to '" + request.actor + "' " +
- "can't be sent as the connection just closed.";
- } else {
- msg = "server side packet can't be received as the connection just closed.";
- }
- let packet = { error: "connectionClosed", message: msg };
- request.emit("json-reply", packet);
- };
-
- let pendingRequestsToReject = [];
- this._pendingRequests.forEach((requests, actor) => {
- if (!actor.startsWith(prefix)) {
- return;
- }
- this._pendingRequests.delete(actor);
- pendingRequestsToReject = pendingRequestsToReject.concat(requests);
- });
- pendingRequestsToReject.forEach(request => reject("pending", request));
-
- let activeRequestsToReject = [];
- this._activeRequests.forEach((request, actor) => {
- if (!actor.startsWith(prefix)) {
- return;
- }
- this._activeRequests.delete(actor);
- activeRequestsToReject = activeRequestsToReject.concat(request);
- });
- activeRequestsToReject.forEach(request => reject("active", request));
- },
-
- /**
- * Search for all requests in process for this client, including those made via
- * protocol.js and wait all of them to complete. Since the requests seen when this is
- * first called may in turn trigger more requests, we keep recursing through this
- * function until there is no more activity.
- *
- * This is a fairly heavy weight process, so it's only meant to be used in tests.
- *
- * @return Promise
- * Resolved when all requests have settled.
- */
- waitForRequestsToSettle() {
- let requests = [];
-
- // Gather all pending and active requests in this client
- // The request object supports a Promise API for completion (it has .then())
- this._pendingRequests.forEach(requestsForActor => {
- // Each value is an array of pending requests
- requests = requests.concat(requestsForActor);
- });
- this._activeRequests.forEach(requestForActor => {
- // Each value is a single active request
- requests = requests.concat(requestForActor);
- });
-
- // protocol.js
- // Use a Set because some fronts (like domwalker) seem to have multiple parents.
- let fronts = new Set();
- let poolsToVisit = [...this._pools];
-
- // With protocol.js, each front can potentially have it's own pools containing child
- // fronts, forming a tree. Descend through all the pools to locate all child fronts.
- while (poolsToVisit.length) {
- let pool = poolsToVisit.shift();
- fronts.add(pool);
- for (let child of pool.poolChildren()) {
- poolsToVisit.push(child);
- }
- }
-
- // For each front, wait for its requests to settle
- for (let front of fronts) {
- if (front.hasRequests()) {
- requests.push(front.waitForRequestsToSettle());
- }
- }
-
- // Abort early if there are no requests
- if (!requests.length) {
- return Promise.resolve();
- }
-
- return DevToolsUtils.settleAll(requests).catch(() => {
- // One of the requests might have failed, but ignore that situation here and pipe
- // both success and failure through the same path. The important part is just that
- // we waited.
- }).then(() => {
- // Repeat, more requests may have started in response to those we just waited for
- return this.waitForRequestsToSettle();
- });
- },
-
- registerClient: function (client) {
- let actorID = client.actor;
- if (!actorID) {
- throw new Error("DebuggerServer.registerClient expects " +
- "a client instance with an `actor` attribute.");
- }
- if (!Array.isArray(client.events)) {
- throw new Error("DebuggerServer.registerClient expects " +
- "a client instance with an `events` attribute " +
- "that is an array.");
- }
- if (client.events.length > 0 && typeof (client.emit) != "function") {
- throw new Error("DebuggerServer.registerClient expects " +
- "a client instance with non-empty `events` array to" +
- "have an `emit` function.");
- }
- if (this._clients.has(actorID)) {
- throw new Error("DebuggerServer.registerClient already registered " +
- "a client for this actor.");
- }
- this._clients.set(actorID, client);
- },
-
- unregisterClient: function (client) {
- let actorID = client.actor;
- if (!actorID) {
- throw new Error("DebuggerServer.unregisterClient expects " +
- "a Client instance with a `actor` attribute.");
- }
- this._clients.delete(actorID);
- },
-
- /**
- * Actor lifetime management, echos the server's actor pools.
- */
- __pools: null,
- get _pools() {
- if (this.__pools) {
- return this.__pools;
- }
- this.__pools = new Set();
- return this.__pools;
- },
-
- addActorPool: function (pool) {
- this._pools.add(pool);
- },
- removeActorPool: function (pool) {
- this._pools.delete(pool);
- },
- getActor: function (actorID) {
- let pool = this.poolFor(actorID);
- return pool ? pool.get(actorID) : null;
- },
-
- poolFor: function (actorID) {
- for (let pool of this._pools) {
- if (pool.has(actorID)) {
- return pool;
- }
- }
- return null;
- },
-
- /**
- * Currently attached addon.
- */
- activeAddon: null
-};
-
-eventSource(DebuggerClient.prototype);
-
-class Request extends EventEmitter {
- constructor(request) {
- super();
- this.request = request;
- }
-
- get actor() {
- return this.request.to || this.request.actor;
- }
-}
-
-/**
- * Creates a tab client for the remote debugging protocol server. This client
- * is a front to the tab actor created in the server side, hiding the protocol
- * details in a traditional JavaScript API.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param form object
- * The protocol form for this tab.
- */
-function TabClient(client, form) {
- this.client = client;
- this._actor = form.from;
- this._threadActor = form.threadActor;
- this.javascriptEnabled = form.javascriptEnabled;
- this.cacheDisabled = form.cacheDisabled;
- this.thread = null;
- this.request = this.client.request;
- this.traits = form.traits || {};
- this.events = ["workerListChanged"];
-}
-
-TabClient.prototype = {
- get actor() {
- return this._actor;
- },
- get _transport() {
- return this.client._transport;
- },
-
- /**
- * Attach to a thread actor.
- *
- * @param object options
- * Configuration options.
- * - useSourceMaps: whether to use source maps or not.
- * @param function onResponse
- * Called with the response packet and a ThreadClient
- * (which will be undefined on error).
- */
- attachThread: function (options = {}, onResponse = noop) {
- if (this.thread) {
- DevToolsUtils.executeSoon(() => onResponse({}, this.thread));
- return promise.resolve([{}, this.thread]);
- }
-
- let packet = {
- to: this._threadActor,
- type: "attach",
- options,
- };
- return this.request(packet).then(response => {
- if (!response.error) {
- this.thread = new ThreadClient(this, this._threadActor);
- this.client.registerClient(this.thread);
- }
- onResponse(response, this.thread);
- return [response, this.thread];
- });
- },
-
- /**
- * Detach the client from the tab actor.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- detach: DebuggerClient.requester({
- type: "detach"
- }, {
- before: function (packet) {
- if (this.thread) {
- this.thread.detach();
- }
- return packet;
- },
- after: function (response) {
- this.client.unregisterClient(this);
- return response;
- },
- }),
-
- /**
- * Bring the window to the front.
- */
- focus: DebuggerClient.requester({
- type: "focus"
- }, {}),
-
- /**
- * Reload the page in this tab.
- *
- * @param [optional] object options
- * An object with a `force` property indicating whether or not
- * this reload should skip the cache
- */
- reload: function (options = { force: false }) {
- return this._reload(options);
- },
- _reload: DebuggerClient.requester({
- type: "reload",
- options: arg(0)
- }),
-
- /**
- * Navigate to another URL.
- *
- * @param string url
- * The URL to navigate to.
- */
- navigateTo: DebuggerClient.requester({
- type: "navigateTo",
- url: arg(0)
- }),
-
- /**
- * Reconfigure the tab actor.
- *
- * @param object options
- * A dictionary object of the new options to use in the tab actor.
- * @param function onResponse
- * Called with the response packet.
- */
- reconfigure: DebuggerClient.requester({
- type: "reconfigure",
- options: arg(0)
- }),
-
- listWorkers: DebuggerClient.requester({
- type: "listWorkers"
- }),
-
- attachWorker: function (workerActor, onResponse) {
- return this.client.attachWorker(workerActor, onResponse);
- },
-};
-
-eventSource(TabClient.prototype);
-
-function WorkerClient(client, form) {
- this.client = client;
- this._actor = form.from;
- this._isClosed = false;
- this._url = form.url;
-
- this._onClose = this._onClose.bind(this);
-
- this.addListener("close", this._onClose);
-
- this.traits = {};
-}
-
-WorkerClient.prototype = {
- get _transport() {
- return this.client._transport;
- },
-
- get request() {
- return this.client.request;
- },
-
- get actor() {
- return this._actor;
- },
-
- get url() {
- return this._url;
- },
-
- get isClosed() {
- return this._isClosed;
- },
-
- detach: DebuggerClient.requester({ type: "detach" }, {
- after: function (response) {
- if (this.thread) {
- this.client.unregisterClient(this.thread);
- }
- this.client.unregisterClient(this);
- return response;
- },
- }),
-
- attachThread: function (options = {}, onResponse = noop) {
- if (this.thread) {
- let response = [{
- type: "connected",
- threadActor: this.thread._actor,
- consoleActor: this.consoleActor,
- }, this.thread];
- DevToolsUtils.executeSoon(() => onResponse(response));
- return response;
- }
-
- // The connect call on server doesn't attach the thread as of version 44.
- return this.request({
- to: this._actor,
- type: "connect",
- options,
- }).then(connectResponse => {
- if (connectResponse.error) {
- onResponse(connectResponse, null);
- return [connectResponse, null];
- }
-
- return this.request({
- to: connectResponse.threadActor,
- type: "attach",
- options,
- }).then(attachResponse => {
- if (attachResponse.error) {
- onResponse(attachResponse, null);
- }
-
- this.thread = new ThreadClient(this, connectResponse.threadActor);
- this.consoleActor = connectResponse.consoleActor;
- this.client.registerClient(this.thread);
-
- onResponse(connectResponse, this.thread);
- return [connectResponse, this.thread];
- });
- }, error => {
- onResponse(error, null);
- });
- },
-
- _onClose: function () {
- this.removeListener("close", this._onClose);
-
- if (this.thread) {
- this.client.unregisterClient(this.thread);
- }
- this.client.unregisterClient(this);
- this._isClosed = true;
- },
-
- reconfigure: function () {
- return Promise.resolve();
- },
-
- events: ["close"]
-};
-
-eventSource(WorkerClient.prototype);
-
-function AddonClient(client, actor) {
- this._client = client;
- this._actor = actor;
- this.request = this._client.request;
- this.events = [];
-}
-
-AddonClient.prototype = {
- get actor() {
- return this._actor;
- },
- get _transport() {
- return this._client._transport;
- },
-
- /**
- * Detach the client from the addon actor.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- detach: DebuggerClient.requester({
- type: "detach"
- }, {
- after: function (response) {
- if (this._client.activeAddon === this) {
- this._client.activeAddon = null;
- }
- this._client.unregisterClient(this);
- return response;
- },
- })
-};
-
-/**
- * A RootClient object represents a root actor on the server. Each
- * DebuggerClient keeps a RootClient instance representing the root actor
- * for the initial connection; DebuggerClient's 'listTabs' and
- * 'listChildProcesses' methods forward to that root actor.
- *
- * @param client object
- * The client connection to which this actor belongs.
- * @param greeting string
- * The greeting packet from the root actor we're to represent.
- *
- * Properties of a RootClient instance:
- *
- * @property actor string
- * The name of this child's root actor.
- * @property applicationType string
- * The application type, as given in the root actor's greeting packet.
- * @property traits object
- * The traits object, as given in the root actor's greeting packet.
- */
-function RootClient(client, greeting) {
- this._client = client;
- this.actor = greeting.from;
- this.applicationType = greeting.applicationType;
- this.traits = greeting.traits;
-}
-exports.RootClient = RootClient;
-
-RootClient.prototype = {
- constructor: RootClient,
-
- /**
- * Gets the "root" form, which lists all the global actors that affect the entire
- * browser. This can replace usages of `listTabs` that only wanted the global actors
- * and didn't actually care about tabs.
- */
- getRoot: DebuggerClient.requester({ type: "getRoot" }),
-
- /**
- * List the open tabs.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- listTabs: DebuggerClient.requester({ type: "listTabs" }),
-
- /**
- * List the installed addons.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- listAddons: DebuggerClient.requester({ type: "listAddons" }),
-
- /**
- * List the registered workers.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- listWorkers: DebuggerClient.requester({ type: "listWorkers" }),
-
- /**
- * List the registered service workers.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- listServiceWorkerRegistrations: DebuggerClient.requester({
- type: "listServiceWorkerRegistrations"
- }),
-
- /**
- * List the running processes.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- listProcesses: DebuggerClient.requester({ type: "listProcesses" }),
-
- /**
- * Fetch the TabActor for the currently selected tab, or for a specific
- * tab given as first parameter.
- *
- * @param [optional] object filter
- * A dictionary object with following optional attributes:
- * - outerWindowID: used to match tabs in parent process
- * - tabId: used to match tabs in child processes
- * - tab: a reference to xul:tab element
- * If nothing is specified, returns the actor for the currently
- * selected tab.
- */
- getTab: function (filter) {
- let packet = {
- to: this.actor,
- type: "getTab"
- };
-
- if (filter) {
- if (typeof (filter.outerWindowID) == "number") {
- packet.outerWindowID = filter.outerWindowID;
- } else if (typeof (filter.tabId) == "number") {
- packet.tabId = filter.tabId;
- } else if ("tab" in filter) {
- let browser = filter.tab.linkedBrowser;
- if (browser.frameLoader.tabParent) {
- // Tabs in child process
- packet.tabId = browser.frameLoader.tabParent.tabId;
- } else if (browser.outerWindowID) {
- // <xul:browser> tabs in parent process
- packet.outerWindowID = browser.outerWindowID;
- } else {
- // <iframe mozbrowser> tabs in parent process
- let windowUtils = browser.contentWindow
- .QueryInterface(Ci.nsIInterfaceRequestor)
- .getInterface(Ci.nsIDOMWindowUtils);
- packet.outerWindowID = windowUtils.outerWindowID;
- }
- } else {
- // Throw if a filter object have been passed but without
- // any clearly idenfified filter.
- throw new Error("Unsupported argument given to getTab request");
- }
- }
-
- return this.request(packet);
- },
-
- /**
- * Fetch the WindowActor for a specific window, like a browser window in
- * Firefox, but it can be used to reach any window in the process.
- *
- * @param number outerWindowID
- * The outerWindowID of the top level window you are looking for.
- */
- getWindow: function ({ outerWindowID }) {
- if (!outerWindowID) {
- throw new Error("Must specify outerWindowID");
- }
-
- let packet = {
- to: this.actor,
- type: "getWindow",
- outerWindowID,
- };
-
- return this.request(packet);
- },
-
- /**
- * Description of protocol's actors and methods.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- protocolDescription: DebuggerClient.requester({ type: "protocolDescription" }),
-
- /*
- * Methods constructed by DebuggerClient.requester require these forwards
- * on their 'this'.
- */
- get _transport() {
- return this._client._transport;
- },
- get request() {
- return this._client.request;
- }
-};
-
-/**
- * Creates a thread client for the remote debugging protocol server. This client
- * is a front to the thread actor created in the server side, hiding the
- * protocol details in a traditional JavaScript API.
- *
- * @param client DebuggerClient|TabClient
- * The parent of the thread (tab for tab-scoped debuggers, DebuggerClient
- * for chrome debuggers).
- * @param actor string
- * The actor ID for this thread.
- */
-function ThreadClient(client, actor) {
- this._parent = client;
- this.client = client instanceof DebuggerClient ? client : client.client;
- this._actor = actor;
- this._frameCache = [];
- this._scriptCache = {};
- this._pauseGrips = {};
- this._threadGrips = {};
- this.request = this.client.request;
-}
-
-ThreadClient.prototype = {
- _state: "paused",
- get state() {
- return this._state;
- },
- get paused() {
- return this._state === "paused";
- },
-
- _pauseOnExceptions: false,
- _ignoreCaughtExceptions: false,
- _pauseOnDOMEvents: null,
-
- _actor: null,
- get actor() {
- return this._actor;
- },
-
- get _transport() {
- return this.client._transport;
- },
-
- _assertPaused: function (command) {
- if (!this.paused) {
- throw Error(command + " command sent while not paused. Currently " + this._state);
- }
- },
-
- /**
- * Resume a paused thread. If the optional limit parameter is present, then
- * the thread will also pause when that limit is reached.
- *
- * @param [optional] object limit
- * An object with a type property set to the appropriate limit (next,
- * step, or finish) per the remote debugging protocol specification.
- * Use null to specify no limit.
- * @param function onResponse
- * Called with the response packet.
- */
- _doResume: DebuggerClient.requester({
- type: "resume",
- resumeLimit: arg(0)
- }, {
- before: function (packet) {
- this._assertPaused("resume");
-
- // Put the client in a tentative "resuming" state so we can prevent
- // further requests that should only be sent in the paused state.
- this._previousState = this._state;
- this._state = "resuming";
-
- if (this._pauseOnExceptions) {
- packet.pauseOnExceptions = this._pauseOnExceptions;
- }
- if (this._ignoreCaughtExceptions) {
- packet.ignoreCaughtExceptions = this._ignoreCaughtExceptions;
- }
- if (this._pauseOnDOMEvents) {
- packet.pauseOnDOMEvents = this._pauseOnDOMEvents;
- }
- return packet;
- },
- after: function (response) {
- if (response.error && this._state == "resuming") {
- // There was an error resuming, update the state to the new one
- // reported by the server, if given (only on wrongState), otherwise
- // reset back to the previous state.
- if (response.state) {
- this._state = ThreadStateTypes[response.state];
- } else {
- this._state = this._previousState;
- }
- }
- delete this._previousState;
- return response;
- },
- }),
-
- /**
- * Reconfigure the thread actor.
- *
- * @param object options
- * A dictionary object of the new options to use in the thread actor.
- * @param function onResponse
- * Called with the response packet.
- */
- reconfigure: DebuggerClient.requester({
- type: "reconfigure",
- options: arg(0)
- }),
-
- /**
- * Resume a paused thread.
- */
- resume: function (onResponse) {
- return this._doResume(null, onResponse);
- },
-
- /**
- * Resume then pause without stepping.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- resumeThenPause: function (onResponse) {
- return this._doResume({ type: "break" }, onResponse);
- },
-
- /**
- * Step over a function call.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- stepOver: function (onResponse) {
- return this._doResume({ type: "next" }, onResponse);
- },
-
- /**
- * Step into a function call.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- stepIn: function (onResponse) {
- return this._doResume({ type: "step" }, onResponse);
- },
-
- /**
- * Step out of a function call.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- stepOut: function (onResponse) {
- return this._doResume({ type: "finish" }, onResponse);
- },
-
- /**
- * Immediately interrupt a running thread.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- interrupt: function (onResponse) {
- return this._doInterrupt(null, onResponse);
- },
-
- /**
- * Pause execution right before the next JavaScript bytecode is executed.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- breakOnNext: function (onResponse) {
- return this._doInterrupt("onNext", onResponse);
- },
-
- /**
- * Interrupt a running thread.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- _doInterrupt: DebuggerClient.requester({
- type: "interrupt",
- when: arg(0)
- }),
-
- /**
- * Enable or disable pausing when an exception is thrown.
- *
- * @param boolean pauseOnExceptions
- * Enables pausing if true, disables otherwise.
- * @param boolean ignoreCaughtExceptions
- * Whether to ignore caught exceptions
- * @param function onResponse
- * Called with the response packet.
- */
- pauseOnExceptions: function (pauseOnExceptions,
- ignoreCaughtExceptions,
- onResponse = noop) {
- this._pauseOnExceptions = pauseOnExceptions;
- this._ignoreCaughtExceptions = ignoreCaughtExceptions;
-
- // Otherwise send the flag using a standard resume request.
- if (!this.paused) {
- return this.interrupt(response => {
- if (response.error) {
- // Can't continue if pausing failed.
- onResponse(response);
- return response;
- }
- return this.resume(onResponse);
- });
- }
-
- onResponse();
- return promise.resolve();
- },
-
- /**
- * Enable pausing when the specified DOM events are triggered. Disabling
- * pausing on an event can be realized by calling this method with the updated
- * array of events that doesn't contain it.
- *
- * @param array|string events
- * An array of strings, representing the DOM event types to pause on,
- * or "*" to pause on all DOM events. Pass an empty array to
- * completely disable pausing on DOM events.
- * @param function onResponse
- * Called with the response packet in a future turn of the event loop.
- */
- pauseOnDOMEvents: function (events, onResponse = noop) {
- this._pauseOnDOMEvents = events;
- // If the debuggee is paused, the value of the array will be communicated in
- // the next resumption. Otherwise we have to force a pause in order to send
- // the array.
- if (this.paused) {
- DevToolsUtils.executeSoon(() => onResponse({}));
- return {};
- }
- return this.interrupt(response => {
- // Can't continue if pausing failed.
- if (response.error) {
- onResponse(response);
- return response;
- }
- return this.resume(onResponse);
- });
- },
-
- /**
- * Send a clientEvaluate packet to the debuggee. Response
- * will be a resume packet.
- *
- * @param string frame
- * The actor ID of the frame where the evaluation should take place.
- * @param string expression
- * The expression that will be evaluated in the scope of the frame
- * above.
- * @param function onResponse
- * Called with the response packet.
- */
- eval: DebuggerClient.requester({
- type: "clientEvaluate",
- frame: arg(0),
- expression: arg(1)
- }, {
- before: function (packet) {
- this._assertPaused("eval");
- // Put the client in a tentative "resuming" state so we can prevent
- // further requests that should only be sent in the paused state.
- this._state = "resuming";
- return packet;
- },
- after: function (response) {
- if (response.error) {
- // There was an error resuming, back to paused state.
- this._state = "paused";
- }
- return response;
- },
- }),
-
- /**
- * Detach from the thread actor.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- detach: DebuggerClient.requester({
- type: "detach"
- }, {
- after: function (response) {
- this.client.unregisterClient(this);
- this._parent.thread = null;
- return response;
- },
- }),
-
- /**
- * Release multiple thread-lifetime object actors. If any pause-lifetime
- * actors are included in the request, a |notReleasable| error will return,
- * but all the thread-lifetime ones will have been released.
- *
- * @param array actors
- * An array with actor IDs to release.
- */
- releaseMany: DebuggerClient.requester({
- type: "releaseMany",
- actors: arg(0),
- }),
-
- /**
- * Promote multiple pause-lifetime object actors to thread-lifetime ones.
- *
- * @param array actors
- * An array with actor IDs to promote.
- */
- threadGrips: DebuggerClient.requester({
- type: "threadGrips",
- actors: arg(0)
- }),
-
- /**
- * Return the event listeners defined on the page.
- *
- * @param onResponse Function
- * Called with the thread's response.
- */
- eventListeners: DebuggerClient.requester({
- type: "eventListeners"
- }),
-
- /**
- * Request the loaded sources for the current thread.
- *
- * @param onResponse Function
- * Called with the thread's response.
- */
- getSources: DebuggerClient.requester({
- type: "sources"
- }),
-
- /**
- * Clear the thread's source script cache. A scriptscleared event
- * will be sent.
- */
- _clearScripts: function () {
- if (Object.keys(this._scriptCache).length > 0) {
- this._scriptCache = {};
- this.emit("scriptscleared");
- }
- },
-
- /**
- * Request frames from the callstack for the current thread.
- *
- * @param start integer
- * The number of the youngest stack frame to return (the youngest
- * frame is 0).
- * @param count integer
- * The maximum number of frames to return, or null to return all
- * frames.
- * @param onResponse function
- * Called with the thread's response.
- */
- getFrames: DebuggerClient.requester({
- type: "frames",
- start: arg(0),
- count: arg(1)
- }),
-
- /**
- * An array of cached frames. Clients can observe the framesadded and
- * framescleared event to keep up to date on changes to this cache,
- * and can fill it using the fillFrames method.
- */
- get cachedFrames() {
- return this._frameCache;
- },
-
- /**
- * true if there are more stack frames available on the server.
- */
- get moreFrames() {
- return this.paused && (!this._frameCache || this._frameCache.length == 0
- || !this._frameCache[this._frameCache.length - 1].oldest);
- },
-
- /**
- * Request the frame environment.
- *
- * @param frameId string
- */
- getEnvironment: function (frameId) {
- return this.request({ to: frameId, type: "getEnvironment" });
- },
-
- /**
- * Ensure that at least total stack frames have been loaded in the
- * ThreadClient's stack frame cache. A framesadded event will be
- * sent when the stack frame cache is updated.
- *
- * @param total number
- * The minimum number of stack frames to be included.
- * @param callback function
- * Optional callback function called when frames have been loaded
- * @returns true if a framesadded notification should be expected.
- */
- fillFrames: function (total, callback = noop) {
- this._assertPaused("fillFrames");
- if (this._frameCache.length >= total) {
- return false;
- }
-
- let numFrames = this._frameCache.length;
-
- this.getFrames(numFrames, total - numFrames, (response) => {
- if (response.error) {
- callback(response);
- return;
- }
-
- let threadGrips = DevToolsUtils.values(this._threadGrips);
-
- for (let i in response.frames) {
- let frame = response.frames[i];
- if (!frame.where.source) {
- // Older servers use urls instead, so we need to resolve
- // them to source actors
- for (let grip of threadGrips) {
- if (grip instanceof SourceClient && grip.url === frame.url) {
- frame.where.source = grip._form;
- }
- }
- }
-
- this._frameCache[frame.depth] = frame;
- }
-
- // If we got as many frames as we asked for, there might be more
- // frames available.
- this.emit("framesadded");
-
- callback(response);
- });
-
- return true;
- },
-
- /**
- * Clear the thread's stack frame cache. A framescleared event
- * will be sent.
- */
- _clearFrames: function () {
- if (this._frameCache.length > 0) {
- this._frameCache = [];
- this.emit("framescleared");
- }
- },
-
- /**
- * Return a ObjectClient object for the given object grip.
- *
- * @param grip object
- * A pause-lifetime object grip returned by the protocol.
- */
- pauseGrip: function (grip) {
- if (grip.actor in this._pauseGrips) {
- return this._pauseGrips[grip.actor];
- }
-
- let client = new ObjectClient(this.client, grip);
- this._pauseGrips[grip.actor] = client;
- return client;
- },
-
- /**
- * Get or create a long string client, checking the grip client cache if it
- * already exists.
- *
- * @param grip Object
- * The long string grip returned by the protocol.
- * @param gripCacheName String
- * The property name of the grip client cache to check for existing
- * clients in.
- */
- _longString: function (grip, gripCacheName) {
- if (grip.actor in this[gripCacheName]) {
- return this[gripCacheName][grip.actor];
- }
-
- let client = new LongStringClient(this.client, grip);
- this[gripCacheName][grip.actor] = client;
- return client;
- },
-
- /**
- * Return an instance of LongStringClient for the given long string grip that
- * is scoped to the current pause.
- *
- * @param grip Object
- * The long string grip returned by the protocol.
- */
- pauseLongString: function (grip) {
- return this._longString(grip, "_pauseGrips");
- },
-
- /**
- * Return an instance of LongStringClient for the given long string grip that
- * is scoped to the thread lifetime.
- *
- * @param grip Object
- * The long string grip returned by the protocol.
- */
- threadLongString: function (grip) {
- return this._longString(grip, "_threadGrips");
- },
-
- /**
- * Get or create an ArrayBuffer client, checking the grip client cache if it
- * already exists.
- *
- * @param grip Object
- * The ArrayBuffer grip returned by the protocol.
- * @param gripCacheName String
- * The property name of the grip client cache to check for existing
- * clients in.
- */
- _arrayBuffer: function (grip, gripCacheName) {
- if (grip.actor in this[gripCacheName]) {
- return this[gripCacheName][grip.actor];
- }
-
- let client = new ArrayBufferClient(this.client, grip);
- this[gripCacheName][grip.actor] = client;
- return client;
- },
-
- /**
- * Return an instance of ArrayBufferClient for the given ArrayBuffer grip that
- * is scoped to the thread lifetime.
- *
- * @param grip Object
- * The ArrayBuffer grip returned by the protocol.
- */
- threadArrayBuffer: function (grip) {
- return this._arrayBuffer(grip, "_threadGrips");
- },
-
- /**
- * Clear and invalidate all the grip clients from the given cache.
- *
- * @param gripCacheName
- * The property name of the grip cache we want to clear.
- */
- _clearObjectClients: function (gripCacheName) {
- for (let id in this[gripCacheName]) {
- this[gripCacheName][id].valid = false;
- }
- this[gripCacheName] = {};
- },
-
- /**
- * Invalidate pause-lifetime grip clients and clear the list of current grip
- * clients.
- */
- _clearPauseGrips: function () {
- this._clearObjectClients("_pauseGrips");
- },
-
- /**
- * Invalidate thread-lifetime grip clients and clear the list of current grip
- * clients.
- */
- _clearThreadGrips: function () {
- this._clearObjectClients("_threadGrips");
- },
-
- /**
- * Handle thread state change by doing necessary cleanup and notifying all
- * registered listeners.
- */
- _onThreadState: function (packet) {
- this._state = ThreadStateTypes[packet.type];
- // The debugger UI may not be initialized yet so we want to keep
- // the packet around so it knows what to pause state to display
- // when it's initialized
- this._lastPausePacket = packet.type === "resumed" ? null : packet;
- this._clearFrames();
- this._clearPauseGrips();
- packet.type === ThreadStateTypes.detached && this._clearThreadGrips();
- this.client._eventsEnabled && this.emit(packet.type, packet);
- },
-
- getLastPausePacket: function () {
- return this._lastPausePacket;
- },
-
- /**
- * Return an EnvironmentClient instance for the given environment actor form.
- */
- environment: function (form) {
- return new EnvironmentClient(this.client, form);
- },
-
- /**
- * Return an instance of SourceClient for the given source actor form.
- */
- source: function (form) {
- if (form.actor in this._threadGrips) {
- return this._threadGrips[form.actor];
- }
-
- this._threadGrips[form.actor] = new SourceClient(this, form);
- return this._threadGrips[form.actor];
- },
-
- /**
- * Request the prototype and own properties of mutlipleObjects.
- *
- * @param onResponse function
- * Called with the request's response.
- * @param actors [string]
- * List of actor ID of the queried objects.
- */
- getPrototypesAndProperties: DebuggerClient.requester({
- type: "prototypesAndProperties",
- actors: arg(0)
- }),
-
- events: ["newSource"]
-};
-
-eventSource(ThreadClient.prototype);
-
-/**
- * Creates a tracing profiler client for the remote debugging protocol
- * server. This client is a front to the trace actor created on the
- * server side, hiding the protocol details in a traditional
- * JavaScript API.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param actor string
- * The actor ID for this thread.
- */
-function TraceClient(client, actor) {
- this._client = client;
- this._actor = actor;
- this._activeTraces = new Set();
- this._waitingPackets = new Map();
- this._expectedPacket = 0;
- this.request = this._client.request;
- this.events = [];
-}
-
-TraceClient.prototype = {
- get actor() {
- return this._actor;
- },
- get tracing() {
- return this._activeTraces.size > 0;
- },
-
- get _transport() {
- return this._client._transport;
- },
-
- /**
- * Detach from the trace actor.
- */
- detach: DebuggerClient.requester({
- type: "detach"
- }, {
- after: function (response) {
- this._client.unregisterClient(this);
- return response;
- },
- }),
-
- /**
- * Start a new trace.
- *
- * @param trace [string]
- * An array of trace types to be recorded by the new trace.
- *
- * @param name string
- * The name of the new trace.
- *
- * @param onResponse function
- * Called with the request's response.
- */
- startTrace: DebuggerClient.requester({
- type: "startTrace",
- name: arg(1),
- trace: arg(0)
- }, {
- after: function (response) {
- if (response.error) {
- return response;
- }
-
- if (!this.tracing) {
- this._waitingPackets.clear();
- this._expectedPacket = 0;
- }
- this._activeTraces.add(response.name);
-
- return response;
- },
- }),
-
- /**
- * End a trace. If a name is provided, stop the named
- * trace. Otherwise, stop the most recently started trace.
- *
- * @param name string
- * The name of the trace to stop.
- *
- * @param onResponse function
- * Called with the request's response.
- */
- stopTrace: DebuggerClient.requester({
- type: "stopTrace",
- name: arg(0)
- }, {
- after: function (response) {
- if (response.error) {
- return response;
- }
-
- this._activeTraces.delete(response.name);
-
- return response;
- },
- })
-};
-
-/**
- * Grip clients are used to retrieve information about the relevant object.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param grip object
- * A pause-lifetime object grip returned by the protocol.
- */
-function ObjectClient(client, grip) {
- this._grip = grip;
- this._client = client;
- this.request = this._client.request;
-}
-exports.ObjectClient = ObjectClient;
-
-ObjectClient.prototype = {
- get actor() {
- return this._grip.actor;
- },
- get _transport() {
- return this._client._transport;
- },
-
- valid: true,
-
- get isFrozen() {
- return this._grip.frozen;
- },
- get isSealed() {
- return this._grip.sealed;
- },
- get isExtensible() {
- return this._grip.extensible;
- },
-
- getDefinitionSite: DebuggerClient.requester({
- type: "definitionSite"
- }, {
- before: function (packet) {
- if (this._grip.class != "Function") {
- throw new Error("getDefinitionSite is only valid for function grips.");
- }
- return packet;
- }
- }),
-
- /**
- * Request the names of a function's formal parameters.
- *
- * @param onResponse function
- * Called with an object of the form:
- * { parameterNames:[<parameterName>, ...] }
- * where each <parameterName> is the name of a parameter.
- */
- getParameterNames: DebuggerClient.requester({
- type: "parameterNames"
- }, {
- before: function (packet) {
- if (this._grip.class !== "Function") {
- throw new Error("getParameterNames is only valid for function grips.");
- }
- return packet;
- },
- }),
-
- /**
- * Request the names of the properties defined on the object and not its
- * prototype.
- *
- * @param onResponse function Called with the request's response.
- */
- getOwnPropertyNames: DebuggerClient.requester({
- type: "ownPropertyNames"
- }),
-
- /**
- * Request the prototype and own properties of the object.
- *
- * @param onResponse function Called with the request's response.
- */
- getPrototypeAndProperties: DebuggerClient.requester({
- type: "prototypeAndProperties"
- }),
-
- /**
- * Request a PropertyIteratorClient instance to ease listing
- * properties for this object.
- *
- * @param options Object
- * A dictionary object with various boolean attributes:
- * - ignoreIndexedProperties Boolean
- * If true, filters out Array items.
- * e.g. properties names between `0` and `object.length`.
- * - ignoreNonIndexedProperties Boolean
- * If true, filters out items that aren't array items
- * e.g. properties names that are not a number between `0`
- * and `object.length`.
- * - sort Boolean
- * If true, the iterator will sort the properties by name
- * before dispatching them.
- * @param onResponse function Called with the client instance.
- */
- enumProperties: DebuggerClient.requester({
- type: "enumProperties",
- options: arg(0)
- }, {
- after: function (response) {
- if (response.iterator) {
- return { iterator: new PropertyIteratorClient(this._client, response.iterator) };
- }
- return response;
- },
- }),
-
- /**
- * Request a PropertyIteratorClient instance to enumerate entries in a
- * Map/Set-like object.
- *
- * @param onResponse function Called with the request's response.
- */
- enumEntries: DebuggerClient.requester({
- type: "enumEntries"
- }, {
- before: function (packet) {
- if (!["Map", "WeakMap", "Set", "WeakSet"].includes(this._grip.class)) {
- throw new Error("enumEntries is only valid for Map/Set-like grips.");
- }
- return packet;
- },
- after: function (response) {
- if (response.iterator) {
- return {
- iterator: new PropertyIteratorClient(this._client, response.iterator)
- };
- }
- return response;
- }
- }),
-
- /**
- * Request a SymbolIteratorClient instance to enumerate symbols in an object.
- *
- * @param onResponse function Called with the request's response.
- */
- enumSymbols: DebuggerClient.requester({
- type: "enumSymbols"
- }, {
- before: function (packet) {
- if (this._grip.type !== "object") {
- throw new Error("enumSymbols is only valid for objects grips.");
- }
- return packet;
- },
- after: function (response) {
- if (response.iterator) {
- return {
- iterator: new SymbolIteratorClient(this._client, response.iterator)
- };
- }
- return response;
- }
- }),
-
- /**
- * Request the property descriptor of the object's specified property.
- *
- * @param name string The name of the requested property.
- * @param onResponse function Called with the request's response.
- */
- getProperty: DebuggerClient.requester({
- type: "property",
- name: arg(0)
- }),
-
- /**
- * Request the prototype of the object.
- *
- * @param onResponse function Called with the request's response.
- */
- getPrototype: DebuggerClient.requester({
- type: "prototype"
- }),
-
- /**
- * Request the display string of the object.
- *
- * @param onResponse function Called with the request's response.
- */
- getDisplayString: DebuggerClient.requester({
- type: "displayString"
- }),
-
- /**
- * Request the scope of the object.
- *
- * @param onResponse function Called with the request's response.
- */
- getScope: DebuggerClient.requester({
- type: "scope"
- }, {
- before: function (packet) {
- if (this._grip.class !== "Function") {
- throw new Error("scope is only valid for function grips.");
- }
- return packet;
- },
- }),
-
- /**
- * Request the promises directly depending on the current promise.
- */
- getDependentPromises: DebuggerClient.requester({
- type: "dependentPromises"
- }, {
- before: function (packet) {
- if (this._grip.class !== "Promise") {
- throw new Error("getDependentPromises is only valid for promise " +
- "grips.");
- }
- return packet;
- }
- }),
-
- /**
- * Request the stack to the promise's allocation point.
- */
- getPromiseAllocationStack: DebuggerClient.requester({
- type: "allocationStack"
- }, {
- before: function (packet) {
- if (this._grip.class !== "Promise") {
- throw new Error("getAllocationStack is only valid for promise grips.");
- }
- return packet;
- }
- }),
-
- /**
- * Request the stack to the promise's fulfillment point.
- */
- getPromiseFulfillmentStack: DebuggerClient.requester({
- type: "fulfillmentStack"
- }, {
- before: function (packet) {
- if (this._grip.class !== "Promise") {
- throw new Error("getPromiseFulfillmentStack is only valid for " +
- "promise grips.");
- }
- return packet;
- }
- }),
-
- /**
- * Request the stack to the promise's rejection point.
- */
- getPromiseRejectionStack: DebuggerClient.requester({
- type: "rejectionStack"
- }, {
- before: function (packet) {
- if (this._grip.class !== "Promise") {
- throw new Error("getPromiseRejectionStack is only valid for " +
- "promise grips.");
- }
- return packet;
- }
- })
-};
-
-/**
- * A PropertyIteratorClient provides a way to access to property names and
- * values of an object efficiently, slice by slice.
- * Note that the properties can be sorted in the backend,
- * this is controled while creating the PropertyIteratorClient
- * from ObjectClient.enumProperties.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param grip Object
- * A PropertyIteratorActor grip returned by the protocol via
- * TabActor.enumProperties request.
- */
-function PropertyIteratorClient(client, grip) {
- this._grip = grip;
- this._client = client;
- this.request = this._client.request;
-}
-
-PropertyIteratorClient.prototype = {
- get actor() {
- return this._grip.actor;
- },
-
- /**
- * Get the total number of properties available in the iterator.
- */
- get count() {
- return this._grip.count;
- },
-
- /**
- * Get one or more property names that correspond to the positions in the
- * indexes parameter.
- *
- * @param indexes Array
- * An array of property indexes.
- * @param callback Function
- * The function called when we receive the property names.
- */
- names: DebuggerClient.requester({
- type: "names",
- indexes: arg(0)
- }, {}),
-
- /**
- * Get a set of following property value(s).
- *
- * @param start Number
- * The index of the first property to fetch.
- * @param count Number
- * The number of properties to fetch.
- * @param callback Function
- * The function called when we receive the property values.
- */
- slice: DebuggerClient.requester({
- type: "slice",
- start: arg(0),
- count: arg(1)
- }, {}),
-
- /**
- * Get all the property values.
- *
- * @param callback Function
- * The function called when we receive the property values.
- */
- all: DebuggerClient.requester({
- type: "all"
- }, {}),
-};
-
-/**
- * A SymbolIteratorClient provides a way to access to symbols
- * of an object efficiently, slice by slice.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param grip Object
- * A SymbolIteratorActor grip returned by the protocol via
- * TabActor.enumSymbols request.
- */
-function SymbolIteratorClient(client, grip) {
- this._grip = grip;
- this._client = client;
- this.request = this._client.request;
-}
-
-SymbolIteratorClient.prototype = {
- get actor() {
- return this._grip.actor;
- },
-
- /**
- * Get the total number of symbols available in the iterator.
- */
- get count() {
- return this._grip.count;
- },
-
- /**
- * Get a set of following symbols.
- *
- * @param start Number
- * The index of the first symbol to fetch.
- * @param count Number
- * The number of symbols to fetch.
- * @param callback Function
- * The function called when we receive the symbols.
- */
- slice: DebuggerClient.requester({
- type: "slice",
- start: arg(0),
- count: arg(1)
- }, {}),
-
- /**
- * Get all the symbols.
- *
- * @param callback Function
- * The function called when we receive the symbols.
- */
- all: DebuggerClient.requester({
- type: "all"
- }, {}),
-};
-
-/**
- * A ArrayBufferClient provides a way to access ArrayBuffer from the
- * debugger server.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param grip Object
- * A pause-lifetime ArrayBuffer grip returned by the protocol.
- */
-function ArrayBufferClient(client, grip) {
- this._grip = grip;
- this._client = client;
- this.request = this._client.request;
-}
-ArrayBufferClient.prototype = {
- get actor() {
- return this._grip.actor;
- },
- get length() {
- return this._grip.length;
- },
- get _transport() {
- return this._client._transport;
- },
-
- valid: true,
-
- slice: DebuggerClient.requester({
- type: "slice",
- start: arg(0),
- count: arg(1)
- }),
-};
-
-/**
- * A LongStringClient provides a way to access "very long" strings from the
- * debugger server.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param grip Object
- * A pause-lifetime long string grip returned by the protocol.
- */
-function LongStringClient(client, grip) {
- this._grip = grip;
- this._client = client;
- this.request = this._client.request;
-}
-exports.LongStringClient = LongStringClient;
-
-LongStringClient.prototype = {
- get actor() {
- return this._grip.actor;
- },
- get length() {
- return this._grip.length;
- },
- get initial() {
- return this._grip.initial;
- },
- get _transport() {
- return this._client._transport;
- },
-
- valid: true,
-
- /**
- * Get the substring of this LongString from start to end.
- *
- * @param start Number
- * The starting index.
- * @param end Number
- * The ending index.
- * @param callback Function
- * The function called when we receive the substring.
- */
- substring: DebuggerClient.requester({
- type: "substring",
- start: arg(0),
- end: arg(1)
- }),
-};
-
-/**
- * A SourceClient provides a way to access the source text of a script.
- *
- * @param client ThreadClient
- * The thread client parent.
- * @param form Object
- * The form sent across the remote debugging protocol.
- */
-function SourceClient(client, form) {
- this._form = form;
- this._isBlackBoxed = form.isBlackBoxed;
- this._isPrettyPrinted = form.isPrettyPrinted;
- this._activeThread = client;
- this._client = client.client;
-}
-
-SourceClient.prototype = {
- get _transport() {
- return this._client._transport;
- },
- get isBlackBoxed() {
- return this._isBlackBoxed;
- },
- get isPrettyPrinted() {
- return this._isPrettyPrinted;
- },
- get actor() {
- return this._form.actor;
- },
- get request() {
- return this._client.request;
- },
- get url() {
- return this._form.url;
- },
-
- /**
- * Black box this SourceClient's source.
- *
- * @param callback Function
- * The callback function called when we receive the response from the server.
- */
- blackBox: DebuggerClient.requester({
- type: "blackbox"
- }, {
- after: function (response) {
- if (!response.error) {
- this._isBlackBoxed = true;
- if (this._activeThread) {
- this._activeThread.emit("blackboxchange", this);
- }
- }
- return response;
- }
- }),
-
- /**
- * Un-black box this SourceClient's source.
- *
- * @param callback Function
- * The callback function called when we receive the response from the server.
- */
- unblackBox: DebuggerClient.requester({
- type: "unblackbox"
- }, {
- after: function (response) {
- if (!response.error) {
- this._isBlackBoxed = false;
- if (this._activeThread) {
- this._activeThread.emit("blackboxchange", this);
- }
- }
- return response;
- }
- }),
-
- /**
- * Get Executable Lines from a source
- *
- * @param callback Function
- * The callback function called when we receive the response from the server.
- */
- getExecutableLines: function (cb = noop) {
- let packet = {
- to: this._form.actor,
- type: "getExecutableLines"
- };
-
- return this._client.request(packet).then(res => {
- cb(res.lines);
- return res.lines;
- });
- },
-
- /**
- * Get a long string grip for this SourceClient's source.
- */
- source: function (callback = noop) {
- let packet = {
- to: this._form.actor,
- type: "source"
- };
- return this._client.request(packet).then(response => {
- return this._onSourceResponse(response, callback);
- });
- },
-
- /**
- * Pretty print this source's text.
- */
- prettyPrint: function (indent, callback = noop) {
- const packet = {
- to: this._form.actor,
- type: "prettyPrint",
- indent
- };
- return this._client.request(packet).then(response => {
- if (!response.error) {
- this._isPrettyPrinted = true;
- this._activeThread._clearFrames();
- this._activeThread.emit("prettyprintchange", this);
- }
- return this._onSourceResponse(response, callback);
- });
- },
-
- /**
- * Stop pretty printing this source's text.
- */
- disablePrettyPrint: function (callback = noop) {
- const packet = {
- to: this._form.actor,
- type: "disablePrettyPrint"
- };
- return this._client.request(packet).then(response => {
- if (!response.error) {
- this._isPrettyPrinted = false;
- this._activeThread._clearFrames();
- this._activeThread.emit("prettyprintchange", this);
- }
- return this._onSourceResponse(response, callback);
- });
- },
-
- _onSourceResponse: function (response, callback) {
- if (response.error) {
- callback(response);
- return response;
- }
-
- if (typeof response.source === "string") {
- callback(response);
- return response;
- }
-
- let { contentType, source } = response;
- if (source.type === "arrayBuffer") {
- let arrayBuffer = this._activeThread.threadArrayBuffer(source);
- return arrayBuffer.slice(0, arrayBuffer.length).then(function (resp) {
- if (resp.error) {
- callback(resp);
- return resp;
- }
- // Keeping str as a string, ArrayBuffer/Uint8Array will not survive
- // setIn/mergeIn operations.
- const str = atob(resp.encoded);
- let newResponse = {
- source: {
- binary: str,
- toString: () => "[wasm]",
- },
- contentType,
- };
- callback(newResponse);
- return newResponse;
- });
- }
-
- let longString = this._activeThread.threadLongString(source);
- return longString.substring(0, longString.length).then(function (resp) {
- if (resp.error) {
- callback(resp);
- return resp;
- }
-
- let newResponse = {
- source: resp.substring,
- contentType: contentType
- };
- callback(newResponse);
- return newResponse;
- });
- },
-
- /**
- * Request to set a breakpoint in the specified location.
- *
- * @param object location
- * The location and condition of the breakpoint in
- * the form of { line[, column, condition] }.
- * @param function onResponse
- * Called with the thread's response.
- */
- setBreakpoint: function ({ line, column, condition, noSliding }, onResponse = noop) {
- // A helper function that sets the breakpoint.
- let doSetBreakpoint = callback => {
- let root = this._client.mainRoot;
- let location = {
- line,
- column,
- };
-
- let packet = {
- to: this.actor,
- type: "setBreakpoint",
- location,
- condition,
- noSliding,
- };
-
- // Backwards compatibility: send the breakpoint request to the
- // thread if the server doesn't support Debugger.Source actors.
- if (!root.traits.debuggerSourceActors) {
- packet.to = this._activeThread.actor;
- packet.location.url = this.url;
- }
-
- return this._client.request(packet).then(response => {
- // Ignoring errors, since the user may be setting a breakpoint in a
- // dead script that will reappear on a page reload.
- let bpClient;
- if (response.actor) {
- bpClient = new BreakpointClient(
- this._client,
- this,
- response.actor,
- location,
- root.traits.conditionalBreakpoints ? condition : undefined
- );
- }
- onResponse(response, bpClient);
- if (callback) {
- callback();
- }
- return [response, bpClient];
- });
- };
-
- // If the debuggee is paused, just set the breakpoint.
- if (this._activeThread.paused) {
- return doSetBreakpoint();
- }
- // Otherwise, force a pause in order to set the breakpoint.
- return this._activeThread.interrupt().then(response => {
- if (response.error) {
- // Can't set the breakpoint if pausing failed.
- onResponse(response);
- return response;
- }
-
- const { type, why } = response;
- const cleanUp = type == "paused" && why.type == "interrupted"
- ? () => this._activeThread.resume()
- : noop;
-
- return doSetBreakpoint(cleanUp);
- });
- }
-};
-
-/**
- * Breakpoint clients are used to remove breakpoints that are no longer used.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param sourceClient SourceClient
- * The source where this breakpoint exists
- * @param actor string
- * The actor ID for this breakpoint.
- * @param location object
- * The location of the breakpoint. This is an object with two properties:
- * url and line.
- * @param condition string
- * The conditional expression of the breakpoint
- */
-function BreakpointClient(client, sourceClient, actor, location, condition) {
- this._client = client;
- this._actor = actor;
- this.location = location;
- this.location.actor = sourceClient.actor;
- this.location.url = sourceClient.url;
- this.source = sourceClient;
- this.request = this._client.request;
-
- // The condition property should only exist if it's a truthy value
- if (condition) {
- this.condition = condition;
- }
-}
-
-BreakpointClient.prototype = {
-
- _actor: null,
- get actor() {
- return this._actor;
- },
- get _transport() {
- return this._client._transport;
- },
-
- /**
- * Remove the breakpoint from the server.
- */
- remove: DebuggerClient.requester({
- type: "delete"
- }),
-
- /**
- * Determines if this breakpoint has a condition
- */
- hasCondition: function () {
- let root = this._client.mainRoot;
- // XXX bug 990137: We will remove support for client-side handling of
- // conditional breakpoints
- if (root.traits.conditionalBreakpoints) {
- return "condition" in this;
- }
- return "conditionalExpression" in this;
- },
-
- /**
- * Get the condition of this breakpoint. Currently we have to
- * support locally emulated conditional breakpoints until the
- * debugger servers are updated (see bug 990137). We used a
- * different property when moving it server-side to ensure that we
- * are testing the right code.
- */
- getCondition: function () {
- let root = this._client.mainRoot;
- if (root.traits.conditionalBreakpoints) {
- return this.condition;
- }
- return this.conditionalExpression;
- },
-
- /**
- * Set the condition of this breakpoint
- */
- setCondition: function (gThreadClient, condition) {
- let root = this._client.mainRoot;
- let deferred = promise.defer();
-
- if (root.traits.conditionalBreakpoints) {
- let info = {
- line: this.location.line,
- column: this.location.column,
- condition: condition
- };
-
- // Remove the current breakpoint and add a new one with the
- // condition.
- this.remove(response => {
- if (response && response.error) {
- deferred.reject(response);
- return;
- }
-
- this.source.setBreakpoint(info, (resp, newBreakpoint) => {
- if (resp && resp.error) {
- deferred.reject(resp);
- } else {
- deferred.resolve(newBreakpoint);
- }
- });
- });
- } else {
- // The property shouldn't even exist if the condition is blank
- if (condition === "") {
- delete this.conditionalExpression;
- } else {
- this.conditionalExpression = condition;
- }
- deferred.resolve(this);
- }
-
- return deferred.promise;
- }
-};
-
-eventSource(BreakpointClient.prototype);
-
-/**
- * Environment clients are used to manipulate the lexical environment actors.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param form Object
- * The form sent across the remote debugging protocol.
- */
-function EnvironmentClient(client, form) {
- this._client = client;
- this._form = form;
- this.request = this._client.request;
-}
-exports.EnvironmentClient = EnvironmentClient;
-
-EnvironmentClient.prototype = {
-
- get actor() {
- return this._form.actor;
- },
- get _transport() {
- return this._client._transport;
- },
-
- /**
- * Fetches the bindings introduced by this lexical environment.
- */
- getBindings: DebuggerClient.requester({
- type: "bindings"
- }),
-
- /**
- * Changes the value of the identifier whose name is name (a string) to that
- * represented by value (a grip).
- */
- assign: DebuggerClient.requester({
- type: "assign",
- name: arg(0),
- value: arg(1)
- })
-};
-
-eventSource(EnvironmentClient.prototype);
+module.exports = eventSource;
copy from devtools/shared/client/main.js
copy to devtools/shared/client/long-string-client.js
--- a/devtools/shared/client/main.js
+++ b/devtools/shared/client/long-string-client.js
@@ -1,2966 +1,29 @@
-/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
-/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
-const { Ci, Cu } = require("chrome");
-const DevToolsUtils = require("devtools/shared/DevToolsUtils");
-const { getStack, callFunctionWithAsyncStack } = require("devtools/shared/platform/stack");
-
-const promise = Cu.import("resource://devtools/shared/deprecated-sync-thenables.js", {}).Promise;
-
-loader.lazyRequireGetter(this, "EventEmitter", "devtools/shared/event-emitter");
-loader.lazyRequireGetter(this, "WebConsoleClient", "devtools/shared/webconsole/client", true);
-loader.lazyRequireGetter(this, "DebuggerSocket", "devtools/shared/security/socket", true);
-loader.lazyRequireGetter(this, "Authentication", "devtools/shared/security/auth");
-
-const noop = () => {};
-
-/**
- * TODO: Get rid of this API in favor of EventTarget (bug 1042642)
- *
- * Add simple event notification to a prototype object. Any object that has
- * some use for event notifications or the observer pattern in general can be
- * augmented with the necessary facilities by passing its prototype to this
- * function.
- *
- * @param proto object
- * The prototype object that will be modified.
- */
-function eventSource(proto) {
- /**
- * Add a listener to the event source for a given event.
- *
- * @param name string
- * The event to listen for.
- * @param listener function
- * Called when the event is fired. If the same listener
- * is added more than once, it will be called once per
- * addListener call.
- */
- proto.addListener = function (name, listener) {
- if (typeof listener != "function") {
- throw TypeError("Listeners must be functions.");
- }
-
- if (!this._listeners) {
- this._listeners = {};
- }
-
- this._getListeners(name).push(listener);
- };
-
- /**
- * Add a listener to the event source for a given event. The
- * listener will be removed after it is called for the first time.
- *
- * @param name string
- * The event to listen for.
- * @param listener function
- * Called when the event is fired.
- */
- proto.addOneTimeListener = function (name, listener) {
- let l = (...args) => {
- this.removeListener(name, l);
- listener.apply(null, args);
- };
- this.addListener(name, l);
- };
-
- /**
- * Remove a listener from the event source previously added with
- * addListener().
- *
- * @param name string
- * The event name used during addListener to add the listener.
- * @param listener function
- * The callback to remove. If addListener was called multiple
- * times, all instances will be removed.
- */
- proto.removeListener = function (name, listener) {
- if (!this._listeners || (listener && !this._listeners[name])) {
- return;
- }
-
- if (!listener) {
- this._listeners[name] = [];
- } else {
- this._listeners[name] =
- this._listeners[name].filter(l => l != listener);
- }
- };
-
- /**
- * Returns the listeners for the specified event name. If none are defined it
- * initializes an empty list and returns that.
- *
- * @param name string
- * The event name.
- */
- proto._getListeners = function (name) {
- if (name in this._listeners) {
- return this._listeners[name];
- }
- this._listeners[name] = [];
- return this._listeners[name];
- };
-
- /**
- * Notify listeners of an event.
- *
- * @param name string
- * The event to fire.
- * @param arguments
- * All arguments will be passed along to the listeners,
- * including the name argument.
- */
- proto.emit = function () {
- if (!this._listeners) {
- return;
- }
-
- let name = arguments[0];
- let listeners = this._getListeners(name).slice(0);
-
- for (let listener of listeners) {
- try {
- listener.apply(null, arguments);
- } catch (e) {
- // Prevent a bad listener from interfering with the others.
- DevToolsUtils.reportException("notify event '" + name + "'", e);
- }
- }
- };
-}
-
-/**
- * Set of protocol messages that affect thread state, and the
- * state the actor is in after each message.
- */
-const ThreadStateTypes = {
- "paused": "paused",
- "resumed": "attached",
- "detached": "detached",
- "running": "attached"
-};
-
-/**
- * Set of protocol messages that are sent by the server without a prior request
- * by the client.
- */
-const UnsolicitedNotifications = {
- "consoleAPICall": "consoleAPICall",
- "eventNotification": "eventNotification",
- "fileActivity": "fileActivity",
- "lastPrivateContextExited": "lastPrivateContextExited",
- "logMessage": "logMessage",
- "networkEvent": "networkEvent",
- "networkEventUpdate": "networkEventUpdate",
- "newGlobal": "newGlobal",
- "newScript": "newScript",
- "tabDetached": "tabDetached",
- "tabListChanged": "tabListChanged",
- "reflowActivity": "reflowActivity",
- "addonListChanged": "addonListChanged",
- "workerListChanged": "workerListChanged",
- "serviceWorkerRegistrationListChanged": "serviceWorkerRegistrationList",
- "tabNavigated": "tabNavigated",
- "frameUpdate": "frameUpdate",
- "pageError": "pageError",
- "documentLoad": "documentLoad",
- "enteredFrame": "enteredFrame",
- "exitedFrame": "exitedFrame",
- "appOpen": "appOpen",
- "appClose": "appClose",
- "appInstall": "appInstall",
- "appUninstall": "appUninstall",
- "evaluationResult": "evaluationResult",
- "newSource": "newSource",
- "updatedSource": "updatedSource",
- "inspectObject": "inspectObject"
-};
-
-/**
- * Set of pause types that are sent by the server and not as an immediate
- * response to a client request.
- */
-const UnsolicitedPauses = {
- "resumeLimit": "resumeLimit",
- "debuggerStatement": "debuggerStatement",
- "breakpoint": "breakpoint",
- "DOMEvent": "DOMEvent",
- "watchpoint": "watchpoint",
- "exception": "exception"
-};
-
-/**
- * Creates a client for the remote debugging protocol server. This client
- * provides the means to communicate with the server and exchange the messages
- * required by the protocol in a traditional JavaScript API.
- */
-const DebuggerClient = exports.DebuggerClient = function (transport) {
- this._transport = transport;
- this._transport.hooks = this;
-
- // Map actor ID to client instance for each actor type.
- this._clients = new Map();
-
- this._pendingRequests = new Map();
- this._activeRequests = new Map();
- this._eventsEnabled = true;
-
- this.traits = {};
-
- this.request = this.request.bind(this);
- this.localTransport = this._transport.onOutputStreamReady === undefined;
-
- /*
- * As the first thing on the connection, expect a greeting packet from
- * the connection's root actor.
- */
- this.mainRoot = null;
- this.expectReply("root", (packet) => {
- this.mainRoot = new RootClient(this, packet);
- this.emit("connected", packet.applicationType, packet.traits);
- });
-};
-
-/**
- * A declarative helper for defining methods that send requests to the server.
- *
- * @param packetSkeleton
- * The form of the packet to send. Can specify fields to be filled from
- * the parameters by using the |arg| function.
- * @param before
- * The function to call before sending the packet. Is passed the packet,
- * and the return value is used as the new packet. The |this| context is
- * the instance of the client object we are defining a method for.
- * @param after
- * The function to call after the response is received. It is passed the
- * response, and the return value is considered the new response that
- * will be passed to the callback. The |this| context is the instance of
- * the client object we are defining a method for.
- * @return Request
- * The `Request` object that is a Promise object and resolves once
- * we receive the response. (See request method for more details)
- */
-DebuggerClient.requester = function (packetSkeleton, config = {}) {
- let { before, after } = config;
- return DevToolsUtils.makeInfallible(function (...args) {
- let outgoingPacket = {
- to: packetSkeleton.to || this.actor
- };
-
- let maxPosition = -1;
- for (let k of Object.keys(packetSkeleton)) {
- if (packetSkeleton[k] instanceof DebuggerClient.Argument) {
- let { position } = packetSkeleton[k];
- outgoingPacket[k] = packetSkeleton[k].getArgument(args);
- maxPosition = Math.max(position, maxPosition);
- } else {
- outgoingPacket[k] = packetSkeleton[k];
- }
- }
-
- if (before) {
- outgoingPacket = before.call(this, outgoingPacket);
- }
-
- return this.request(outgoingPacket, DevToolsUtils.makeInfallible((response) => {
- if (after) {
- let { from } = response;
- response = after.call(this, response);
- if (!response.from) {
- response.from = from;
- }
- }
-
- // The callback is always the last parameter.
- let thisCallback = args[maxPosition + 1];
- if (thisCallback) {
- thisCallback(response);
- }
- return response;
- }, "DebuggerClient.requester request callback"));
- }, "DebuggerClient.requester");
-};
-
-function arg(pos) {
- return new DebuggerClient.Argument(pos);
-}
-
-DebuggerClient.Argument = function (position) {
- this.position = position;
-};
-
-DebuggerClient.Argument.prototype.getArgument = function (params) {
- if (!(this.position in params)) {
- throw new Error("Bad index into params: " + this.position);
- }
- return params[this.position];
-};
-
-// Expose these to save callers the trouble of importing DebuggerSocket
-DebuggerClient.socketConnect = function (options) {
- // Defined here instead of just copying the function to allow lazy-load
- return DebuggerSocket.connect(options);
-};
-DevToolsUtils.defineLazyGetter(DebuggerClient, "Authenticators", () => {
- return Authentication.Authenticators;
-});
-DevToolsUtils.defineLazyGetter(DebuggerClient, "AuthenticationResult", () => {
- return Authentication.AuthenticationResult;
-});
-
-DebuggerClient.prototype = {
- /**
- * Connect to the server and start exchanging protocol messages.
- *
- * @param onConnected function
- * If specified, will be called when the greeting packet is
- * received from the debugging server.
- *
- * @return Promise
- * Resolves once connected with an array whose first element
- * is the application type, by default "browser", and the second
- * element is the traits object (help figure out the features
- * and behaviors of the server we connect to. See RootActor).
- */
- connect: function (onConnected) {
- let deferred = promise.defer();
- this.emit("connect");
-
- // Also emit the event on the |DebuggerClient| object (not on the instance),
- // so it's possible to track all instances.
- EventEmitter.emit(DebuggerClient, "connect", this);
-
- this.addOneTimeListener("connected", (name, applicationType, traits) => {
- this.traits = traits;
- if (onConnected) {
- onConnected(applicationType, traits);
- }
- deferred.resolve([applicationType, traits]);
- });
-
- this._transport.ready();
- return deferred.promise;
- },
-
- /**
- * Shut down communication with the debugging server.
- *
- * @param onClosed function
- * If specified, will be called when the debugging connection
- * has been closed. This parameter is deprecated - please use
- * the returned Promise.
- * @return Promise
- * Resolves after the underlying transport is closed.
- */
- close: function (onClosed) {
- let deferred = promise.defer();
- if (onClosed) {
- deferred.promise.then(onClosed);
- }
-
- // Disable detach event notifications, because event handlers will be in a
- // cleared scope by the time they run.
- this._eventsEnabled = false;
-
- let cleanup = () => {
- this._transport.close();
- this._transport = null;
- };
-
- // If the connection is already closed,
- // there is no need to detach client
- // as we won't be able to send any message.
- if (this._closed) {
- cleanup();
- deferred.resolve();
- return deferred.promise;
- }
-
- this.addOneTimeListener("closed", deferred.resolve);
-
- // Call each client's `detach` method by calling
- // lastly registered ones first to give a chance
- // to detach child clients first.
- let clients = [...this._clients.values()];
- this._clients.clear();
- const detachClients = () => {
- let client = clients.pop();
- if (!client) {
- // All clients detached.
- cleanup();
- return;
- }
- if (client.detach) {
- client.detach(detachClients);
- return;
- }
- detachClients();
- };
- detachClients();
-
- return deferred.promise;
- },
-
- /*
- * This function exists only to preserve DebuggerClient's interface;
- * new code should say 'client.mainRoot.listTabs()'.
- */
- listTabs: function (onResponse) {
- return this.mainRoot.listTabs(onResponse);
- },
-
- /*
- * This function exists only to preserve DebuggerClient's interface;
- * new code should say 'client.mainRoot.listAddons()'.
- */
- listAddons: function (onResponse) {
- return this.mainRoot.listAddons(onResponse);
- },
-
- getTab: function (filter) {
- return this.mainRoot.getTab(filter);
- },
-
- /**
- * Attach to a tab actor.
- *
- * @param string tabActor
- * The actor ID for the tab to attach.
- * @param function onResponse
- * Called with the response packet and a TabClient
- * (which will be undefined on error).
- */
- attachTab: function (tabActor, onResponse = noop) {
- if (this._clients.has(tabActor)) {
- let cachedTab = this._clients.get(tabActor);
- let cachedResponse = {
- cacheDisabled: cachedTab.cacheDisabled,
- javascriptEnabled: cachedTab.javascriptEnabled,
- traits: cachedTab.traits,
- };
- DevToolsUtils.executeSoon(() => onResponse(cachedResponse, cachedTab));
- return promise.resolve([cachedResponse, cachedTab]);
- }
-
- let packet = {
- to: tabActor,
- type: "attach"
- };
- return this.request(packet).then(response => {
- let tabClient;
- if (!response.error) {
- tabClient = new TabClient(this, response);
- this.registerClient(tabClient);
- }
- onResponse(response, tabClient);
- return [response, tabClient];
- });
- },
-
- attachWorker: function (workerActor, onResponse = noop) {
- let workerClient = this._clients.get(workerActor);
- if (workerClient !== undefined) {
- let response = {
- from: workerClient.actor,
- type: "attached",
- url: workerClient.url
- };
- DevToolsUtils.executeSoon(() => onResponse(response, workerClient));
- return promise.resolve([response, workerClient]);
- }
-
- return this.request({ to: workerActor, type: "attach" }).then(response => {
- if (response.error) {
- onResponse(response, null);
- return [response, null];
- }
-
- workerClient = new WorkerClient(this, response);
- this.registerClient(workerClient);
- onResponse(response, workerClient);
- return [response, workerClient];
- });
- },
-
- /**
- * Attach to an addon actor.
- *
- * @param string addonActor
- * The actor ID for the addon to attach.
- * @param function onResponse
- * Called with the response packet and a AddonClient
- * (which will be undefined on error).
- */
- attachAddon: function (addonActor, onResponse = noop) {
- let packet = {
- to: addonActor,
- type: "attach"
- };
- return this.request(packet).then(response => {
- let addonClient;
- if (!response.error) {
- addonClient = new AddonClient(this, addonActor);
- this.registerClient(addonClient);
- this.activeAddon = addonClient;
- }
- onResponse(response, addonClient);
- return [response, addonClient];
- });
- },
-
- /**
- * Attach to a Web Console actor.
- *
- * @param string consoleActor
- * The ID for the console actor to attach to.
- * @param array listeners
- * The console listeners you want to start.
- * @param function onResponse
- * Called with the response packet and a WebConsoleClient
- * instance (which will be undefined on error).
- */
- attachConsole:
- function (consoleActor, listeners, onResponse = noop) {
- let packet = {
- to: consoleActor,
- type: "startListeners",
- listeners: listeners,
- };
-
- return this.request(packet).then(response => {
- let consoleClient;
- if (!response.error) {
- if (this._clients.has(consoleActor)) {
- consoleClient = this._clients.get(consoleActor);
- } else {
- consoleClient = new WebConsoleClient(this, response);
- this.registerClient(consoleClient);
- }
- }
- onResponse(response, consoleClient);
- return [response, consoleClient];
- });
- },
-
- /**
- * Attach to a global-scoped thread actor for chrome debugging.
- *
- * @param string threadActor
- * The actor ID for the thread to attach.
- * @param function onResponse
- * Called with the response packet and a ThreadClient
- * (which will be undefined on error).
- * @param object options
- * Configuration options.
- * - useSourceMaps: whether to use source maps or not.
- */
- attachThread: function (threadActor, onResponse = noop, options = {}) {
- if (this._clients.has(threadActor)) {
- let client = this._clients.get(threadActor);
- DevToolsUtils.executeSoon(() => onResponse({}, client));
- return promise.resolve([{}, client]);
- }
-
- let packet = {
- to: threadActor,
- type: "attach",
- options,
- };
- return this.request(packet).then(response => {
- let threadClient;
- if (!response.error) {
- threadClient = new ThreadClient(this, threadActor);
- this.registerClient(threadClient);
- }
- onResponse(response, threadClient);
- return [response, threadClient];
- });
- },
-
- /**
- * Attach to a trace actor.
- *
- * @param string traceActor
- * The actor ID for the tracer to attach.
- * @param function onResponse
- * Called with the response packet and a TraceClient
- * (which will be undefined on error).
- */
- attachTracer: function (traceActor, onResponse = noop) {
- if (this._clients.has(traceActor)) {
- let client = this._clients.get(traceActor);
- DevToolsUtils.executeSoon(() => onResponse({}, client));
- return promise.resolve([{}, client]);
- }
-
- let packet = {
- to: traceActor,
- type: "attach"
- };
- return this.request(packet).then(response => {
- let traceClient;
- if (!response.error) {
- traceClient = new TraceClient(this, traceActor);
- this.registerClient(traceClient);
- }
- onResponse(response, traceClient);
- return [response, traceClient];
- });
- },
-
- /**
- * Fetch the ChromeActor for the main process or ChildProcessActor for a
- * a given child process ID.
- *
- * @param number id
- * The ID for the process to attach (returned by `listProcesses`).
- * Connected to the main process if omitted, or is 0.
- */
- getProcess: function (id) {
- let packet = {
- to: "root",
- type: "getProcess"
- };
- if (typeof (id) == "number") {
- packet.id = id;
- }
- return this.request(packet);
- },
-
- /**
- * Release an object actor.
- *
- * @param string actor
- * The actor ID to send the request to.
- * @param onResponse function
- * If specified, will be called with the response packet when
- * debugging server responds.
- */
- release: DebuggerClient.requester({
- to: arg(0),
- type: "release"
- }),
-
- /**
- * Send a request to the debugging server.
- *
- * @param packet object
- * A JSON packet to send to the debugging server.
- * @param onResponse function
- * If specified, will be called with the JSON response packet when
- * debugging server responds.
- * @return Request
- * This object emits a number of events to allow you to respond to
- * different parts of the request lifecycle.
- * It is also a Promise object, with a `then` method, that is resolved
- * whenever a JSON or a Bulk response is received; and is rejected
- * if the response is an error.
- * Note: This return value can be ignored if you are using JSON alone,
- * because the callback provided in |onResponse| will be bound to the
- * "json-reply" event automatically.
- *
- * Events emitted:
- * * json-reply: The server replied with a JSON packet, which is
- * passed as event data.
- * * bulk-reply: The server replied with bulk data, which you can read
- * using the event data object containing:
- * * actor: Name of actor that received the packet
- * * type: Name of actor's method that was called on receipt
- * * length: Size of the data to be read
- * * stream: This input stream should only be used directly if you
- * can ensure that you will read exactly |length| bytes
- * and will not close the stream when reading is complete
- * * done: If you use the stream directly (instead of |copyTo|
- * below), you must signal completion by resolving /
- * rejecting this deferred. If it's rejected, the
- * transport will be closed. If an Error is supplied as a
- * rejection value, it will be logged via |dumpn|. If you
- * do use |copyTo|, resolving is taken care of for you
- * when copying completes.
- * * copyTo: A helper function for getting your data out of the
- * stream that meets the stream handling requirements
- * above, and has the following signature:
- * @param output nsIAsyncOutputStream
- * The stream to copy to.
- * @return Promise
- * The promise is resolved when copying completes or
- * rejected if any (unexpected) errors occur.
- * This object also emits "progress" events for each chunk
- * that is copied. See stream-utils.js.
- */
- request: function (packet, onResponse) {
- if (!this.mainRoot) {
- throw Error("Have not yet received a hello packet from the server.");
- }
- let type = packet.type || "";
- if (!packet.to) {
- throw Error("'" + type + "' request packet has no destination.");
- }
-
- // The onResponse callback might modify the response, so we need to call
- // it and resolve the promise with its result if it's truthy.
- const safeOnResponse = response => {
- if (!onResponse) {
- return response;
- }
- return onResponse(response) || response;
- };
-
- if (this._closed) {
- let msg = "'" + type + "' request packet to " +
- "'" + packet.to + "' " +
- "can't be sent as the connection is closed.";
- let resp = { error: "connectionClosed", message: msg };
- return promise.reject(safeOnResponse(resp));
- }
-
- let request = new Request(packet);
- request.format = "json";
- request.stack = getStack();
-
- // Implement a Promise like API on the returned object
- // that resolves/rejects on request response
- let deferred = promise.defer();
- function listenerJson(resp) {
- removeRequestListeners();
- if (resp.error) {
- deferred.reject(safeOnResponse(resp));
- } else {
- deferred.resolve(safeOnResponse(resp));
- }
- }
- function listenerBulk(resp) {
- removeRequestListeners();
- deferred.resolve(safeOnResponse(resp));
- }
-
- const removeRequestListeners = () => {
- request.off("json-reply", listenerJson);
- request.off("bulk-reply", listenerBulk);
- };
-
- request.on("json-reply", listenerJson);
- request.on("bulk-reply", listenerBulk);
-
- this._sendOrQueueRequest(request);
- request.then = deferred.promise.then.bind(deferred.promise);
-
- return request;
- },
-
- /**
- * Transmit streaming data via a bulk request.
- *
- * This method initiates the bulk send process by queuing up the header data.
- * The caller receives eventual access to a stream for writing.
- *
- * Since this opens up more options for how the server might respond (it could
- * send back either JSON or bulk data), and the returned Request object emits
- * events for different stages of the request process that you may want to
- * react to.
- *
- * @param request Object
- * This is modeled after the format of JSON packets above, but does not
- * actually contain the data, but is instead just a routing header:
- * * actor: Name of actor that will receive the packet
- * * type: Name of actor's method that should be called on receipt
- * * length: Size of the data to be sent
- * @return Request
- * This object emits a number of events to allow you to respond to
- * different parts of the request lifecycle.
- *
- * Events emitted:
- * * bulk-send-ready: Ready to send bulk data to the server, using the
- * event data object containing:
- * * stream: This output stream should only be used directly if
- * you can ensure that you will write exactly |length|
- * bytes and will not close the stream when writing is
- * complete
- * * done: If you use the stream directly (instead of |copyFrom|
- * below), you must signal completion by resolving /
- * rejecting this deferred. If it's rejected, the
- * transport will be closed. If an Error is supplied as
- * a rejection value, it will be logged via |dumpn|. If
- * you do use |copyFrom|, resolving is taken care of for
- * you when copying completes.
- * * copyFrom: A helper function for getting your data onto the
- * stream that meets the stream handling requirements
- * above, and has the following signature:
- * @param input nsIAsyncInputStream
- * The stream to copy from.
- * @return Promise
- * The promise is resolved when copying completes or
- * rejected if any (unexpected) errors occur.
- * This object also emits "progress" events for each chunk
- * that is copied. See stream-utils.js.
- * * json-reply: The server replied with a JSON packet, which is
- * passed as event data.
- * * bulk-reply: The server replied with bulk data, which you can read
- * using the event data object containing:
- * * actor: Name of actor that received the packet
- * * type: Name of actor's method that was called on receipt
- * * length: Size of the data to be read
- * * stream: This input stream should only be used directly if you
- * can ensure that you will read exactly |length| bytes
- * and will not close the stream when reading is complete
- * * done: If you use the stream directly (instead of |copyTo|
- * below), you must signal completion by resolving /
- * rejecting this deferred. If it's rejected, the
- * transport will be closed. If an Error is supplied as a
- * rejection value, it will be logged via |dumpn|. If you
- * do use |copyTo|, resolving is taken care of for you
- * when copying completes.
- * * copyTo: A helper function for getting your data out of the
- * stream that meets the stream handling requirements
- * above, and has the following signature:
- * @param output nsIAsyncOutputStream
- * The stream to copy to.
- * @return Promise
- * The promise is resolved when copying completes or
- * rejected if any (unexpected) errors occur.
- * This object also emits "progress" events for each chunk
- * that is copied. See stream-utils.js.
- */
- startBulkRequest: function (request) {
- if (!this.traits.bulk) {
- throw Error("Server doesn't support bulk transfers");
- }
- if (!this.mainRoot) {
- throw Error("Have not yet received a hello packet from the server.");
- }
- if (!request.type) {
- throw Error("Bulk packet is missing the required 'type' field.");
- }
- if (!request.actor) {
- throw Error("'" + request.type + "' bulk packet has no destination.");
- }
- if (!request.length) {
- throw Error("'" + request.type + "' bulk packet has no length.");
- }
-
- request = new Request(request);
- request.format = "bulk";
-
- this._sendOrQueueRequest(request);
-
- return request;
- },
-
- /**
- * If a new request can be sent immediately, do so. Otherwise, queue it.
- */
- _sendOrQueueRequest(request) {
- let actor = request.actor;
- if (!this._activeRequests.has(actor)) {
- this._sendRequest(request);
- } else {
- this._queueRequest(request);
- }
- },
-
- /**
- * Send a request.
- * @throws Error if there is already an active request in flight for the same
- * actor.
- */
- _sendRequest(request) {
- let actor = request.actor;
- this.expectReply(actor, request);
-
- if (request.format === "json") {
- this._transport.send(request.request);
- return;
- }
-
- this._transport.startBulkSend(request.request).then((...args) => {
- request.emit("bulk-send-ready", ...args);
- });
- },
-
- /**
- * Queue a request to be sent later. Queues are only drained when an in
- * flight request to a given actor completes.
- */
- _queueRequest(request) {
- let actor = request.actor;
- let queue = this._pendingRequests.get(actor) || [];
- queue.push(request);
- this._pendingRequests.set(actor, queue);
- },
-
- /**
- * Attempt the next request to a given actor (if any).
- */
- _attemptNextRequest(actor) {
- if (this._activeRequests.has(actor)) {
- return;
- }
- let queue = this._pendingRequests.get(actor);
- if (!queue) {
- return;
- }
- let request = queue.shift();
- if (queue.length === 0) {
- this._pendingRequests.delete(actor);
- }
- this._sendRequest(request);
- },
-
- /**
- * Arrange to hand the next reply from |actor| to the handler bound to
- * |request|.
- *
- * DebuggerClient.prototype.request / startBulkRequest usually takes care of
- * establishing the handler for a given request, but in rare cases (well,
- * greetings from new root actors, is the only case at the moment) we must be
- * prepared for a "reply" that doesn't correspond to any request we sent.
- */
- expectReply: function (actor, request) {
- if (this._activeRequests.has(actor)) {
- throw Error("clashing handlers for next reply from " + actor);
- }
-
- // If a handler is passed directly (as it is with the handler for the root
- // actor greeting), create a dummy request to bind this to.
- if (typeof request === "function") {
- let handler = request;
- request = new Request();
- request.on("json-reply", handler);
- }
-
- this._activeRequests.set(actor, request);
- },
-
- // Transport hooks.
-
- /**
- * Called by DebuggerTransport to dispatch incoming packets as appropriate.
- *
- * @param packet object
- * The incoming packet.
- */
- onPacket: function (packet) {
- if (!packet.from) {
- DevToolsUtils.reportException(
- "onPacket",
- new Error("Server did not specify an actor, dropping packet: " +
- JSON.stringify(packet)));
- return;
- }
-
- // If we have a registered Front for this actor, let it handle the packet
- // and skip all the rest of this unpleasantness.
- let front = this.getActor(packet.from);
- if (front) {
- front.onPacket(packet);
- return;
- }
-
- // Check for "forwardingCancelled" here instead of using a client to handle it.
- // This is necessary because we might receive this event while the client is closing,
- // and the clients have already been removed by that point.
- if (this.mainRoot &&
- packet.from == this.mainRoot.actor &&
- packet.type == "forwardingCancelled") {
- this.purgeRequests(packet.prefix);
- return;
- }
-
- if (this._clients.has(packet.from) && packet.type) {
- let client = this._clients.get(packet.from);
- let type = packet.type;
- if (client.events.indexOf(type) != -1) {
- client.emit(type, packet);
- // we ignore the rest, as the client is expected to handle this packet.
- return;
- }
- }
-
- let activeRequest;
- // See if we have a handler function waiting for a reply from this
- // actor. (Don't count unsolicited notifications or pauses as
- // replies.)
- if (this._activeRequests.has(packet.from) &&
- !(packet.type in UnsolicitedNotifications) &&
- !(packet.type == ThreadStateTypes.paused &&
- packet.why.type in UnsolicitedPauses)) {
- activeRequest = this._activeRequests.get(packet.from);
- this._activeRequests.delete(packet.from);
- }
-
- // If there is a subsequent request for the same actor, hand it off to the
- // transport. Delivery of packets on the other end is always async, even
- // in the local transport case.
- this._attemptNextRequest(packet.from);
-
- // Packets that indicate thread state changes get special treatment.
- if (packet.type in ThreadStateTypes &&
- this._clients.has(packet.from) &&
- typeof this._clients.get(packet.from)._onThreadState == "function") {
- this._clients.get(packet.from)._onThreadState(packet);
- }
-
- // TODO: Bug 1151156 - Remove once Gecko 40 is on b2g-stable.
- if (!this.traits.noNeedToFakeResumptionOnNavigation) {
- // On navigation the server resumes, so the client must resume as well.
- // We achieve that by generating a fake resumption packet that triggers
- // the client's thread state change listeners.
- if (packet.type == UnsolicitedNotifications.tabNavigated &&
- this._clients.has(packet.from) &&
- this._clients.get(packet.from).thread) {
- let thread = this._clients.get(packet.from).thread;
- let resumption = { from: thread._actor, type: "resumed" };
- thread._onThreadState(resumption);
- }
- }
-
- // Only try to notify listeners on events, not responses to requests
- // that lack a packet type.
- if (packet.type) {
- this.emit(packet.type, packet);
- }
-
- if (activeRequest) {
- let emitReply = () => activeRequest.emit("json-reply", packet);
- if (activeRequest.stack) {
- callFunctionWithAsyncStack(emitReply, activeRequest.stack,
- "DevTools RDP");
- } else {
- emitReply();
- }
- }
- },
-
- /**
- * Called by the DebuggerTransport to dispatch incoming bulk packets as
- * appropriate.
- *
- * @param packet object
- * The incoming packet, which contains:
- * * actor: Name of actor that will receive the packet
- * * type: Name of actor's method that should be called on receipt
- * * length: Size of the data to be read
- * * stream: This input stream should only be used directly if you can
- * ensure that you will read exactly |length| bytes and will
- * not close the stream when reading is complete
- * * done: If you use the stream directly (instead of |copyTo|
- * below), you must signal completion by resolving /
- * rejecting this deferred. If it's rejected, the transport
- * will be closed. If an Error is supplied as a rejection
- * value, it will be logged via |dumpn|. If you do use
- * |copyTo|, resolving is taken care of for you when copying
- * completes.
- * * copyTo: A helper function for getting your data out of the stream
- * that meets the stream handling requirements above, and has
- * the following signature:
- * @param output nsIAsyncOutputStream
- * The stream to copy to.
- * @return Promise
- * The promise is resolved when copying completes or rejected
- * if any (unexpected) errors occur.
- * This object also emits "progress" events for each chunk
- * that is copied. See stream-utils.js.
- */
- onBulkPacket: function (packet) {
- let { actor } = packet;
-
- if (!actor) {
- DevToolsUtils.reportException(
- "onBulkPacket",
- new Error("Server did not specify an actor, dropping bulk packet: " +
- JSON.stringify(packet)));
- return;
- }
-
- // See if we have a handler function waiting for a reply from this
- // actor.
- if (!this._activeRequests.has(actor)) {
- return;
- }
-
- let activeRequest = this._activeRequests.get(actor);
- this._activeRequests.delete(actor);
-
- // If there is a subsequent request for the same actor, hand it off to the
- // transport. Delivery of packets on the other end is always async, even
- // in the local transport case.
- this._attemptNextRequest(actor);
-
- activeRequest.emit("bulk-reply", packet);
- },
-
- /**
- * Called by DebuggerTransport when the underlying stream is closed.
- *
- * @param status nsresult
- * The status code that corresponds to the reason for closing
- * the stream.
- */
- onClosed: function () {
- this._closed = true;
- this.emit("closed");
-
- this.purgeRequests();
-
- // The |_pools| array on the client-side currently is used only by
- // protocol.js to store active fronts, mirroring the actor pools found in
- // the server. So, read all usages of "pool" as "protocol.js front".
- //
- // In the normal case where we shutdown cleanly, the toolbox tells each tool
- // to close, and they each call |destroy| on any fronts they were using.
- // When |destroy| or |cleanup| is called on a protocol.js front, it also
- // removes itself from the |_pools| array. Once the toolbox has shutdown,
- // the connection is closed, and we reach here. All fronts (should have
- // been) |destroy|ed, so |_pools| should empty.
- //
- // If the connection instead aborts unexpectedly, we may end up here with
- // all fronts used during the life of the connection. So, we call |cleanup|
- // on them clear their state, reject pending requests, and remove themselves
- // from |_pools|. This saves the toolbox from hanging indefinitely, in case
- // it waits for some server response before shutdown that will now never
- // arrive.
- for (let pool of this._pools) {
- pool.cleanup();
- }
- },
-
- /**
- * Purge pending and active requests in this client.
- *
- * @param prefix string (optional)
- * If a prefix is given, only requests for actor IDs that start with the prefix
- * will be cleaned up. This is useful when forwarding of a portion of requests
- * is cancelled on the server.
- */
- purgeRequests(prefix = "") {
- let reject = function (type, request) {
- // Server can send packets on its own and client only pass a callback
- // to expectReply, so that there is no request object.
- let msg;
- if (request.request) {
- msg = "'" + request.request.type + "' " + type + " request packet" +
- " to '" + request.actor + "' " +
- "can't be sent as the connection just closed.";
- } else {
- msg = "server side packet can't be received as the connection just closed.";
- }
- let packet = { error: "connectionClosed", message: msg };
- request.emit("json-reply", packet);
- };
-
- let pendingRequestsToReject = [];
- this._pendingRequests.forEach((requests, actor) => {
- if (!actor.startsWith(prefix)) {
- return;
- }
- this._pendingRequests.delete(actor);
- pendingRequestsToReject = pendingRequestsToReject.concat(requests);
- });
- pendingRequestsToReject.forEach(request => reject("pending", request));
-
- let activeRequestsToReject = [];
- this._activeRequests.forEach((request, actor) => {
- if (!actor.startsWith(prefix)) {
- return;
- }
- this._activeRequests.delete(actor);
- activeRequestsToReject = activeRequestsToReject.concat(request);
- });
- activeRequestsToReject.forEach(request => reject("active", request));
- },
-
- /**
- * Search for all requests in process for this client, including those made via
- * protocol.js and wait all of them to complete. Since the requests seen when this is
- * first called may in turn trigger more requests, we keep recursing through this
- * function until there is no more activity.
- *
- * This is a fairly heavy weight process, so it's only meant to be used in tests.
- *
- * @return Promise
- * Resolved when all requests have settled.
- */
- waitForRequestsToSettle() {
- let requests = [];
-
- // Gather all pending and active requests in this client
- // The request object supports a Promise API for completion (it has .then())
- this._pendingRequests.forEach(requestsForActor => {
- // Each value is an array of pending requests
- requests = requests.concat(requestsForActor);
- });
- this._activeRequests.forEach(requestForActor => {
- // Each value is a single active request
- requests = requests.concat(requestForActor);
- });
-
- // protocol.js
- // Use a Set because some fronts (like domwalker) seem to have multiple parents.
- let fronts = new Set();
- let poolsToVisit = [...this._pools];
-
- // With protocol.js, each front can potentially have it's own pools containing child
- // fronts, forming a tree. Descend through all the pools to locate all child fronts.
- while (poolsToVisit.length) {
- let pool = poolsToVisit.shift();
- fronts.add(pool);
- for (let child of pool.poolChildren()) {
- poolsToVisit.push(child);
- }
- }
-
- // For each front, wait for its requests to settle
- for (let front of fronts) {
- if (front.hasRequests()) {
- requests.push(front.waitForRequestsToSettle());
- }
- }
-
- // Abort early if there are no requests
- if (!requests.length) {
- return Promise.resolve();
- }
-
- return DevToolsUtils.settleAll(requests).catch(() => {
- // One of the requests might have failed, but ignore that situation here and pipe
- // both success and failure through the same path. The important part is just that
- // we waited.
- }).then(() => {
- // Repeat, more requests may have started in response to those we just waited for
- return this.waitForRequestsToSettle();
- });
- },
-
- registerClient: function (client) {
- let actorID = client.actor;
- if (!actorID) {
- throw new Error("DebuggerServer.registerClient expects " +
- "a client instance with an `actor` attribute.");
- }
- if (!Array.isArray(client.events)) {
- throw new Error("DebuggerServer.registerClient expects " +
- "a client instance with an `events` attribute " +
- "that is an array.");
- }
- if (client.events.length > 0 && typeof (client.emit) != "function") {
- throw new Error("DebuggerServer.registerClient expects " +
- "a client instance with non-empty `events` array to" +
- "have an `emit` function.");
- }
- if (this._clients.has(actorID)) {
- throw new Error("DebuggerServer.registerClient already registered " +
- "a client for this actor.");
- }
- this._clients.set(actorID, client);
- },
-
- unregisterClient: function (client) {
- let actorID = client.actor;
- if (!actorID) {
- throw new Error("DebuggerServer.unregisterClient expects " +
- "a Client instance with a `actor` attribute.");
- }
- this._clients.delete(actorID);
- },
-
- /**
- * Actor lifetime management, echos the server's actor pools.
- */
- __pools: null,
- get _pools() {
- if (this.__pools) {
- return this.__pools;
- }
- this.__pools = new Set();
- return this.__pools;
- },
-
- addActorPool: function (pool) {
- this._pools.add(pool);
- },
- removeActorPool: function (pool) {
- this._pools.delete(pool);
- },
- getActor: function (actorID) {
- let pool = this.poolFor(actorID);
- return pool ? pool.get(actorID) : null;
- },
-
- poolFor: function (actorID) {
- for (let pool of this._pools) {
- if (pool.has(actorID)) {
- return pool;
- }
- }
- return null;
- },
-
- /**
- * Currently attached addon.
- */
- activeAddon: null
-};
-
-eventSource(DebuggerClient.prototype);
-
-class Request extends EventEmitter {
- constructor(request) {
- super();
- this.request = request;
- }
-
- get actor() {
- return this.request.to || this.request.actor;
- }
-}
-
-/**
- * Creates a tab client for the remote debugging protocol server. This client
- * is a front to the tab actor created in the server side, hiding the protocol
- * details in a traditional JavaScript API.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param form object
- * The protocol form for this tab.
- */
-function TabClient(client, form) {
- this.client = client;
- this._actor = form.from;
- this._threadActor = form.threadActor;
- this.javascriptEnabled = form.javascriptEnabled;
- this.cacheDisabled = form.cacheDisabled;
- this.thread = null;
- this.request = this.client.request;
- this.traits = form.traits || {};
- this.events = ["workerListChanged"];
-}
-
-TabClient.prototype = {
- get actor() {
- return this._actor;
- },
- get _transport() {
- return this.client._transport;
- },
-
- /**
- * Attach to a thread actor.
- *
- * @param object options
- * Configuration options.
- * - useSourceMaps: whether to use source maps or not.
- * @param function onResponse
- * Called with the response packet and a ThreadClient
- * (which will be undefined on error).
- */
- attachThread: function (options = {}, onResponse = noop) {
- if (this.thread) {
- DevToolsUtils.executeSoon(() => onResponse({}, this.thread));
- return promise.resolve([{}, this.thread]);
- }
-
- let packet = {
- to: this._threadActor,
- type: "attach",
- options,
- };
- return this.request(packet).then(response => {
- if (!response.error) {
- this.thread = new ThreadClient(this, this._threadActor);
- this.client.registerClient(this.thread);
- }
- onResponse(response, this.thread);
- return [response, this.thread];
- });
- },
-
- /**
- * Detach the client from the tab actor.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- detach: DebuggerClient.requester({
- type: "detach"
- }, {
- before: function (packet) {
- if (this.thread) {
- this.thread.detach();
- }
- return packet;
- },
- after: function (response) {
- this.client.unregisterClient(this);
- return response;
- },
- }),
-
- /**
- * Bring the window to the front.
- */
- focus: DebuggerClient.requester({
- type: "focus"
- }, {}),
-
- /**
- * Reload the page in this tab.
- *
- * @param [optional] object options
- * An object with a `force` property indicating whether or not
- * this reload should skip the cache
- */
- reload: function (options = { force: false }) {
- return this._reload(options);
- },
- _reload: DebuggerClient.requester({
- type: "reload",
- options: arg(0)
- }),
-
- /**
- * Navigate to another URL.
- *
- * @param string url
- * The URL to navigate to.
- */
- navigateTo: DebuggerClient.requester({
- type: "navigateTo",
- url: arg(0)
- }),
-
- /**
- * Reconfigure the tab actor.
- *
- * @param object options
- * A dictionary object of the new options to use in the tab actor.
- * @param function onResponse
- * Called with the response packet.
- */
- reconfigure: DebuggerClient.requester({
- type: "reconfigure",
- options: arg(0)
- }),
-
- listWorkers: DebuggerClient.requester({
- type: "listWorkers"
- }),
-
- attachWorker: function (workerActor, onResponse) {
- return this.client.attachWorker(workerActor, onResponse);
- },
-};
-
-eventSource(TabClient.prototype);
-
-function WorkerClient(client, form) {
- this.client = client;
- this._actor = form.from;
- this._isClosed = false;
- this._url = form.url;
-
- this._onClose = this._onClose.bind(this);
-
- this.addListener("close", this._onClose);
-
- this.traits = {};
-}
-
-WorkerClient.prototype = {
- get _transport() {
- return this.client._transport;
- },
-
- get request() {
- return this.client.request;
- },
-
- get actor() {
- return this._actor;
- },
-
- get url() {
- return this._url;
- },
-
- get isClosed() {
- return this._isClosed;
- },
-
- detach: DebuggerClient.requester({ type: "detach" }, {
- after: function (response) {
- if (this.thread) {
- this.client.unregisterClient(this.thread);
- }
- this.client.unregisterClient(this);
- return response;
- },
- }),
-
- attachThread: function (options = {}, onResponse = noop) {
- if (this.thread) {
- let response = [{
- type: "connected",
- threadActor: this.thread._actor,
- consoleActor: this.consoleActor,
- }, this.thread];
- DevToolsUtils.executeSoon(() => onResponse(response));
- return response;
- }
-
- // The connect call on server doesn't attach the thread as of version 44.
- return this.request({
- to: this._actor,
- type: "connect",
- options,
- }).then(connectResponse => {
- if (connectResponse.error) {
- onResponse(connectResponse, null);
- return [connectResponse, null];
- }
-
- return this.request({
- to: connectResponse.threadActor,
- type: "attach",
- options,
- }).then(attachResponse => {
- if (attachResponse.error) {
- onResponse(attachResponse, null);
- }
-
- this.thread = new ThreadClient(this, connectResponse.threadActor);
- this.consoleActor = connectResponse.consoleActor;
- this.client.registerClient(this.thread);
-
- onResponse(connectResponse, this.thread);
- return [connectResponse, this.thread];
- });
- }, error => {
- onResponse(error, null);
- });
- },
-
- _onClose: function () {
- this.removeListener("close", this._onClose);
-
- if (this.thread) {
- this.client.unregisterClient(this.thread);
- }
- this.client.unregisterClient(this);
- this._isClosed = true;
- },
-
- reconfigure: function () {
- return Promise.resolve();
- },
-
- events: ["close"]
-};
-
-eventSource(WorkerClient.prototype);
-
-function AddonClient(client, actor) {
- this._client = client;
- this._actor = actor;
- this.request = this._client.request;
- this.events = [];
-}
-
-AddonClient.prototype = {
- get actor() {
- return this._actor;
- },
- get _transport() {
- return this._client._transport;
- },
-
- /**
- * Detach the client from the addon actor.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- detach: DebuggerClient.requester({
- type: "detach"
- }, {
- after: function (response) {
- if (this._client.activeAddon === this) {
- this._client.activeAddon = null;
- }
- this._client.unregisterClient(this);
- return response;
- },
- })
-};
-
-/**
- * A RootClient object represents a root actor on the server. Each
- * DebuggerClient keeps a RootClient instance representing the root actor
- * for the initial connection; DebuggerClient's 'listTabs' and
- * 'listChildProcesses' methods forward to that root actor.
- *
- * @param client object
- * The client connection to which this actor belongs.
- * @param greeting string
- * The greeting packet from the root actor we're to represent.
- *
- * Properties of a RootClient instance:
- *
- * @property actor string
- * The name of this child's root actor.
- * @property applicationType string
- * The application type, as given in the root actor's greeting packet.
- * @property traits object
- * The traits object, as given in the root actor's greeting packet.
- */
-function RootClient(client, greeting) {
- this._client = client;
- this.actor = greeting.from;
- this.applicationType = greeting.applicationType;
- this.traits = greeting.traits;
-}
-exports.RootClient = RootClient;
-
-RootClient.prototype = {
- constructor: RootClient,
-
- /**
- * Gets the "root" form, which lists all the global actors that affect the entire
- * browser. This can replace usages of `listTabs` that only wanted the global actors
- * and didn't actually care about tabs.
- */
- getRoot: DebuggerClient.requester({ type: "getRoot" }),
-
- /**
- * List the open tabs.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- listTabs: DebuggerClient.requester({ type: "listTabs" }),
-
- /**
- * List the installed addons.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- listAddons: DebuggerClient.requester({ type: "listAddons" }),
-
- /**
- * List the registered workers.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- listWorkers: DebuggerClient.requester({ type: "listWorkers" }),
-
- /**
- * List the registered service workers.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- listServiceWorkerRegistrations: DebuggerClient.requester({
- type: "listServiceWorkerRegistrations"
- }),
-
- /**
- * List the running processes.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- listProcesses: DebuggerClient.requester({ type: "listProcesses" }),
-
- /**
- * Fetch the TabActor for the currently selected tab, or for a specific
- * tab given as first parameter.
- *
- * @param [optional] object filter
- * A dictionary object with following optional attributes:
- * - outerWindowID: used to match tabs in parent process
- * - tabId: used to match tabs in child processes
- * - tab: a reference to xul:tab element
- * If nothing is specified, returns the actor for the currently
- * selected tab.
- */
- getTab: function (filter) {
- let packet = {
- to: this.actor,
- type: "getTab"
- };
-
- if (filter) {
- if (typeof (filter.outerWindowID) == "number") {
- packet.outerWindowID = filter.outerWindowID;
- } else if (typeof (filter.tabId) == "number") {
- packet.tabId = filter.tabId;
- } else if ("tab" in filter) {
- let browser = filter.tab.linkedBrowser;
- if (browser.frameLoader.tabParent) {
- // Tabs in child process
- packet.tabId = browser.frameLoader.tabParent.tabId;
- } else if (browser.outerWindowID) {
- // <xul:browser> tabs in parent process
- packet.outerWindowID = browser.outerWindowID;
- } else {
- // <iframe mozbrowser> tabs in parent process
- let windowUtils = browser.contentWindow
- .QueryInterface(Ci.nsIInterfaceRequestor)
- .getInterface(Ci.nsIDOMWindowUtils);
- packet.outerWindowID = windowUtils.outerWindowID;
- }
- } else {
- // Throw if a filter object have been passed but without
- // any clearly idenfified filter.
- throw new Error("Unsupported argument given to getTab request");
- }
- }
-
- return this.request(packet);
- },
-
- /**
- * Fetch the WindowActor for a specific window, like a browser window in
- * Firefox, but it can be used to reach any window in the process.
- *
- * @param number outerWindowID
- * The outerWindowID of the top level window you are looking for.
- */
- getWindow: function ({ outerWindowID }) {
- if (!outerWindowID) {
- throw new Error("Must specify outerWindowID");
- }
-
- let packet = {
- to: this.actor,
- type: "getWindow",
- outerWindowID,
- };
-
- return this.request(packet);
- },
-
- /**
- * Description of protocol's actors and methods.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- protocolDescription: DebuggerClient.requester({ type: "protocolDescription" }),
-
- /*
- * Methods constructed by DebuggerClient.requester require these forwards
- * on their 'this'.
- */
- get _transport() {
- return this._client._transport;
- },
- get request() {
- return this._client.request;
- }
-};
-
-/**
- * Creates a thread client for the remote debugging protocol server. This client
- * is a front to the thread actor created in the server side, hiding the
- * protocol details in a traditional JavaScript API.
- *
- * @param client DebuggerClient|TabClient
- * The parent of the thread (tab for tab-scoped debuggers, DebuggerClient
- * for chrome debuggers).
- * @param actor string
- * The actor ID for this thread.
- */
-function ThreadClient(client, actor) {
- this._parent = client;
- this.client = client instanceof DebuggerClient ? client : client.client;
- this._actor = actor;
- this._frameCache = [];
- this._scriptCache = {};
- this._pauseGrips = {};
- this._threadGrips = {};
- this.request = this.client.request;
-}
-
-ThreadClient.prototype = {
- _state: "paused",
- get state() {
- return this._state;
- },
- get paused() {
- return this._state === "paused";
- },
-
- _pauseOnExceptions: false,
- _ignoreCaughtExceptions: false,
- _pauseOnDOMEvents: null,
-
- _actor: null,
- get actor() {
- return this._actor;
- },
-
- get _transport() {
- return this.client._transport;
- },
-
- _assertPaused: function (command) {
- if (!this.paused) {
- throw Error(command + " command sent while not paused. Currently " + this._state);
- }
- },
-
- /**
- * Resume a paused thread. If the optional limit parameter is present, then
- * the thread will also pause when that limit is reached.
- *
- * @param [optional] object limit
- * An object with a type property set to the appropriate limit (next,
- * step, or finish) per the remote debugging protocol specification.
- * Use null to specify no limit.
- * @param function onResponse
- * Called with the response packet.
- */
- _doResume: DebuggerClient.requester({
- type: "resume",
- resumeLimit: arg(0)
- }, {
- before: function (packet) {
- this._assertPaused("resume");
-
- // Put the client in a tentative "resuming" state so we can prevent
- // further requests that should only be sent in the paused state.
- this._previousState = this._state;
- this._state = "resuming";
-
- if (this._pauseOnExceptions) {
- packet.pauseOnExceptions = this._pauseOnExceptions;
- }
- if (this._ignoreCaughtExceptions) {
- packet.ignoreCaughtExceptions = this._ignoreCaughtExceptions;
- }
- if (this._pauseOnDOMEvents) {
- packet.pauseOnDOMEvents = this._pauseOnDOMEvents;
- }
- return packet;
- },
- after: function (response) {
- if (response.error && this._state == "resuming") {
- // There was an error resuming, update the state to the new one
- // reported by the server, if given (only on wrongState), otherwise
- // reset back to the previous state.
- if (response.state) {
- this._state = ThreadStateTypes[response.state];
- } else {
- this._state = this._previousState;
- }
- }
- delete this._previousState;
- return response;
- },
- }),
-
- /**
- * Reconfigure the thread actor.
- *
- * @param object options
- * A dictionary object of the new options to use in the thread actor.
- * @param function onResponse
- * Called with the response packet.
- */
- reconfigure: DebuggerClient.requester({
- type: "reconfigure",
- options: arg(0)
- }),
-
- /**
- * Resume a paused thread.
- */
- resume: function (onResponse) {
- return this._doResume(null, onResponse);
- },
-
- /**
- * Resume then pause without stepping.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- resumeThenPause: function (onResponse) {
- return this._doResume({ type: "break" }, onResponse);
- },
-
- /**
- * Step over a function call.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- stepOver: function (onResponse) {
- return this._doResume({ type: "next" }, onResponse);
- },
-
- /**
- * Step into a function call.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- stepIn: function (onResponse) {
- return this._doResume({ type: "step" }, onResponse);
- },
-
- /**
- * Step out of a function call.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- stepOut: function (onResponse) {
- return this._doResume({ type: "finish" }, onResponse);
- },
-
- /**
- * Immediately interrupt a running thread.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- interrupt: function (onResponse) {
- return this._doInterrupt(null, onResponse);
- },
-
- /**
- * Pause execution right before the next JavaScript bytecode is executed.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- breakOnNext: function (onResponse) {
- return this._doInterrupt("onNext", onResponse);
- },
-
- /**
- * Interrupt a running thread.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- _doInterrupt: DebuggerClient.requester({
- type: "interrupt",
- when: arg(0)
- }),
-
- /**
- * Enable or disable pausing when an exception is thrown.
- *
- * @param boolean pauseOnExceptions
- * Enables pausing if true, disables otherwise.
- * @param boolean ignoreCaughtExceptions
- * Whether to ignore caught exceptions
- * @param function onResponse
- * Called with the response packet.
- */
- pauseOnExceptions: function (pauseOnExceptions,
- ignoreCaughtExceptions,
- onResponse = noop) {
- this._pauseOnExceptions = pauseOnExceptions;
- this._ignoreCaughtExceptions = ignoreCaughtExceptions;
-
- // Otherwise send the flag using a standard resume request.
- if (!this.paused) {
- return this.interrupt(response => {
- if (response.error) {
- // Can't continue if pausing failed.
- onResponse(response);
- return response;
- }
- return this.resume(onResponse);
- });
- }
-
- onResponse();
- return promise.resolve();
- },
-
- /**
- * Enable pausing when the specified DOM events are triggered. Disabling
- * pausing on an event can be realized by calling this method with the updated
- * array of events that doesn't contain it.
- *
- * @param array|string events
- * An array of strings, representing the DOM event types to pause on,
- * or "*" to pause on all DOM events. Pass an empty array to
- * completely disable pausing on DOM events.
- * @param function onResponse
- * Called with the response packet in a future turn of the event loop.
- */
- pauseOnDOMEvents: function (events, onResponse = noop) {
- this._pauseOnDOMEvents = events;
- // If the debuggee is paused, the value of the array will be communicated in
- // the next resumption. Otherwise we have to force a pause in order to send
- // the array.
- if (this.paused) {
- DevToolsUtils.executeSoon(() => onResponse({}));
- return {};
- }
- return this.interrupt(response => {
- // Can't continue if pausing failed.
- if (response.error) {
- onResponse(response);
- return response;
- }
- return this.resume(onResponse);
- });
- },
-
- /**
- * Send a clientEvaluate packet to the debuggee. Response
- * will be a resume packet.
- *
- * @param string frame
- * The actor ID of the frame where the evaluation should take place.
- * @param string expression
- * The expression that will be evaluated in the scope of the frame
- * above.
- * @param function onResponse
- * Called with the response packet.
- */
- eval: DebuggerClient.requester({
- type: "clientEvaluate",
- frame: arg(0),
- expression: arg(1)
- }, {
- before: function (packet) {
- this._assertPaused("eval");
- // Put the client in a tentative "resuming" state so we can prevent
- // further requests that should only be sent in the paused state.
- this._state = "resuming";
- return packet;
- },
- after: function (response) {
- if (response.error) {
- // There was an error resuming, back to paused state.
- this._state = "paused";
- }
- return response;
- },
- }),
-
- /**
- * Detach from the thread actor.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- detach: DebuggerClient.requester({
- type: "detach"
- }, {
- after: function (response) {
- this.client.unregisterClient(this);
- this._parent.thread = null;
- return response;
- },
- }),
-
- /**
- * Release multiple thread-lifetime object actors. If any pause-lifetime
- * actors are included in the request, a |notReleasable| error will return,
- * but all the thread-lifetime ones will have been released.
- *
- * @param array actors
- * An array with actor IDs to release.
- */
- releaseMany: DebuggerClient.requester({
- type: "releaseMany",
- actors: arg(0),
- }),
-
- /**
- * Promote multiple pause-lifetime object actors to thread-lifetime ones.
- *
- * @param array actors
- * An array with actor IDs to promote.
- */
- threadGrips: DebuggerClient.requester({
- type: "threadGrips",
- actors: arg(0)
- }),
-
- /**
- * Return the event listeners defined on the page.
- *
- * @param onResponse Function
- * Called with the thread's response.
- */
- eventListeners: DebuggerClient.requester({
- type: "eventListeners"
- }),
-
- /**
- * Request the loaded sources for the current thread.
- *
- * @param onResponse Function
- * Called with the thread's response.
- */
- getSources: DebuggerClient.requester({
- type: "sources"
- }),
-
- /**
- * Clear the thread's source script cache. A scriptscleared event
- * will be sent.
- */
- _clearScripts: function () {
- if (Object.keys(this._scriptCache).length > 0) {
- this._scriptCache = {};
- this.emit("scriptscleared");
- }
- },
-
- /**
- * Request frames from the callstack for the current thread.
- *
- * @param start integer
- * The number of the youngest stack frame to return (the youngest
- * frame is 0).
- * @param count integer
- * The maximum number of frames to return, or null to return all
- * frames.
- * @param onResponse function
- * Called with the thread's response.
- */
- getFrames: DebuggerClient.requester({
- type: "frames",
- start: arg(0),
- count: arg(1)
- }),
-
- /**
- * An array of cached frames. Clients can observe the framesadded and
- * framescleared event to keep up to date on changes to this cache,
- * and can fill it using the fillFrames method.
- */
- get cachedFrames() {
- return this._frameCache;
- },
-
- /**
- * true if there are more stack frames available on the server.
- */
- get moreFrames() {
- return this.paused && (!this._frameCache || this._frameCache.length == 0
- || !this._frameCache[this._frameCache.length - 1].oldest);
- },
-
- /**
- * Request the frame environment.
- *
- * @param frameId string
- */
- getEnvironment: function (frameId) {
- return this.request({ to: frameId, type: "getEnvironment" });
- },
-
- /**
- * Ensure that at least total stack frames have been loaded in the
- * ThreadClient's stack frame cache. A framesadded event will be
- * sent when the stack frame cache is updated.
- *
- * @param total number
- * The minimum number of stack frames to be included.
- * @param callback function
- * Optional callback function called when frames have been loaded
- * @returns true if a framesadded notification should be expected.
- */
- fillFrames: function (total, callback = noop) {
- this._assertPaused("fillFrames");
- if (this._frameCache.length >= total) {
- return false;
- }
-
- let numFrames = this._frameCache.length;
-
- this.getFrames(numFrames, total - numFrames, (response) => {
- if (response.error) {
- callback(response);
- return;
- }
-
- let threadGrips = DevToolsUtils.values(this._threadGrips);
-
- for (let i in response.frames) {
- let frame = response.frames[i];
- if (!frame.where.source) {
- // Older servers use urls instead, so we need to resolve
- // them to source actors
- for (let grip of threadGrips) {
- if (grip instanceof SourceClient && grip.url === frame.url) {
- frame.where.source = grip._form;
- }
- }
- }
-
- this._frameCache[frame.depth] = frame;
- }
-
- // If we got as many frames as we asked for, there might be more
- // frames available.
- this.emit("framesadded");
-
- callback(response);
- });
-
- return true;
- },
-
- /**
- * Clear the thread's stack frame cache. A framescleared event
- * will be sent.
- */
- _clearFrames: function () {
- if (this._frameCache.length > 0) {
- this._frameCache = [];
- this.emit("framescleared");
- }
- },
-
- /**
- * Return a ObjectClient object for the given object grip.
- *
- * @param grip object
- * A pause-lifetime object grip returned by the protocol.
- */
- pauseGrip: function (grip) {
- if (grip.actor in this._pauseGrips) {
- return this._pauseGrips[grip.actor];
- }
-
- let client = new ObjectClient(this.client, grip);
- this._pauseGrips[grip.actor] = client;
- return client;
- },
-
- /**
- * Get or create a long string client, checking the grip client cache if it
- * already exists.
- *
- * @param grip Object
- * The long string grip returned by the protocol.
- * @param gripCacheName String
- * The property name of the grip client cache to check for existing
- * clients in.
- */
- _longString: function (grip, gripCacheName) {
- if (grip.actor in this[gripCacheName]) {
- return this[gripCacheName][grip.actor];
- }
-
- let client = new LongStringClient(this.client, grip);
- this[gripCacheName][grip.actor] = client;
- return client;
- },
-
- /**
- * Return an instance of LongStringClient for the given long string grip that
- * is scoped to the current pause.
- *
- * @param grip Object
- * The long string grip returned by the protocol.
- */
- pauseLongString: function (grip) {
- return this._longString(grip, "_pauseGrips");
- },
-
- /**
- * Return an instance of LongStringClient for the given long string grip that
- * is scoped to the thread lifetime.
- *
- * @param grip Object
- * The long string grip returned by the protocol.
- */
- threadLongString: function (grip) {
- return this._longString(grip, "_threadGrips");
- },
-
- /**
- * Get or create an ArrayBuffer client, checking the grip client cache if it
- * already exists.
- *
- * @param grip Object
- * The ArrayBuffer grip returned by the protocol.
- * @param gripCacheName String
- * The property name of the grip client cache to check for existing
- * clients in.
- */
- _arrayBuffer: function (grip, gripCacheName) {
- if (grip.actor in this[gripCacheName]) {
- return this[gripCacheName][grip.actor];
- }
-
- let client = new ArrayBufferClient(this.client, grip);
- this[gripCacheName][grip.actor] = client;
- return client;
- },
-
- /**
- * Return an instance of ArrayBufferClient for the given ArrayBuffer grip that
- * is scoped to the thread lifetime.
- *
- * @param grip Object
- * The ArrayBuffer grip returned by the protocol.
- */
- threadArrayBuffer: function (grip) {
- return this._arrayBuffer(grip, "_threadGrips");
- },
-
- /**
- * Clear and invalidate all the grip clients from the given cache.
- *
- * @param gripCacheName
- * The property name of the grip cache we want to clear.
- */
- _clearObjectClients: function (gripCacheName) {
- for (let id in this[gripCacheName]) {
- this[gripCacheName][id].valid = false;
- }
- this[gripCacheName] = {};
- },
-
- /**
- * Invalidate pause-lifetime grip clients and clear the list of current grip
- * clients.
- */
- _clearPauseGrips: function () {
- this._clearObjectClients("_pauseGrips");
- },
-
- /**
- * Invalidate thread-lifetime grip clients and clear the list of current grip
- * clients.
- */
- _clearThreadGrips: function () {
- this._clearObjectClients("_threadGrips");
- },
-
- /**
- * Handle thread state change by doing necessary cleanup and notifying all
- * registered listeners.
- */
- _onThreadState: function (packet) {
- this._state = ThreadStateTypes[packet.type];
- // The debugger UI may not be initialized yet so we want to keep
- // the packet around so it knows what to pause state to display
- // when it's initialized
- this._lastPausePacket = packet.type === "resumed" ? null : packet;
- this._clearFrames();
- this._clearPauseGrips();
- packet.type === ThreadStateTypes.detached && this._clearThreadGrips();
- this.client._eventsEnabled && this.emit(packet.type, packet);
- },
-
- getLastPausePacket: function () {
- return this._lastPausePacket;
- },
-
- /**
- * Return an EnvironmentClient instance for the given environment actor form.
- */
- environment: function (form) {
- return new EnvironmentClient(this.client, form);
- },
-
- /**
- * Return an instance of SourceClient for the given source actor form.
- */
- source: function (form) {
- if (form.actor in this._threadGrips) {
- return this._threadGrips[form.actor];
- }
-
- this._threadGrips[form.actor] = new SourceClient(this, form);
- return this._threadGrips[form.actor];
- },
-
- /**
- * Request the prototype and own properties of mutlipleObjects.
- *
- * @param onResponse function
- * Called with the request's response.
- * @param actors [string]
- * List of actor ID of the queried objects.
- */
- getPrototypesAndProperties: DebuggerClient.requester({
- type: "prototypesAndProperties",
- actors: arg(0)
- }),
-
- events: ["newSource"]
-};
-
-eventSource(ThreadClient.prototype);
-
-/**
- * Creates a tracing profiler client for the remote debugging protocol
- * server. This client is a front to the trace actor created on the
- * server side, hiding the protocol details in a traditional
- * JavaScript API.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param actor string
- * The actor ID for this thread.
- */
-function TraceClient(client, actor) {
- this._client = client;
- this._actor = actor;
- this._activeTraces = new Set();
- this._waitingPackets = new Map();
- this._expectedPacket = 0;
- this.request = this._client.request;
- this.events = [];
-}
-
-TraceClient.prototype = {
- get actor() {
- return this._actor;
- },
- get tracing() {
- return this._activeTraces.size > 0;
- },
-
- get _transport() {
- return this._client._transport;
- },
-
- /**
- * Detach from the trace actor.
- */
- detach: DebuggerClient.requester({
- type: "detach"
- }, {
- after: function (response) {
- this._client.unregisterClient(this);
- return response;
- },
- }),
-
- /**
- * Start a new trace.
- *
- * @param trace [string]
- * An array of trace types to be recorded by the new trace.
- *
- * @param name string
- * The name of the new trace.
- *
- * @param onResponse function
- * Called with the request's response.
- */
- startTrace: DebuggerClient.requester({
- type: "startTrace",
- name: arg(1),
- trace: arg(0)
- }, {
- after: function (response) {
- if (response.error) {
- return response;
- }
-
- if (!this.tracing) {
- this._waitingPackets.clear();
- this._expectedPacket = 0;
- }
- this._activeTraces.add(response.name);
-
- return response;
- },
- }),
-
- /**
- * End a trace. If a name is provided, stop the named
- * trace. Otherwise, stop the most recently started trace.
- *
- * @param name string
- * The name of the trace to stop.
- *
- * @param onResponse function
- * Called with the request's response.
- */
- stopTrace: DebuggerClient.requester({
- type: "stopTrace",
- name: arg(0)
- }, {
- after: function (response) {
- if (response.error) {
- return response;
- }
-
- this._activeTraces.delete(response.name);
-
- return response;
- },
- })
-};
-
-/**
- * Grip clients are used to retrieve information about the relevant object.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param grip object
- * A pause-lifetime object grip returned by the protocol.
- */
-function ObjectClient(client, grip) {
- this._grip = grip;
- this._client = client;
- this.request = this._client.request;
-}
-exports.ObjectClient = ObjectClient;
-
-ObjectClient.prototype = {
- get actor() {
- return this._grip.actor;
- },
- get _transport() {
- return this._client._transport;
- },
-
- valid: true,
-
- get isFrozen() {
- return this._grip.frozen;
- },
- get isSealed() {
- return this._grip.sealed;
- },
- get isExtensible() {
- return this._grip.extensible;
- },
-
- getDefinitionSite: DebuggerClient.requester({
- type: "definitionSite"
- }, {
- before: function (packet) {
- if (this._grip.class != "Function") {
- throw new Error("getDefinitionSite is only valid for function grips.");
- }
- return packet;
- }
- }),
-
- /**
- * Request the names of a function's formal parameters.
- *
- * @param onResponse function
- * Called with an object of the form:
- * { parameterNames:[<parameterName>, ...] }
- * where each <parameterName> is the name of a parameter.
- */
- getParameterNames: DebuggerClient.requester({
- type: "parameterNames"
- }, {
- before: function (packet) {
- if (this._grip.class !== "Function") {
- throw new Error("getParameterNames is only valid for function grips.");
- }
- return packet;
- },
- }),
-
- /**
- * Request the names of the properties defined on the object and not its
- * prototype.
- *
- * @param onResponse function Called with the request's response.
- */
- getOwnPropertyNames: DebuggerClient.requester({
- type: "ownPropertyNames"
- }),
-
- /**
- * Request the prototype and own properties of the object.
- *
- * @param onResponse function Called with the request's response.
- */
- getPrototypeAndProperties: DebuggerClient.requester({
- type: "prototypeAndProperties"
- }),
-
- /**
- * Request a PropertyIteratorClient instance to ease listing
- * properties for this object.
- *
- * @param options Object
- * A dictionary object with various boolean attributes:
- * - ignoreIndexedProperties Boolean
- * If true, filters out Array items.
- * e.g. properties names between `0` and `object.length`.
- * - ignoreNonIndexedProperties Boolean
- * If true, filters out items that aren't array items
- * e.g. properties names that are not a number between `0`
- * and `object.length`.
- * - sort Boolean
- * If true, the iterator will sort the properties by name
- * before dispatching them.
- * @param onResponse function Called with the client instance.
- */
- enumProperties: DebuggerClient.requester({
- type: "enumProperties",
- options: arg(0)
- }, {
- after: function (response) {
- if (response.iterator) {
- return { iterator: new PropertyIteratorClient(this._client, response.iterator) };
- }
- return response;
- },
- }),
-
- /**
- * Request a PropertyIteratorClient instance to enumerate entries in a
- * Map/Set-like object.
- *
- * @param onResponse function Called with the request's response.
- */
- enumEntries: DebuggerClient.requester({
- type: "enumEntries"
- }, {
- before: function (packet) {
- if (!["Map", "WeakMap", "Set", "WeakSet"].includes(this._grip.class)) {
- throw new Error("enumEntries is only valid for Map/Set-like grips.");
- }
- return packet;
- },
- after: function (response) {
- if (response.iterator) {
- return {
- iterator: new PropertyIteratorClient(this._client, response.iterator)
- };
- }
- return response;
- }
- }),
-
- /**
- * Request a SymbolIteratorClient instance to enumerate symbols in an object.
- *
- * @param onResponse function Called with the request's response.
- */
- enumSymbols: DebuggerClient.requester({
- type: "enumSymbols"
- }, {
- before: function (packet) {
- if (this._grip.type !== "object") {
- throw new Error("enumSymbols is only valid for objects grips.");
- }
- return packet;
- },
- after: function (response) {
- if (response.iterator) {
- return {
- iterator: new SymbolIteratorClient(this._client, response.iterator)
- };
- }
- return response;
- }
- }),
-
- /**
- * Request the property descriptor of the object's specified property.
- *
- * @param name string The name of the requested property.
- * @param onResponse function Called with the request's response.
- */
- getProperty: DebuggerClient.requester({
- type: "property",
- name: arg(0)
- }),
-
- /**
- * Request the prototype of the object.
- *
- * @param onResponse function Called with the request's response.
- */
- getPrototype: DebuggerClient.requester({
- type: "prototype"
- }),
-
- /**
- * Request the display string of the object.
- *
- * @param onResponse function Called with the request's response.
- */
- getDisplayString: DebuggerClient.requester({
- type: "displayString"
- }),
-
- /**
- * Request the scope of the object.
- *
- * @param onResponse function Called with the request's response.
- */
- getScope: DebuggerClient.requester({
- type: "scope"
- }, {
- before: function (packet) {
- if (this._grip.class !== "Function") {
- throw new Error("scope is only valid for function grips.");
- }
- return packet;
- },
- }),
-
- /**
- * Request the promises directly depending on the current promise.
- */
- getDependentPromises: DebuggerClient.requester({
- type: "dependentPromises"
- }, {
- before: function (packet) {
- if (this._grip.class !== "Promise") {
- throw new Error("getDependentPromises is only valid for promise " +
- "grips.");
- }
- return packet;
- }
- }),
-
- /**
- * Request the stack to the promise's allocation point.
- */
- getPromiseAllocationStack: DebuggerClient.requester({
- type: "allocationStack"
- }, {
- before: function (packet) {
- if (this._grip.class !== "Promise") {
- throw new Error("getAllocationStack is only valid for promise grips.");
- }
- return packet;
- }
- }),
-
- /**
- * Request the stack to the promise's fulfillment point.
- */
- getPromiseFulfillmentStack: DebuggerClient.requester({
- type: "fulfillmentStack"
- }, {
- before: function (packet) {
- if (this._grip.class !== "Promise") {
- throw new Error("getPromiseFulfillmentStack is only valid for " +
- "promise grips.");
- }
- return packet;
- }
- }),
-
- /**
- * Request the stack to the promise's rejection point.
- */
- getPromiseRejectionStack: DebuggerClient.requester({
- type: "rejectionStack"
- }, {
- before: function (packet) {
- if (this._grip.class !== "Promise") {
- throw new Error("getPromiseRejectionStack is only valid for " +
- "promise grips.");
- }
- return packet;
- }
- })
-};
-
-/**
- * A PropertyIteratorClient provides a way to access to property names and
- * values of an object efficiently, slice by slice.
- * Note that the properties can be sorted in the backend,
- * this is controled while creating the PropertyIteratorClient
- * from ObjectClient.enumProperties.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param grip Object
- * A PropertyIteratorActor grip returned by the protocol via
- * TabActor.enumProperties request.
- */
-function PropertyIteratorClient(client, grip) {
- this._grip = grip;
- this._client = client;
- this.request = this._client.request;
-}
-
-PropertyIteratorClient.prototype = {
- get actor() {
- return this._grip.actor;
- },
-
- /**
- * Get the total number of properties available in the iterator.
- */
- get count() {
- return this._grip.count;
- },
-
- /**
- * Get one or more property names that correspond to the positions in the
- * indexes parameter.
- *
- * @param indexes Array
- * An array of property indexes.
- * @param callback Function
- * The function called when we receive the property names.
- */
- names: DebuggerClient.requester({
- type: "names",
- indexes: arg(0)
- }, {}),
-
- /**
- * Get a set of following property value(s).
- *
- * @param start Number
- * The index of the first property to fetch.
- * @param count Number
- * The number of properties to fetch.
- * @param callback Function
- * The function called when we receive the property values.
- */
- slice: DebuggerClient.requester({
- type: "slice",
- start: arg(0),
- count: arg(1)
- }, {}),
-
- /**
- * Get all the property values.
- *
- * @param callback Function
- * The function called when we receive the property values.
- */
- all: DebuggerClient.requester({
- type: "all"
- }, {}),
-};
-
-/**
- * A SymbolIteratorClient provides a way to access to symbols
- * of an object efficiently, slice by slice.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param grip Object
- * A SymbolIteratorActor grip returned by the protocol via
- * TabActor.enumSymbols request.
- */
-function SymbolIteratorClient(client, grip) {
- this._grip = grip;
- this._client = client;
- this.request = this._client.request;
-}
-
-SymbolIteratorClient.prototype = {
- get actor() {
- return this._grip.actor;
- },
-
- /**
- * Get the total number of symbols available in the iterator.
- */
- get count() {
- return this._grip.count;
- },
-
- /**
- * Get a set of following symbols.
- *
- * @param start Number
- * The index of the first symbol to fetch.
- * @param count Number
- * The number of symbols to fetch.
- * @param callback Function
- * The function called when we receive the symbols.
- */
- slice: DebuggerClient.requester({
- type: "slice",
- start: arg(0),
- count: arg(1)
- }, {}),
-
- /**
- * Get all the symbols.
- *
- * @param callback Function
- * The function called when we receive the symbols.
- */
- all: DebuggerClient.requester({
- type: "all"
- }, {}),
-};
-
-/**
- * A ArrayBufferClient provides a way to access ArrayBuffer from the
- * debugger server.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param grip Object
- * A pause-lifetime ArrayBuffer grip returned by the protocol.
- */
-function ArrayBufferClient(client, grip) {
- this._grip = grip;
- this._client = client;
- this.request = this._client.request;
-}
-ArrayBufferClient.prototype = {
- get actor() {
- return this._grip.actor;
- },
- get length() {
- return this._grip.length;
- },
- get _transport() {
- return this._client._transport;
- },
-
- valid: true,
-
- slice: DebuggerClient.requester({
- type: "slice",
- start: arg(0),
- count: arg(1)
- }),
-};
-
+const {arg, DebuggerClient} = require("./debugger-client");
/**
* A LongStringClient provides a way to access "very long" strings from the
* debugger server.
*
* @param client DebuggerClient
* The debugger client parent.
* @param grip Object
* A pause-lifetime long string grip returned by the protocol.
*/
function LongStringClient(client, grip) {
this._grip = grip;
this._client = client;
this.request = this._client.request;
}
-exports.LongStringClient = LongStringClient;
LongStringClient.prototype = {
get actor() {
return this._grip.actor;
},
get length() {
return this._grip.length;
},
@@ -2985,442 +48,9 @@ LongStringClient.prototype = {
*/
substring: DebuggerClient.requester({
type: "substring",
start: arg(0),
end: arg(1)
}),
};
-/**
- * A SourceClient provides a way to access the source text of a script.
- *
- * @param client ThreadClient
- * The thread client parent.
- * @param form Object
- * The form sent across the remote debugging protocol.
- */
-function SourceClient(client, form) {
- this._form = form;
- this._isBlackBoxed = form.isBlackBoxed;
- this._isPrettyPrinted = form.isPrettyPrinted;
- this._activeThread = client;
- this._client = client.client;
-}
-
-SourceClient.prototype = {
- get _transport() {
- return this._client._transport;
- },
- get isBlackBoxed() {
- return this._isBlackBoxed;
- },
- get isPrettyPrinted() {
- return this._isPrettyPrinted;
- },
- get actor() {
- return this._form.actor;
- },
- get request() {
- return this._client.request;
- },
- get url() {
- return this._form.url;
- },
-
- /**
- * Black box this SourceClient's source.
- *
- * @param callback Function
- * The callback function called when we receive the response from the server.
- */
- blackBox: DebuggerClient.requester({
- type: "blackbox"
- }, {
- after: function (response) {
- if (!response.error) {
- this._isBlackBoxed = true;
- if (this._activeThread) {
- this._activeThread.emit("blackboxchange", this);
- }
- }
- return response;
- }
- }),
-
- /**
- * Un-black box this SourceClient's source.
- *
- * @param callback Function
- * The callback function called when we receive the response from the server.
- */
- unblackBox: DebuggerClient.requester({
- type: "unblackbox"
- }, {
- after: function (response) {
- if (!response.error) {
- this._isBlackBoxed = false;
- if (this._activeThread) {
- this._activeThread.emit("blackboxchange", this);
- }
- }
- return response;
- }
- }),
-
- /**
- * Get Executable Lines from a source
- *
- * @param callback Function
- * The callback function called when we receive the response from the server.
- */
- getExecutableLines: function (cb = noop) {
- let packet = {
- to: this._form.actor,
- type: "getExecutableLines"
- };
-
- return this._client.request(packet).then(res => {
- cb(res.lines);
- return res.lines;
- });
- },
-
- /**
- * Get a long string grip for this SourceClient's source.
- */
- source: function (callback = noop) {
- let packet = {
- to: this._form.actor,
- type: "source"
- };
- return this._client.request(packet).then(response => {
- return this._onSourceResponse(response, callback);
- });
- },
-
- /**
- * Pretty print this source's text.
- */
- prettyPrint: function (indent, callback = noop) {
- const packet = {
- to: this._form.actor,
- type: "prettyPrint",
- indent
- };
- return this._client.request(packet).then(response => {
- if (!response.error) {
- this._isPrettyPrinted = true;
- this._activeThread._clearFrames();
- this._activeThread.emit("prettyprintchange", this);
- }
- return this._onSourceResponse(response, callback);
- });
- },
-
- /**
- * Stop pretty printing this source's text.
- */
- disablePrettyPrint: function (callback = noop) {
- const packet = {
- to: this._form.actor,
- type: "disablePrettyPrint"
- };
- return this._client.request(packet).then(response => {
- if (!response.error) {
- this._isPrettyPrinted = false;
- this._activeThread._clearFrames();
- this._activeThread.emit("prettyprintchange", this);
- }
- return this._onSourceResponse(response, callback);
- });
- },
-
- _onSourceResponse: function (response, callback) {
- if (response.error) {
- callback(response);
- return response;
- }
-
- if (typeof response.source === "string") {
- callback(response);
- return response;
- }
-
- let { contentType, source } = response;
- if (source.type === "arrayBuffer") {
- let arrayBuffer = this._activeThread.threadArrayBuffer(source);
- return arrayBuffer.slice(0, arrayBuffer.length).then(function (resp) {
- if (resp.error) {
- callback(resp);
- return resp;
- }
- // Keeping str as a string, ArrayBuffer/Uint8Array will not survive
- // setIn/mergeIn operations.
- const str = atob(resp.encoded);
- let newResponse = {
- source: {
- binary: str,
- toString: () => "[wasm]",
- },
- contentType,
- };
- callback(newResponse);
- return newResponse;
- });
- }
-
- let longString = this._activeThread.threadLongString(source);
- return longString.substring(0, longString.length).then(function (resp) {
- if (resp.error) {
- callback(resp);
- return resp;
- }
-
- let newResponse = {
- source: resp.substring,
- contentType: contentType
- };
- callback(newResponse);
- return newResponse;
- });
- },
-
- /**
- * Request to set a breakpoint in the specified location.
- *
- * @param object location
- * The location and condition of the breakpoint in
- * the form of { line[, column, condition] }.
- * @param function onResponse
- * Called with the thread's response.
- */
- setBreakpoint: function ({ line, column, condition, noSliding }, onResponse = noop) {
- // A helper function that sets the breakpoint.
- let doSetBreakpoint = callback => {
- let root = this._client.mainRoot;
- let location = {
- line,
- column,
- };
-
- let packet = {
- to: this.actor,
- type: "setBreakpoint",
- location,
- condition,
- noSliding,
- };
-
- // Backwards compatibility: send the breakpoint request to the
- // thread if the server doesn't support Debugger.Source actors.
- if (!root.traits.debuggerSourceActors) {
- packet.to = this._activeThread.actor;
- packet.location.url = this.url;
- }
-
- return this._client.request(packet).then(response => {
- // Ignoring errors, since the user may be setting a breakpoint in a
- // dead script that will reappear on a page reload.
- let bpClient;
- if (response.actor) {
- bpClient = new BreakpointClient(
- this._client,
- this,
- response.actor,
- location,
- root.traits.conditionalBreakpoints ? condition : undefined
- );
- }
- onResponse(response, bpClient);
- if (callback) {
- callback();
- }
- return [response, bpClient];
- });
- };
-
- // If the debuggee is paused, just set the breakpoint.
- if (this._activeThread.paused) {
- return doSetBreakpoint();
- }
- // Otherwise, force a pause in order to set the breakpoint.
- return this._activeThread.interrupt().then(response => {
- if (response.error) {
- // Can't set the breakpoint if pausing failed.
- onResponse(response);
- return response;
- }
-
- const { type, why } = response;
- const cleanUp = type == "paused" && why.type == "interrupted"
- ? () => this._activeThread.resume()
- : noop;
-
- return doSetBreakpoint(cleanUp);
- });
- }
-};
-
-/**
- * Breakpoint clients are used to remove breakpoints that are no longer used.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param sourceClient SourceClient
- * The source where this breakpoint exists
- * @param actor string
- * The actor ID for this breakpoint.
- * @param location object
- * The location of the breakpoint. This is an object with two properties:
- * url and line.
- * @param condition string
- * The conditional expression of the breakpoint
- */
-function BreakpointClient(client, sourceClient, actor, location, condition) {
- this._client = client;
- this._actor = actor;
- this.location = location;
- this.location.actor = sourceClient.actor;
- this.location.url = sourceClient.url;
- this.source = sourceClient;
- this.request = this._client.request;
-
- // The condition property should only exist if it's a truthy value
- if (condition) {
- this.condition = condition;
- }
-}
-
-BreakpointClient.prototype = {
-
- _actor: null,
- get actor() {
- return this._actor;
- },
- get _transport() {
- return this._client._transport;
- },
-
- /**
- * Remove the breakpoint from the server.
- */
- remove: DebuggerClient.requester({
- type: "delete"
- }),
-
- /**
- * Determines if this breakpoint has a condition
- */
- hasCondition: function () {
- let root = this._client.mainRoot;
- // XXX bug 990137: We will remove support for client-side handling of
- // conditional breakpoints
- if (root.traits.conditionalBreakpoints) {
- return "condition" in this;
- }
- return "conditionalExpression" in this;
- },
-
- /**
- * Get the condition of this breakpoint. Currently we have to
- * support locally emulated conditional breakpoints until the
- * debugger servers are updated (see bug 990137). We used a
- * different property when moving it server-side to ensure that we
- * are testing the right code.
- */
- getCondition: function () {
- let root = this._client.mainRoot;
- if (root.traits.conditionalBreakpoints) {
- return this.condition;
- }
- return this.conditionalExpression;
- },
-
- /**
- * Set the condition of this breakpoint
- */
- setCondition: function (gThreadClient, condition) {
- let root = this._client.mainRoot;
- let deferred = promise.defer();
-
- if (root.traits.conditionalBreakpoints) {
- let info = {
- line: this.location.line,
- column: this.location.column,
- condition: condition
- };
-
- // Remove the current breakpoint and add a new one with the
- // condition.
- this.remove(response => {
- if (response && response.error) {
- deferred.reject(response);
- return;
- }
-
- this.source.setBreakpoint(info, (resp, newBreakpoint) => {
- if (resp && resp.error) {
- deferred.reject(resp);
- } else {
- deferred.resolve(newBreakpoint);
- }
- });
- });
- } else {
- // The property shouldn't even exist if the condition is blank
- if (condition === "") {
- delete this.conditionalExpression;
- } else {
- this.conditionalExpression = condition;
- }
- deferred.resolve(this);
- }
-
- return deferred.promise;
- }
-};
-
-eventSource(BreakpointClient.prototype);
-
-/**
- * Environment clients are used to manipulate the lexical environment actors.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param form Object
- * The form sent across the remote debugging protocol.
- */
-function EnvironmentClient(client, form) {
- this._client = client;
- this._form = form;
- this.request = this._client.request;
-}
-exports.EnvironmentClient = EnvironmentClient;
-
-EnvironmentClient.prototype = {
-
- get actor() {
- return this._form.actor;
- },
- get _transport() {
- return this._client._transport;
- },
-
- /**
- * Fetches the bindings introduced by this lexical environment.
- */
- getBindings: DebuggerClient.requester({
- type: "bindings"
- }),
-
- /**
- * Changes the value of the identifier whose name is name (a string) to that
- * represented by value (a grip).
- */
- assign: DebuggerClient.requester({
- type: "assign",
- name: arg(0),
- value: arg(1)
- })
-};
-
-eventSource(EnvironmentClient.prototype);
+module.exports = LongStringClient;
--- a/devtools/shared/client/main.js
+++ b/devtools/shared/client/main.js
@@ -1,3426 +1,21 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
-const { Ci, Cu } = require("chrome");
-const DevToolsUtils = require("devtools/shared/DevToolsUtils");
-const { getStack, callFunctionWithAsyncStack } = require("devtools/shared/platform/stack");
-
-const promise = Cu.import("resource://devtools/shared/deprecated-sync-thenables.js", {}).Promise;
-
-loader.lazyRequireGetter(this, "EventEmitter", "devtools/shared/event-emitter");
-loader.lazyRequireGetter(this, "WebConsoleClient", "devtools/shared/webconsole/client", true);
-loader.lazyRequireGetter(this, "DebuggerSocket", "devtools/shared/security/socket", true);
-loader.lazyRequireGetter(this, "Authentication", "devtools/shared/security/auth");
-
-const noop = () => {};
-
-/**
- * TODO: Get rid of this API in favor of EventTarget (bug 1042642)
- *
- * Add simple event notification to a prototype object. Any object that has
- * some use for event notifications or the observer pattern in general can be
- * augmented with the necessary facilities by passing its prototype to this
- * function.
- *
- * @param proto object
- * The prototype object that will be modified.
- */
-function eventSource(proto) {
- /**
- * Add a listener to the event source for a given event.
- *
- * @param name string
- * The event to listen for.
- * @param listener function
- * Called when the event is fired. If the same listener
- * is added more than once, it will be called once per
- * addListener call.
- */
- proto.addListener = function (name, listener) {
- if (typeof listener != "function") {
- throw TypeError("Listeners must be functions.");
- }
-
- if (!this._listeners) {
- this._listeners = {};
- }
-
- this._getListeners(name).push(listener);
- };
-
- /**
- * Add a listener to the event source for a given event. The
- * listener will be removed after it is called for the first time.
- *
- * @param name string
- * The event to listen for.
- * @param listener function
- * Called when the event is fired.
- */
- proto.addOneTimeListener = function (name, listener) {
- let l = (...args) => {
- this.removeListener(name, l);
- listener.apply(null, args);
- };
- this.addListener(name, l);
- };
-
- /**
- * Remove a listener from the event source previously added with
- * addListener().
- *
- * @param name string
- * The event name used during addListener to add the listener.
- * @param listener function
- * The callback to remove. If addListener was called multiple
- * times, all instances will be removed.
- */
- proto.removeListener = function (name, listener) {
- if (!this._listeners || (listener && !this._listeners[name])) {
- return;
- }
-
- if (!listener) {
- this._listeners[name] = [];
- } else {
- this._listeners[name] =
- this._listeners[name].filter(l => l != listener);
- }
- };
-
- /**
- * Returns the listeners for the specified event name. If none are defined it
- * initializes an empty list and returns that.
- *
- * @param name string
- * The event name.
- */
- proto._getListeners = function (name) {
- if (name in this._listeners) {
- return this._listeners[name];
- }
- this._listeners[name] = [];
- return this._listeners[name];
- };
-
- /**
- * Notify listeners of an event.
- *
- * @param name string
- * The event to fire.
- * @param arguments
- * All arguments will be passed along to the listeners,
- * including the name argument.
- */
- proto.emit = function () {
- if (!this._listeners) {
- return;
- }
-
- let name = arguments[0];
- let listeners = this._getListeners(name).slice(0);
-
- for (let listener of listeners) {
- try {
- listener.apply(null, arguments);
- } catch (e) {
- // Prevent a bad listener from interfering with the others.
- DevToolsUtils.reportException("notify event '" + name + "'", e);
- }
- }
- };
-}
-
-/**
- * Set of protocol messages that affect thread state, and the
- * state the actor is in after each message.
- */
-const ThreadStateTypes = {
- "paused": "paused",
- "resumed": "attached",
- "detached": "detached",
- "running": "attached"
-};
-
-/**
- * Set of protocol messages that are sent by the server without a prior request
- * by the client.
- */
-const UnsolicitedNotifications = {
- "consoleAPICall": "consoleAPICall",
- "eventNotification": "eventNotification",
- "fileActivity": "fileActivity",
- "lastPrivateContextExited": "lastPrivateContextExited",
- "logMessage": "logMessage",
- "networkEvent": "networkEvent",
- "networkEventUpdate": "networkEventUpdate",
- "newGlobal": "newGlobal",
- "newScript": "newScript",
- "tabDetached": "tabDetached",
- "tabListChanged": "tabListChanged",
- "reflowActivity": "reflowActivity",
- "addonListChanged": "addonListChanged",
- "workerListChanged": "workerListChanged",
- "serviceWorkerRegistrationListChanged": "serviceWorkerRegistrationList",
- "tabNavigated": "tabNavigated",
- "frameUpdate": "frameUpdate",
- "pageError": "pageError",
- "documentLoad": "documentLoad",
- "enteredFrame": "enteredFrame",
- "exitedFrame": "exitedFrame",
- "appOpen": "appOpen",
- "appClose": "appClose",
- "appInstall": "appInstall",
- "appUninstall": "appUninstall",
- "evaluationResult": "evaluationResult",
- "newSource": "newSource",
- "updatedSource": "updatedSource",
- "inspectObject": "inspectObject"
-};
-
-/**
- * Set of pause types that are sent by the server and not as an immediate
- * response to a client request.
- */
-const UnsolicitedPauses = {
- "resumeLimit": "resumeLimit",
- "debuggerStatement": "debuggerStatement",
- "breakpoint": "breakpoint",
- "DOMEvent": "DOMEvent",
- "watchpoint": "watchpoint",
- "exception": "exception"
-};
-
-/**
- * Creates a client for the remote debugging protocol server. This client
- * provides the means to communicate with the server and exchange the messages
- * required by the protocol in a traditional JavaScript API.
- */
-const DebuggerClient = exports.DebuggerClient = function (transport) {
- this._transport = transport;
- this._transport.hooks = this;
-
- // Map actor ID to client instance for each actor type.
- this._clients = new Map();
-
- this._pendingRequests = new Map();
- this._activeRequests = new Map();
- this._eventsEnabled = true;
-
- this.traits = {};
-
- this.request = this.request.bind(this);
- this.localTransport = this._transport.onOutputStreamReady === undefined;
-
- /*
- * As the first thing on the connection, expect a greeting packet from
- * the connection's root actor.
- */
- this.mainRoot = null;
- this.expectReply("root", (packet) => {
- this.mainRoot = new RootClient(this, packet);
- this.emit("connected", packet.applicationType, packet.traits);
- });
-};
-
-/**
- * A declarative helper for defining methods that send requests to the server.
- *
- * @param packetSkeleton
- * The form of the packet to send. Can specify fields to be filled from
- * the parameters by using the |arg| function.
- * @param before
- * The function to call before sending the packet. Is passed the packet,
- * and the return value is used as the new packet. The |this| context is
- * the instance of the client object we are defining a method for.
- * @param after
- * The function to call after the response is received. It is passed the
- * response, and the return value is considered the new response that
- * will be passed to the callback. The |this| context is the instance of
- * the client object we are defining a method for.
- * @return Request
- * The `Request` object that is a Promise object and resolves once
- * we receive the response. (See request method for more details)
- */
-DebuggerClient.requester = function (packetSkeleton, config = {}) {
- let { before, after } = config;
- return DevToolsUtils.makeInfallible(function (...args) {
- let outgoingPacket = {
- to: packetSkeleton.to || this.actor
- };
-
- let maxPosition = -1;
- for (let k of Object.keys(packetSkeleton)) {
- if (packetSkeleton[k] instanceof DebuggerClient.Argument) {
- let { position } = packetSkeleton[k];
- outgoingPacket[k] = packetSkeleton[k].getArgument(args);
- maxPosition = Math.max(position, maxPosition);
- } else {
- outgoingPacket[k] = packetSkeleton[k];
- }
- }
-
- if (before) {
- outgoingPacket = before.call(this, outgoingPacket);
- }
-
- return this.request(outgoingPacket, DevToolsUtils.makeInfallible((response) => {
- if (after) {
- let { from } = response;
- response = after.call(this, response);
- if (!response.from) {
- response.from = from;
- }
- }
-
- // The callback is always the last parameter.
- let thisCallback = args[maxPosition + 1];
- if (thisCallback) {
- thisCallback(response);
- }
- return response;
- }, "DebuggerClient.requester request callback"));
- }, "DebuggerClient.requester");
-};
-
-function arg(pos) {
- return new DebuggerClient.Argument(pos);
-}
-
-DebuggerClient.Argument = function (position) {
- this.position = position;
-};
-
-DebuggerClient.Argument.prototype.getArgument = function (params) {
- if (!(this.position in params)) {
- throw new Error("Bad index into params: " + this.position);
- }
- return params[this.position];
-};
-
-// Expose these to save callers the trouble of importing DebuggerSocket
-DebuggerClient.socketConnect = function (options) {
- // Defined here instead of just copying the function to allow lazy-load
- return DebuggerSocket.connect(options);
-};
-DevToolsUtils.defineLazyGetter(DebuggerClient, "Authenticators", () => {
- return Authentication.Authenticators;
-});
-DevToolsUtils.defineLazyGetter(DebuggerClient, "AuthenticationResult", () => {
- return Authentication.AuthenticationResult;
-});
-
-DebuggerClient.prototype = {
- /**
- * Connect to the server and start exchanging protocol messages.
- *
- * @param onConnected function
- * If specified, will be called when the greeting packet is
- * received from the debugging server.
- *
- * @return Promise
- * Resolves once connected with an array whose first element
- * is the application type, by default "browser", and the second
- * element is the traits object (help figure out the features
- * and behaviors of the server we connect to. See RootActor).
- */
- connect: function (onConnected) {
- let deferred = promise.defer();
- this.emit("connect");
-
- // Also emit the event on the |DebuggerClient| object (not on the instance),
- // so it's possible to track all instances.
- EventEmitter.emit(DebuggerClient, "connect", this);
-
- this.addOneTimeListener("connected", (name, applicationType, traits) => {
- this.traits = traits;
- if (onConnected) {
- onConnected(applicationType, traits);
- }
- deferred.resolve([applicationType, traits]);
- });
-
- this._transport.ready();
- return deferred.promise;
- },
-
- /**
- * Shut down communication with the debugging server.
- *
- * @param onClosed function
- * If specified, will be called when the debugging connection
- * has been closed. This parameter is deprecated - please use
- * the returned Promise.
- * @return Promise
- * Resolves after the underlying transport is closed.
- */
- close: function (onClosed) {
- let deferred = promise.defer();
- if (onClosed) {
- deferred.promise.then(onClosed);
- }
-
- // Disable detach event notifications, because event handlers will be in a
- // cleared scope by the time they run.
- this._eventsEnabled = false;
-
- let cleanup = () => {
- this._transport.close();
- this._transport = null;
- };
-
- // If the connection is already closed,
- // there is no need to detach client
- // as we won't be able to send any message.
- if (this._closed) {
- cleanup();
- deferred.resolve();
- return deferred.promise;
- }
-
- this.addOneTimeListener("closed", deferred.resolve);
-
- // Call each client's `detach` method by calling
- // lastly registered ones first to give a chance
- // to detach child clients first.
- let clients = [...this._clients.values()];
- this._clients.clear();
- const detachClients = () => {
- let client = clients.pop();
- if (!client) {
- // All clients detached.
- cleanup();
- return;
- }
- if (client.detach) {
- client.detach(detachClients);
- return;
- }
- detachClients();
- };
- detachClients();
-
- return deferred.promise;
- },
-
- /*
- * This function exists only to preserve DebuggerClient's interface;
- * new code should say 'client.mainRoot.listTabs()'.
- */
- listTabs: function (onResponse) {
- return this.mainRoot.listTabs(onResponse);
- },
-
- /*
- * This function exists only to preserve DebuggerClient's interface;
- * new code should say 'client.mainRoot.listAddons()'.
- */
- listAddons: function (onResponse) {
- return this.mainRoot.listAddons(onResponse);
- },
-
- getTab: function (filter) {
- return this.mainRoot.getTab(filter);
- },
-
- /**
- * Attach to a tab actor.
- *
- * @param string tabActor
- * The actor ID for the tab to attach.
- * @param function onResponse
- * Called with the response packet and a TabClient
- * (which will be undefined on error).
- */
- attachTab: function (tabActor, onResponse = noop) {
- if (this._clients.has(tabActor)) {
- let cachedTab = this._clients.get(tabActor);
- let cachedResponse = {
- cacheDisabled: cachedTab.cacheDisabled,
- javascriptEnabled: cachedTab.javascriptEnabled,
- traits: cachedTab.traits,
- };
- DevToolsUtils.executeSoon(() => onResponse(cachedResponse, cachedTab));
- return promise.resolve([cachedResponse, cachedTab]);
- }
-
- let packet = {
- to: tabActor,
- type: "attach"
- };
- return this.request(packet).then(response => {
- let tabClient;
- if (!response.error) {
- tabClient = new TabClient(this, response);
- this.registerClient(tabClient);
- }
- onResponse(response, tabClient);
- return [response, tabClient];
- });
- },
-
- attachWorker: function (workerActor, onResponse = noop) {
- let workerClient = this._clients.get(workerActor);
- if (workerClient !== undefined) {
- let response = {
- from: workerClient.actor,
- type: "attached",
- url: workerClient.url
- };
- DevToolsUtils.executeSoon(() => onResponse(response, workerClient));
- return promise.resolve([response, workerClient]);
- }
-
- return this.request({ to: workerActor, type: "attach" }).then(response => {
- if (response.error) {
- onResponse(response, null);
- return [response, null];
- }
-
- workerClient = new WorkerClient(this, response);
- this.registerClient(workerClient);
- onResponse(response, workerClient);
- return [response, workerClient];
- });
- },
-
- /**
- * Attach to an addon actor.
- *
- * @param string addonActor
- * The actor ID for the addon to attach.
- * @param function onResponse
- * Called with the response packet and a AddonClient
- * (which will be undefined on error).
- */
- attachAddon: function (addonActor, onResponse = noop) {
- let packet = {
- to: addonActor,
- type: "attach"
- };
- return this.request(packet).then(response => {
- let addonClient;
- if (!response.error) {
- addonClient = new AddonClient(this, addonActor);
- this.registerClient(addonClient);
- this.activeAddon = addonClient;
- }
- onResponse(response, addonClient);
- return [response, addonClient];
- });
- },
-
- /**
- * Attach to a Web Console actor.
- *
- * @param string consoleActor
- * The ID for the console actor to attach to.
- * @param array listeners
- * The console listeners you want to start.
- * @param function onResponse
- * Called with the response packet and a WebConsoleClient
- * instance (which will be undefined on error).
- */
- attachConsole:
- function (consoleActor, listeners, onResponse = noop) {
- let packet = {
- to: consoleActor,
- type: "startListeners",
- listeners: listeners,
- };
-
- return this.request(packet).then(response => {
- let consoleClient;
- if (!response.error) {
- if (this._clients.has(consoleActor)) {
- consoleClient = this._clients.get(consoleActor);
- } else {
- consoleClient = new WebConsoleClient(this, response);
- this.registerClient(consoleClient);
- }
- }
- onResponse(response, consoleClient);
- return [response, consoleClient];
- });
- },
-
- /**
- * Attach to a global-scoped thread actor for chrome debugging.
- *
- * @param string threadActor
- * The actor ID for the thread to attach.
- * @param function onResponse
- * Called with the response packet and a ThreadClient
- * (which will be undefined on error).
- * @param object options
- * Configuration options.
- * - useSourceMaps: whether to use source maps or not.
- */
- attachThread: function (threadActor, onResponse = noop, options = {}) {
- if (this._clients.has(threadActor)) {
- let client = this._clients.get(threadActor);
- DevToolsUtils.executeSoon(() => onResponse({}, client));
- return promise.resolve([{}, client]);
- }
-
- let packet = {
- to: threadActor,
- type: "attach",
- options,
- };
- return this.request(packet).then(response => {
- let threadClient;
- if (!response.error) {
- threadClient = new ThreadClient(this, threadActor);
- this.registerClient(threadClient);
- }
- onResponse(response, threadClient);
- return [response, threadClient];
- });
- },
-
- /**
- * Attach to a trace actor.
- *
- * @param string traceActor
- * The actor ID for the tracer to attach.
- * @param function onResponse
- * Called with the response packet and a TraceClient
- * (which will be undefined on error).
- */
- attachTracer: function (traceActor, onResponse = noop) {
- if (this._clients.has(traceActor)) {
- let client = this._clients.get(traceActor);
- DevToolsUtils.executeSoon(() => onResponse({}, client));
- return promise.resolve([{}, client]);
- }
-
- let packet = {
- to: traceActor,
- type: "attach"
- };
- return this.request(packet).then(response => {
- let traceClient;
- if (!response.error) {
- traceClient = new TraceClient(this, traceActor);
- this.registerClient(traceClient);
- }
- onResponse(response, traceClient);
- return [response, traceClient];
- });
- },
-
- /**
- * Fetch the ChromeActor for the main process or ChildProcessActor for a
- * a given child process ID.
- *
- * @param number id
- * The ID for the process to attach (returned by `listProcesses`).
- * Connected to the main process if omitted, or is 0.
- */
- getProcess: function (id) {
- let packet = {
- to: "root",
- type: "getProcess"
- };
- if (typeof (id) == "number") {
- packet.id = id;
- }
- return this.request(packet);
- },
-
- /**
- * Release an object actor.
- *
- * @param string actor
- * The actor ID to send the request to.
- * @param onResponse function
- * If specified, will be called with the response packet when
- * debugging server responds.
- */
- release: DebuggerClient.requester({
- to: arg(0),
- type: "release"
- }),
-
- /**
- * Send a request to the debugging server.
- *
- * @param packet object
- * A JSON packet to send to the debugging server.
- * @param onResponse function
- * If specified, will be called with the JSON response packet when
- * debugging server responds.
- * @return Request
- * This object emits a number of events to allow you to respond to
- * different parts of the request lifecycle.
- * It is also a Promise object, with a `then` method, that is resolved
- * whenever a JSON or a Bulk response is received; and is rejected
- * if the response is an error.
- * Note: This return value can be ignored if you are using JSON alone,
- * because the callback provided in |onResponse| will be bound to the
- * "json-reply" event automatically.
- *
- * Events emitted:
- * * json-reply: The server replied with a JSON packet, which is
- * passed as event data.
- * * bulk-reply: The server replied with bulk data, which you can read
- * using the event data object containing:
- * * actor: Name of actor that received the packet
- * * type: Name of actor's method that was called on receipt
- * * length: Size of the data to be read
- * * stream: This input stream should only be used directly if you
- * can ensure that you will read exactly |length| bytes
- * and will not close the stream when reading is complete
- * * done: If you use the stream directly (instead of |copyTo|
- * below), you must signal completion by resolving /
- * rejecting this deferred. If it's rejected, the
- * transport will be closed. If an Error is supplied as a
- * rejection value, it will be logged via |dumpn|. If you
- * do use |copyTo|, resolving is taken care of for you
- * when copying completes.
- * * copyTo: A helper function for getting your data out of the
- * stream that meets the stream handling requirements
- * above, and has the following signature:
- * @param output nsIAsyncOutputStream
- * The stream to copy to.
- * @return Promise
- * The promise is resolved when copying completes or
- * rejected if any (unexpected) errors occur.
- * This object also emits "progress" events for each chunk
- * that is copied. See stream-utils.js.
- */
- request: function (packet, onResponse) {
- if (!this.mainRoot) {
- throw Error("Have not yet received a hello packet from the server.");
- }
- let type = packet.type || "";
- if (!packet.to) {
- throw Error("'" + type + "' request packet has no destination.");
- }
-
- // The onResponse callback might modify the response, so we need to call
- // it and resolve the promise with its result if it's truthy.
- const safeOnResponse = response => {
- if (!onResponse) {
- return response;
- }
- return onResponse(response) || response;
- };
-
- if (this._closed) {
- let msg = "'" + type + "' request packet to " +
- "'" + packet.to + "' " +
- "can't be sent as the connection is closed.";
- let resp = { error: "connectionClosed", message: msg };
- return promise.reject(safeOnResponse(resp));
- }
-
- let request = new Request(packet);
- request.format = "json";
- request.stack = getStack();
-
- // Implement a Promise like API on the returned object
- // that resolves/rejects on request response
- let deferred = promise.defer();
- function listenerJson(resp) {
- removeRequestListeners();
- if (resp.error) {
- deferred.reject(safeOnResponse(resp));
- } else {
- deferred.resolve(safeOnResponse(resp));
- }
- }
- function listenerBulk(resp) {
- removeRequestListeners();
- deferred.resolve(safeOnResponse(resp));
- }
-
- const removeRequestListeners = () => {
- request.off("json-reply", listenerJson);
- request.off("bulk-reply", listenerBulk);
- };
-
- request.on("json-reply", listenerJson);
- request.on("bulk-reply", listenerBulk);
-
- this._sendOrQueueRequest(request);
- request.then = deferred.promise.then.bind(deferred.promise);
-
- return request;
- },
-
- /**
- * Transmit streaming data via a bulk request.
- *
- * This method initiates the bulk send process by queuing up the header data.
- * The caller receives eventual access to a stream for writing.
- *
- * Since this opens up more options for how the server might respond (it could
- * send back either JSON or bulk data), and the returned Request object emits
- * events for different stages of the request process that you may want to
- * react to.
- *
- * @param request Object
- * This is modeled after the format of JSON packets above, but does not
- * actually contain the data, but is instead just a routing header:
- * * actor: Name of actor that will receive the packet
- * * type: Name of actor's method that should be called on receipt
- * * length: Size of the data to be sent
- * @return Request
- * This object emits a number of events to allow you to respond to
- * different parts of the request lifecycle.
- *
- * Events emitted:
- * * bulk-send-ready: Ready to send bulk data to the server, using the
- * event data object containing:
- * * stream: This output stream should only be used directly if
- * you can ensure that you will write exactly |length|
- * bytes and will not close the stream when writing is
- * complete
- * * done: If you use the stream directly (instead of |copyFrom|
- * below), you must signal completion by resolving /
- * rejecting this deferred. If it's rejected, the
- * transport will be closed. If an Error is supplied as
- * a rejection value, it will be logged via |dumpn|. If
- * you do use |copyFrom|, resolving is taken care of for
- * you when copying completes.
- * * copyFrom: A helper function for getting your data onto the
- * stream that meets the stream handling requirements
- * above, and has the following signature:
- * @param input nsIAsyncInputStream
- * The stream to copy from.
- * @return Promise
- * The promise is resolved when copying completes or
- * rejected if any (unexpected) errors occur.
- * This object also emits "progress" events for each chunk
- * that is copied. See stream-utils.js.
- * * json-reply: The server replied with a JSON packet, which is
- * passed as event data.
- * * bulk-reply: The server replied with bulk data, which you can read
- * using the event data object containing:
- * * actor: Name of actor that received the packet
- * * type: Name of actor's method that was called on receipt
- * * length: Size of the data to be read
- * * stream: This input stream should only be used directly if you
- * can ensure that you will read exactly |length| bytes
- * and will not close the stream when reading is complete
- * * done: If you use the stream directly (instead of |copyTo|
- * below), you must signal completion by resolving /
- * rejecting this deferred. If it's rejected, the
- * transport will be closed. If an Error is supplied as a
- * rejection value, it will be logged via |dumpn|. If you
- * do use |copyTo|, resolving is taken care of for you
- * when copying completes.
- * * copyTo: A helper function for getting your data out of the
- * stream that meets the stream handling requirements
- * above, and has the following signature:
- * @param output nsIAsyncOutputStream
- * The stream to copy to.
- * @return Promise
- * The promise is resolved when copying completes or
- * rejected if any (unexpected) errors occur.
- * This object also emits "progress" events for each chunk
- * that is copied. See stream-utils.js.
- */
- startBulkRequest: function (request) {
- if (!this.traits.bulk) {
- throw Error("Server doesn't support bulk transfers");
- }
- if (!this.mainRoot) {
- throw Error("Have not yet received a hello packet from the server.");
- }
- if (!request.type) {
- throw Error("Bulk packet is missing the required 'type' field.");
- }
- if (!request.actor) {
- throw Error("'" + request.type + "' bulk packet has no destination.");
- }
- if (!request.length) {
- throw Error("'" + request.type + "' bulk packet has no length.");
- }
-
- request = new Request(request);
- request.format = "bulk";
-
- this._sendOrQueueRequest(request);
-
- return request;
- },
-
- /**
- * If a new request can be sent immediately, do so. Otherwise, queue it.
- */
- _sendOrQueueRequest(request) {
- let actor = request.actor;
- if (!this._activeRequests.has(actor)) {
- this._sendRequest(request);
- } else {
- this._queueRequest(request);
- }
- },
-
- /**
- * Send a request.
- * @throws Error if there is already an active request in flight for the same
- * actor.
- */
- _sendRequest(request) {
- let actor = request.actor;
- this.expectReply(actor, request);
-
- if (request.format === "json") {
- this._transport.send(request.request);
- return;
- }
-
- this._transport.startBulkSend(request.request).then((...args) => {
- request.emit("bulk-send-ready", ...args);
- });
- },
-
- /**
- * Queue a request to be sent later. Queues are only drained when an in
- * flight request to a given actor completes.
- */
- _queueRequest(request) {
- let actor = request.actor;
- let queue = this._pendingRequests.get(actor) || [];
- queue.push(request);
- this._pendingRequests.set(actor, queue);
- },
-
- /**
- * Attempt the next request to a given actor (if any).
- */
- _attemptNextRequest(actor) {
- if (this._activeRequests.has(actor)) {
- return;
- }
- let queue = this._pendingRequests.get(actor);
- if (!queue) {
- return;
- }
- let request = queue.shift();
- if (queue.length === 0) {
- this._pendingRequests.delete(actor);
- }
- this._sendRequest(request);
- },
-
- /**
- * Arrange to hand the next reply from |actor| to the handler bound to
- * |request|.
- *
- * DebuggerClient.prototype.request / startBulkRequest usually takes care of
- * establishing the handler for a given request, but in rare cases (well,
- * greetings from new root actors, is the only case at the moment) we must be
- * prepared for a "reply" that doesn't correspond to any request we sent.
- */
- expectReply: function (actor, request) {
- if (this._activeRequests.has(actor)) {
- throw Error("clashing handlers for next reply from " + actor);
- }
-
- // If a handler is passed directly (as it is with the handler for the root
- // actor greeting), create a dummy request to bind this to.
- if (typeof request === "function") {
- let handler = request;
- request = new Request();
- request.on("json-reply", handler);
- }
-
- this._activeRequests.set(actor, request);
- },
-
- // Transport hooks.
-
- /**
- * Called by DebuggerTransport to dispatch incoming packets as appropriate.
- *
- * @param packet object
- * The incoming packet.
- */
- onPacket: function (packet) {
- if (!packet.from) {
- DevToolsUtils.reportException(
- "onPacket",
- new Error("Server did not specify an actor, dropping packet: " +
- JSON.stringify(packet)));
- return;
- }
-
- // If we have a registered Front for this actor, let it handle the packet
- // and skip all the rest of this unpleasantness.
- let front = this.getActor(packet.from);
- if (front) {
- front.onPacket(packet);
- return;
- }
-
- // Check for "forwardingCancelled" here instead of using a client to handle it.
- // This is necessary because we might receive this event while the client is closing,
- // and the clients have already been removed by that point.
- if (this.mainRoot &&
- packet.from == this.mainRoot.actor &&
- packet.type == "forwardingCancelled") {
- this.purgeRequests(packet.prefix);
- return;
- }
-
- if (this._clients.has(packet.from) && packet.type) {
- let client = this._clients.get(packet.from);
- let type = packet.type;
- if (client.events.indexOf(type) != -1) {
- client.emit(type, packet);
- // we ignore the rest, as the client is expected to handle this packet.
- return;
- }
- }
-
- let activeRequest;
- // See if we have a handler function waiting for a reply from this
- // actor. (Don't count unsolicited notifications or pauses as
- // replies.)
- if (this._activeRequests.has(packet.from) &&
- !(packet.type in UnsolicitedNotifications) &&
- !(packet.type == ThreadStateTypes.paused &&
- packet.why.type in UnsolicitedPauses)) {
- activeRequest = this._activeRequests.get(packet.from);
- this._activeRequests.delete(packet.from);
- }
-
- // If there is a subsequent request for the same actor, hand it off to the
- // transport. Delivery of packets on the other end is always async, even
- // in the local transport case.
- this._attemptNextRequest(packet.from);
-
- // Packets that indicate thread state changes get special treatment.
- if (packet.type in ThreadStateTypes &&
- this._clients.has(packet.from) &&
- typeof this._clients.get(packet.from)._onThreadState == "function") {
- this._clients.get(packet.from)._onThreadState(packet);
- }
-
- // TODO: Bug 1151156 - Remove once Gecko 40 is on b2g-stable.
- if (!this.traits.noNeedToFakeResumptionOnNavigation) {
- // On navigation the server resumes, so the client must resume as well.
- // We achieve that by generating a fake resumption packet that triggers
- // the client's thread state change listeners.
- if (packet.type == UnsolicitedNotifications.tabNavigated &&
- this._clients.has(packet.from) &&
- this._clients.get(packet.from).thread) {
- let thread = this._clients.get(packet.from).thread;
- let resumption = { from: thread._actor, type: "resumed" };
- thread._onThreadState(resumption);
- }
- }
-
- // Only try to notify listeners on events, not responses to requests
- // that lack a packet type.
- if (packet.type) {
- this.emit(packet.type, packet);
- }
-
- if (activeRequest) {
- let emitReply = () => activeRequest.emit("json-reply", packet);
- if (activeRequest.stack) {
- callFunctionWithAsyncStack(emitReply, activeRequest.stack,
- "DevTools RDP");
- } else {
- emitReply();
- }
- }
- },
-
- /**
- * Called by the DebuggerTransport to dispatch incoming bulk packets as
- * appropriate.
- *
- * @param packet object
- * The incoming packet, which contains:
- * * actor: Name of actor that will receive the packet
- * * type: Name of actor's method that should be called on receipt
- * * length: Size of the data to be read
- * * stream: This input stream should only be used directly if you can
- * ensure that you will read exactly |length| bytes and will
- * not close the stream when reading is complete
- * * done: If you use the stream directly (instead of |copyTo|
- * below), you must signal completion by resolving /
- * rejecting this deferred. If it's rejected, the transport
- * will be closed. If an Error is supplied as a rejection
- * value, it will be logged via |dumpn|. If you do use
- * |copyTo|, resolving is taken care of for you when copying
- * completes.
- * * copyTo: A helper function for getting your data out of the stream
- * that meets the stream handling requirements above, and has
- * the following signature:
- * @param output nsIAsyncOutputStream
- * The stream to copy to.
- * @return Promise
- * The promise is resolved when copying completes or rejected
- * if any (unexpected) errors occur.
- * This object also emits "progress" events for each chunk
- * that is copied. See stream-utils.js.
- */
- onBulkPacket: function (packet) {
- let { actor } = packet;
-
- if (!actor) {
- DevToolsUtils.reportException(
- "onBulkPacket",
- new Error("Server did not specify an actor, dropping bulk packet: " +
- JSON.stringify(packet)));
- return;
- }
-
- // See if we have a handler function waiting for a reply from this
- // actor.
- if (!this._activeRequests.has(actor)) {
- return;
- }
-
- let activeRequest = this._activeRequests.get(actor);
- this._activeRequests.delete(actor);
-
- // If there is a subsequent request for the same actor, hand it off to the
- // transport. Delivery of packets on the other end is always async, even
- // in the local transport case.
- this._attemptNextRequest(actor);
-
- activeRequest.emit("bulk-reply", packet);
- },
-
- /**
- * Called by DebuggerTransport when the underlying stream is closed.
- *
- * @param status nsresult
- * The status code that corresponds to the reason for closing
- * the stream.
- */
- onClosed: function () {
- this._closed = true;
- this.emit("closed");
-
- this.purgeRequests();
-
- // The |_pools| array on the client-side currently is used only by
- // protocol.js to store active fronts, mirroring the actor pools found in
- // the server. So, read all usages of "pool" as "protocol.js front".
- //
- // In the normal case where we shutdown cleanly, the toolbox tells each tool
- // to close, and they each call |destroy| on any fronts they were using.
- // When |destroy| or |cleanup| is called on a protocol.js front, it also
- // removes itself from the |_pools| array. Once the toolbox has shutdown,
- // the connection is closed, and we reach here. All fronts (should have
- // been) |destroy|ed, so |_pools| should empty.
- //
- // If the connection instead aborts unexpectedly, we may end up here with
- // all fronts used during the life of the connection. So, we call |cleanup|
- // on them clear their state, reject pending requests, and remove themselves
- // from |_pools|. This saves the toolbox from hanging indefinitely, in case
- // it waits for some server response before shutdown that will now never
- // arrive.
- for (let pool of this._pools) {
- pool.cleanup();
- }
- },
-
- /**
- * Purge pending and active requests in this client.
- *
- * @param prefix string (optional)
- * If a prefix is given, only requests for actor IDs that start with the prefix
- * will be cleaned up. This is useful when forwarding of a portion of requests
- * is cancelled on the server.
- */
- purgeRequests(prefix = "") {
- let reject = function (type, request) {
- // Server can send packets on its own and client only pass a callback
- // to expectReply, so that there is no request object.
- let msg;
- if (request.request) {
- msg = "'" + request.request.type + "' " + type + " request packet" +
- " to '" + request.actor + "' " +
- "can't be sent as the connection just closed.";
- } else {
- msg = "server side packet can't be received as the connection just closed.";
- }
- let packet = { error: "connectionClosed", message: msg };
- request.emit("json-reply", packet);
- };
-
- let pendingRequestsToReject = [];
- this._pendingRequests.forEach((requests, actor) => {
- if (!actor.startsWith(prefix)) {
- return;
- }
- this._pendingRequests.delete(actor);
- pendingRequestsToReject = pendingRequestsToReject.concat(requests);
- });
- pendingRequestsToReject.forEach(request => reject("pending", request));
-
- let activeRequestsToReject = [];
- this._activeRequests.forEach((request, actor) => {
- if (!actor.startsWith(prefix)) {
- return;
- }
- this._activeRequests.delete(actor);
- activeRequestsToReject = activeRequestsToReject.concat(request);
- });
- activeRequestsToReject.forEach(request => reject("active", request));
- },
-
- /**
- * Search for all requests in process for this client, including those made via
- * protocol.js and wait all of them to complete. Since the requests seen when this is
- * first called may in turn trigger more requests, we keep recursing through this
- * function until there is no more activity.
- *
- * This is a fairly heavy weight process, so it's only meant to be used in tests.
- *
- * @return Promise
- * Resolved when all requests have settled.
- */
- waitForRequestsToSettle() {
- let requests = [];
-
- // Gather all pending and active requests in this client
- // The request object supports a Promise API for completion (it has .then())
- this._pendingRequests.forEach(requestsForActor => {
- // Each value is an array of pending requests
- requests = requests.concat(requestsForActor);
- });
- this._activeRequests.forEach(requestForActor => {
- // Each value is a single active request
- requests = requests.concat(requestForActor);
- });
-
- // protocol.js
- // Use a Set because some fronts (like domwalker) seem to have multiple parents.
- let fronts = new Set();
- let poolsToVisit = [...this._pools];
-
- // With protocol.js, each front can potentially have it's own pools containing child
- // fronts, forming a tree. Descend through all the pools to locate all child fronts.
- while (poolsToVisit.length) {
- let pool = poolsToVisit.shift();
- fronts.add(pool);
- for (let child of pool.poolChildren()) {
- poolsToVisit.push(child);
- }
- }
-
- // For each front, wait for its requests to settle
- for (let front of fronts) {
- if (front.hasRequests()) {
- requests.push(front.waitForRequestsToSettle());
- }
- }
-
- // Abort early if there are no requests
- if (!requests.length) {
- return Promise.resolve();
- }
-
- return DevToolsUtils.settleAll(requests).catch(() => {
- // One of the requests might have failed, but ignore that situation here and pipe
- // both success and failure through the same path. The important part is just that
- // we waited.
- }).then(() => {
- // Repeat, more requests may have started in response to those we just waited for
- return this.waitForRequestsToSettle();
- });
- },
-
- registerClient: function (client) {
- let actorID = client.actor;
- if (!actorID) {
- throw new Error("DebuggerServer.registerClient expects " +
- "a client instance with an `actor` attribute.");
- }
- if (!Array.isArray(client.events)) {
- throw new Error("DebuggerServer.registerClient expects " +
- "a client instance with an `events` attribute " +
- "that is an array.");
- }
- if (client.events.length > 0 && typeof (client.emit) != "function") {
- throw new Error("DebuggerServer.registerClient expects " +
- "a client instance with non-empty `events` array to" +
- "have an `emit` function.");
- }
- if (this._clients.has(actorID)) {
- throw new Error("DebuggerServer.registerClient already registered " +
- "a client for this actor.");
- }
- this._clients.set(actorID, client);
- },
-
- unregisterClient: function (client) {
- let actorID = client.actor;
- if (!actorID) {
- throw new Error("DebuggerServer.unregisterClient expects " +
- "a Client instance with a `actor` attribute.");
- }
- this._clients.delete(actorID);
- },
-
- /**
- * Actor lifetime management, echos the server's actor pools.
- */
- __pools: null,
- get _pools() {
- if (this.__pools) {
- return this.__pools;
- }
- this.__pools = new Set();
- return this.__pools;
- },
-
- addActorPool: function (pool) {
- this._pools.add(pool);
- },
- removeActorPool: function (pool) {
- this._pools.delete(pool);
- },
- getActor: function (actorID) {
- let pool = this.poolFor(actorID);
- return pool ? pool.get(actorID) : null;
- },
-
- poolFor: function (actorID) {
- for (let pool of this._pools) {
- if (pool.has(actorID)) {
- return pool;
- }
- }
- return null;
- },
-
- /**
- * Currently attached addon.
- */
- activeAddon: null
-};
-
-eventSource(DebuggerClient.prototype);
-
-class Request extends EventEmitter {
- constructor(request) {
- super();
- this.request = request;
- }
-
- get actor() {
- return this.request.to || this.request.actor;
- }
-}
-
-/**
- * Creates a tab client for the remote debugging protocol server. This client
- * is a front to the tab actor created in the server side, hiding the protocol
- * details in a traditional JavaScript API.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param form object
- * The protocol form for this tab.
- */
-function TabClient(client, form) {
- this.client = client;
- this._actor = form.from;
- this._threadActor = form.threadActor;
- this.javascriptEnabled = form.javascriptEnabled;
- this.cacheDisabled = form.cacheDisabled;
- this.thread = null;
- this.request = this.client.request;
- this.traits = form.traits || {};
- this.events = ["workerListChanged"];
-}
-
-TabClient.prototype = {
- get actor() {
- return this._actor;
- },
- get _transport() {
- return this.client._transport;
- },
-
- /**
- * Attach to a thread actor.
- *
- * @param object options
- * Configuration options.
- * - useSourceMaps: whether to use source maps or not.
- * @param function onResponse
- * Called with the response packet and a ThreadClient
- * (which will be undefined on error).
- */
- attachThread: function (options = {}, onResponse = noop) {
- if (this.thread) {
- DevToolsUtils.executeSoon(() => onResponse({}, this.thread));
- return promise.resolve([{}, this.thread]);
- }
-
- let packet = {
- to: this._threadActor,
- type: "attach",
- options,
- };
- return this.request(packet).then(response => {
- if (!response.error) {
- this.thread = new ThreadClient(this, this._threadActor);
- this.client.registerClient(this.thread);
- }
- onResponse(response, this.thread);
- return [response, this.thread];
- });
- },
-
- /**
- * Detach the client from the tab actor.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- detach: DebuggerClient.requester({
- type: "detach"
- }, {
- before: function (packet) {
- if (this.thread) {
- this.thread.detach();
- }
- return packet;
- },
- after: function (response) {
- this.client.unregisterClient(this);
- return response;
- },
- }),
-
- /**
- * Bring the window to the front.
- */
- focus: DebuggerClient.requester({
- type: "focus"
- }, {}),
-
- /**
- * Reload the page in this tab.
- *
- * @param [optional] object options
- * An object with a `force` property indicating whether or not
- * this reload should skip the cache
- */
- reload: function (options = { force: false }) {
- return this._reload(options);
- },
- _reload: DebuggerClient.requester({
- type: "reload",
- options: arg(0)
- }),
-
- /**
- * Navigate to another URL.
- *
- * @param string url
- * The URL to navigate to.
- */
- navigateTo: DebuggerClient.requester({
- type: "navigateTo",
- url: arg(0)
- }),
-
- /**
- * Reconfigure the tab actor.
- *
- * @param object options
- * A dictionary object of the new options to use in the tab actor.
- * @param function onResponse
- * Called with the response packet.
- */
- reconfigure: DebuggerClient.requester({
- type: "reconfigure",
- options: arg(0)
- }),
-
- listWorkers: DebuggerClient.requester({
- type: "listWorkers"
- }),
-
- attachWorker: function (workerActor, onResponse) {
- return this.client.attachWorker(workerActor, onResponse);
- },
-};
-
-eventSource(TabClient.prototype);
-
-function WorkerClient(client, form) {
- this.client = client;
- this._actor = form.from;
- this._isClosed = false;
- this._url = form.url;
-
- this._onClose = this._onClose.bind(this);
-
- this.addListener("close", this._onClose);
-
- this.traits = {};
-}
-
-WorkerClient.prototype = {
- get _transport() {
- return this.client._transport;
- },
-
- get request() {
- return this.client.request;
- },
-
- get actor() {
- return this._actor;
- },
-
- get url() {
- return this._url;
- },
-
- get isClosed() {
- return this._isClosed;
- },
-
- detach: DebuggerClient.requester({ type: "detach" }, {
- after: function (response) {
- if (this.thread) {
- this.client.unregisterClient(this.thread);
- }
- this.client.unregisterClient(this);
- return response;
- },
- }),
-
- attachThread: function (options = {}, onResponse = noop) {
- if (this.thread) {
- let response = [{
- type: "connected",
- threadActor: this.thread._actor,
- consoleActor: this.consoleActor,
- }, this.thread];
- DevToolsUtils.executeSoon(() => onResponse(response));
- return response;
- }
-
- // The connect call on server doesn't attach the thread as of version 44.
- return this.request({
- to: this._actor,
- type: "connect",
- options,
- }).then(connectResponse => {
- if (connectResponse.error) {
- onResponse(connectResponse, null);
- return [connectResponse, null];
- }
-
- return this.request({
- to: connectResponse.threadActor,
- type: "attach",
- options,
- }).then(attachResponse => {
- if (attachResponse.error) {
- onResponse(attachResponse, null);
- }
-
- this.thread = new ThreadClient(this, connectResponse.threadActor);
- this.consoleActor = connectResponse.consoleActor;
- this.client.registerClient(this.thread);
-
- onResponse(connectResponse, this.thread);
- return [connectResponse, this.thread];
- });
- }, error => {
- onResponse(error, null);
- });
- },
-
- _onClose: function () {
- this.removeListener("close", this._onClose);
-
- if (this.thread) {
- this.client.unregisterClient(this.thread);
- }
- this.client.unregisterClient(this);
- this._isClosed = true;
- },
-
- reconfigure: function () {
- return Promise.resolve();
- },
-
- events: ["close"]
-};
-
-eventSource(WorkerClient.prototype);
-
-function AddonClient(client, actor) {
- this._client = client;
- this._actor = actor;
- this.request = this._client.request;
- this.events = [];
-}
-
-AddonClient.prototype = {
- get actor() {
- return this._actor;
- },
- get _transport() {
- return this._client._transport;
- },
-
- /**
- * Detach the client from the addon actor.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- detach: DebuggerClient.requester({
- type: "detach"
- }, {
- after: function (response) {
- if (this._client.activeAddon === this) {
- this._client.activeAddon = null;
- }
- this._client.unregisterClient(this);
- return response;
- },
- })
-};
-
-/**
- * A RootClient object represents a root actor on the server. Each
- * DebuggerClient keeps a RootClient instance representing the root actor
- * for the initial connection; DebuggerClient's 'listTabs' and
- * 'listChildProcesses' methods forward to that root actor.
- *
- * @param client object
- * The client connection to which this actor belongs.
- * @param greeting string
- * The greeting packet from the root actor we're to represent.
- *
- * Properties of a RootClient instance:
- *
- * @property actor string
- * The name of this child's root actor.
- * @property applicationType string
- * The application type, as given in the root actor's greeting packet.
- * @property traits object
- * The traits object, as given in the root actor's greeting packet.
- */
-function RootClient(client, greeting) {
- this._client = client;
- this.actor = greeting.from;
- this.applicationType = greeting.applicationType;
- this.traits = greeting.traits;
-}
-exports.RootClient = RootClient;
-
-RootClient.prototype = {
- constructor: RootClient,
-
- /**
- * Gets the "root" form, which lists all the global actors that affect the entire
- * browser. This can replace usages of `listTabs` that only wanted the global actors
- * and didn't actually care about tabs.
- */
- getRoot: DebuggerClient.requester({ type: "getRoot" }),
-
- /**
- * List the open tabs.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- listTabs: DebuggerClient.requester({ type: "listTabs" }),
-
- /**
- * List the installed addons.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- listAddons: DebuggerClient.requester({ type: "listAddons" }),
-
- /**
- * List the registered workers.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- listWorkers: DebuggerClient.requester({ type: "listWorkers" }),
-
- /**
- * List the registered service workers.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- listServiceWorkerRegistrations: DebuggerClient.requester({
- type: "listServiceWorkerRegistrations"
- }),
-
- /**
- * List the running processes.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- listProcesses: DebuggerClient.requester({ type: "listProcesses" }),
-
- /**
- * Fetch the TabActor for the currently selected tab, or for a specific
- * tab given as first parameter.
- *
- * @param [optional] object filter
- * A dictionary object with following optional attributes:
- * - outerWindowID: used to match tabs in parent process
- * - tabId: used to match tabs in child processes
- * - tab: a reference to xul:tab element
- * If nothing is specified, returns the actor for the currently
- * selected tab.
- */
- getTab: function (filter) {
- let packet = {
- to: this.actor,
- type: "getTab"
- };
+const {DebuggerClient} = require("./debugger-client");
+const EnvironmentClient = require("./environment-client");
+const LongStringClient = require("./long-string-client");
+const ObjectClient = require("./object-client");
+const RootClient = require("./root-client");
- if (filter) {
- if (typeof (filter.outerWindowID) == "number") {
- packet.outerWindowID = filter.outerWindowID;
- } else if (typeof (filter.tabId) == "number") {
- packet.tabId = filter.tabId;
- } else if ("tab" in filter) {
- let browser = filter.tab.linkedBrowser;
- if (browser.frameLoader.tabParent) {
- // Tabs in child process
- packet.tabId = browser.frameLoader.tabParent.tabId;
- } else if (browser.outerWindowID) {
- // <xul:browser> tabs in parent process
- packet.outerWindowID = browser.outerWindowID;
- } else {
- // <iframe mozbrowser> tabs in parent process
- let windowUtils = browser.contentWindow
- .QueryInterface(Ci.nsIInterfaceRequestor)
- .getInterface(Ci.nsIDOMWindowUtils);
- packet.outerWindowID = windowUtils.outerWindowID;
- }
- } else {
- // Throw if a filter object have been passed but without
- // any clearly idenfified filter.
- throw new Error("Unsupported argument given to getTab request");
- }
- }
-
- return this.request(packet);
- },
-
- /**
- * Fetch the WindowActor for a specific window, like a browser window in
- * Firefox, but it can be used to reach any window in the process.
- *
- * @param number outerWindowID
- * The outerWindowID of the top level window you are looking for.
- */
- getWindow: function ({ outerWindowID }) {
- if (!outerWindowID) {
- throw new Error("Must specify outerWindowID");
- }
-
- let packet = {
- to: this.actor,
- type: "getWindow",
- outerWindowID,
- };
-
- return this.request(packet);
- },
-
- /**
- * Description of protocol's actors and methods.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- protocolDescription: DebuggerClient.requester({ type: "protocolDescription" }),
-
- /*
- * Methods constructed by DebuggerClient.requester require these forwards
- * on their 'this'.
- */
- get _transport() {
- return this._client._transport;
- },
- get request() {
- return this._client.request;
- }
-};
-
-/**
- * Creates a thread client for the remote debugging protocol server. This client
- * is a front to the thread actor created in the server side, hiding the
- * protocol details in a traditional JavaScript API.
- *
- * @param client DebuggerClient|TabClient
- * The parent of the thread (tab for tab-scoped debuggers, DebuggerClient
- * for chrome debuggers).
- * @param actor string
- * The actor ID for this thread.
- */
-function ThreadClient(client, actor) {
- this._parent = client;
- this.client = client instanceof DebuggerClient ? client : client.client;
- this._actor = actor;
- this._frameCache = [];
- this._scriptCache = {};
- this._pauseGrips = {};
- this._threadGrips = {};
- this.request = this.client.request;
-}
-
-ThreadClient.prototype = {
- _state: "paused",
- get state() {
- return this._state;
- },
- get paused() {
- return this._state === "paused";
- },
-
- _pauseOnExceptions: false,
- _ignoreCaughtExceptions: false,
- _pauseOnDOMEvents: null,
-
- _actor: null,
- get actor() {
- return this._actor;
- },
-
- get _transport() {
- return this.client._transport;
- },
-
- _assertPaused: function (command) {
- if (!this.paused) {
- throw Error(command + " command sent while not paused. Currently " + this._state);
- }
- },
-
- /**
- * Resume a paused thread. If the optional limit parameter is present, then
- * the thread will also pause when that limit is reached.
- *
- * @param [optional] object limit
- * An object with a type property set to the appropriate limit (next,
- * step, or finish) per the remote debugging protocol specification.
- * Use null to specify no limit.
- * @param function onResponse
- * Called with the response packet.
- */
- _doResume: DebuggerClient.requester({
- type: "resume",
- resumeLimit: arg(0)
- }, {
- before: function (packet) {
- this._assertPaused("resume");
-
- // Put the client in a tentative "resuming" state so we can prevent
- // further requests that should only be sent in the paused state.
- this._previousState = this._state;
- this._state = "resuming";
-
- if (this._pauseOnExceptions) {
- packet.pauseOnExceptions = this._pauseOnExceptions;
- }
- if (this._ignoreCaughtExceptions) {
- packet.ignoreCaughtExceptions = this._ignoreCaughtExceptions;
- }
- if (this._pauseOnDOMEvents) {
- packet.pauseOnDOMEvents = this._pauseOnDOMEvents;
- }
- return packet;
- },
- after: function (response) {
- if (response.error && this._state == "resuming") {
- // There was an error resuming, update the state to the new one
- // reported by the server, if given (only on wrongState), otherwise
- // reset back to the previous state.
- if (response.state) {
- this._state = ThreadStateTypes[response.state];
- } else {
- this._state = this._previousState;
- }
- }
- delete this._previousState;
- return response;
- },
- }),
-
- /**
- * Reconfigure the thread actor.
- *
- * @param object options
- * A dictionary object of the new options to use in the thread actor.
- * @param function onResponse
- * Called with the response packet.
- */
- reconfigure: DebuggerClient.requester({
- type: "reconfigure",
- options: arg(0)
- }),
-
- /**
- * Resume a paused thread.
- */
- resume: function (onResponse) {
- return this._doResume(null, onResponse);
- },
-
- /**
- * Resume then pause without stepping.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- resumeThenPause: function (onResponse) {
- return this._doResume({ type: "break" }, onResponse);
- },
-
- /**
- * Step over a function call.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- stepOver: function (onResponse) {
- return this._doResume({ type: "next" }, onResponse);
- },
-
- /**
- * Step into a function call.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- stepIn: function (onResponse) {
- return this._doResume({ type: "step" }, onResponse);
- },
-
- /**
- * Step out of a function call.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- stepOut: function (onResponse) {
- return this._doResume({ type: "finish" }, onResponse);
- },
-
- /**
- * Immediately interrupt a running thread.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- interrupt: function (onResponse) {
- return this._doInterrupt(null, onResponse);
- },
-
- /**
- * Pause execution right before the next JavaScript bytecode is executed.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- breakOnNext: function (onResponse) {
- return this._doInterrupt("onNext", onResponse);
- },
-
- /**
- * Interrupt a running thread.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- _doInterrupt: DebuggerClient.requester({
- type: "interrupt",
- when: arg(0)
- }),
-
- /**
- * Enable or disable pausing when an exception is thrown.
- *
- * @param boolean pauseOnExceptions
- * Enables pausing if true, disables otherwise.
- * @param boolean ignoreCaughtExceptions
- * Whether to ignore caught exceptions
- * @param function onResponse
- * Called with the response packet.
- */
- pauseOnExceptions: function (pauseOnExceptions,
- ignoreCaughtExceptions,
- onResponse = noop) {
- this._pauseOnExceptions = pauseOnExceptions;
- this._ignoreCaughtExceptions = ignoreCaughtExceptions;
-
- // Otherwise send the flag using a standard resume request.
- if (!this.paused) {
- return this.interrupt(response => {
- if (response.error) {
- // Can't continue if pausing failed.
- onResponse(response);
- return response;
- }
- return this.resume(onResponse);
- });
- }
-
- onResponse();
- return promise.resolve();
- },
-
- /**
- * Enable pausing when the specified DOM events are triggered. Disabling
- * pausing on an event can be realized by calling this method with the updated
- * array of events that doesn't contain it.
- *
- * @param array|string events
- * An array of strings, representing the DOM event types to pause on,
- * or "*" to pause on all DOM events. Pass an empty array to
- * completely disable pausing on DOM events.
- * @param function onResponse
- * Called with the response packet in a future turn of the event loop.
- */
- pauseOnDOMEvents: function (events, onResponse = noop) {
- this._pauseOnDOMEvents = events;
- // If the debuggee is paused, the value of the array will be communicated in
- // the next resumption. Otherwise we have to force a pause in order to send
- // the array.
- if (this.paused) {
- DevToolsUtils.executeSoon(() => onResponse({}));
- return {};
- }
- return this.interrupt(response => {
- // Can't continue if pausing failed.
- if (response.error) {
- onResponse(response);
- return response;
- }
- return this.resume(onResponse);
- });
- },
-
- /**
- * Send a clientEvaluate packet to the debuggee. Response
- * will be a resume packet.
- *
- * @param string frame
- * The actor ID of the frame where the evaluation should take place.
- * @param string expression
- * The expression that will be evaluated in the scope of the frame
- * above.
- * @param function onResponse
- * Called with the response packet.
- */
- eval: DebuggerClient.requester({
- type: "clientEvaluate",
- frame: arg(0),
- expression: arg(1)
- }, {
- before: function (packet) {
- this._assertPaused("eval");
- // Put the client in a tentative "resuming" state so we can prevent
- // further requests that should only be sent in the paused state.
- this._state = "resuming";
- return packet;
- },
- after: function (response) {
- if (response.error) {
- // There was an error resuming, back to paused state.
- this._state = "paused";
- }
- return response;
- },
- }),
-
- /**
- * Detach from the thread actor.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- detach: DebuggerClient.requester({
- type: "detach"
- }, {
- after: function (response) {
- this.client.unregisterClient(this);
- this._parent.thread = null;
- return response;
- },
- }),
-
- /**
- * Release multiple thread-lifetime object actors. If any pause-lifetime
- * actors are included in the request, a |notReleasable| error will return,
- * but all the thread-lifetime ones will have been released.
- *
- * @param array actors
- * An array with actor IDs to release.
- */
- releaseMany: DebuggerClient.requester({
- type: "releaseMany",
- actors: arg(0),
- }),
-
- /**
- * Promote multiple pause-lifetime object actors to thread-lifetime ones.
- *
- * @param array actors
- * An array with actor IDs to promote.
- */
- threadGrips: DebuggerClient.requester({
- type: "threadGrips",
- actors: arg(0)
- }),
-
- /**
- * Return the event listeners defined on the page.
- *
- * @param onResponse Function
- * Called with the thread's response.
- */
- eventListeners: DebuggerClient.requester({
- type: "eventListeners"
- }),
-
- /**
- * Request the loaded sources for the current thread.
- *
- * @param onResponse Function
- * Called with the thread's response.
- */
- getSources: DebuggerClient.requester({
- type: "sources"
- }),
-
- /**
- * Clear the thread's source script cache. A scriptscleared event
- * will be sent.
- */
- _clearScripts: function () {
- if (Object.keys(this._scriptCache).length > 0) {
- this._scriptCache = {};
- this.emit("scriptscleared");
- }
- },
-
- /**
- * Request frames from the callstack for the current thread.
- *
- * @param start integer
- * The number of the youngest stack frame to return (the youngest
- * frame is 0).
- * @param count integer
- * The maximum number of frames to return, or null to return all
- * frames.
- * @param onResponse function
- * Called with the thread's response.
- */
- getFrames: DebuggerClient.requester({
- type: "frames",
- start: arg(0),
- count: arg(1)
- }),
-
- /**
- * An array of cached frames. Clients can observe the framesadded and
- * framescleared event to keep up to date on changes to this cache,
- * and can fill it using the fillFrames method.
- */
- get cachedFrames() {
- return this._frameCache;
- },
-
- /**
- * true if there are more stack frames available on the server.
- */
- get moreFrames() {
- return this.paused && (!this._frameCache || this._frameCache.length == 0
- || !this._frameCache[this._frameCache.length - 1].oldest);
- },
-
- /**
- * Request the frame environment.
- *
- * @param frameId string
- */
- getEnvironment: function (frameId) {
- return this.request({ to: frameId, type: "getEnvironment" });
- },
-
- /**
- * Ensure that at least total stack frames have been loaded in the
- * ThreadClient's stack frame cache. A framesadded event will be
- * sent when the stack frame cache is updated.
- *
- * @param total number
- * The minimum number of stack frames to be included.
- * @param callback function
- * Optional callback function called when frames have been loaded
- * @returns true if a framesadded notification should be expected.
- */
- fillFrames: function (total, callback = noop) {
- this._assertPaused("fillFrames");
- if (this._frameCache.length >= total) {
- return false;
- }
-
- let numFrames = this._frameCache.length;
-
- this.getFrames(numFrames, total - numFrames, (response) => {
- if (response.error) {
- callback(response);
- return;
- }
-
- let threadGrips = DevToolsUtils.values(this._threadGrips);
-
- for (let i in response.frames) {
- let frame = response.frames[i];
- if (!frame.where.source) {
- // Older servers use urls instead, so we need to resolve
- // them to source actors
- for (let grip of threadGrips) {
- if (grip instanceof SourceClient && grip.url === frame.url) {
- frame.where.source = grip._form;
- }
- }
- }
-
- this._frameCache[frame.depth] = frame;
- }
-
- // If we got as many frames as we asked for, there might be more
- // frames available.
- this.emit("framesadded");
-
- callback(response);
- });
-
- return true;
- },
-
- /**
- * Clear the thread's stack frame cache. A framescleared event
- * will be sent.
- */
- _clearFrames: function () {
- if (this._frameCache.length > 0) {
- this._frameCache = [];
- this.emit("framescleared");
- }
- },
-
- /**
- * Return a ObjectClient object for the given object grip.
- *
- * @param grip object
- * A pause-lifetime object grip returned by the protocol.
- */
- pauseGrip: function (grip) {
- if (grip.actor in this._pauseGrips) {
- return this._pauseGrips[grip.actor];
- }
-
- let client = new ObjectClient(this.client, grip);
- this._pauseGrips[grip.actor] = client;
- return client;
- },
-
- /**
- * Get or create a long string client, checking the grip client cache if it
- * already exists.
- *
- * @param grip Object
- * The long string grip returned by the protocol.
- * @param gripCacheName String
- * The property name of the grip client cache to check for existing
- * clients in.
- */
- _longString: function (grip, gripCacheName) {
- if (grip.actor in this[gripCacheName]) {
- return this[gripCacheName][grip.actor];
- }
-
- let client = new LongStringClient(this.client, grip);
- this[gripCacheName][grip.actor] = client;
- return client;
- },
-
- /**
- * Return an instance of LongStringClient for the given long string grip that
- * is scoped to the current pause.
- *
- * @param grip Object
- * The long string grip returned by the protocol.
- */
- pauseLongString: function (grip) {
- return this._longString(grip, "_pauseGrips");
- },
-
- /**
- * Return an instance of LongStringClient for the given long string grip that
- * is scoped to the thread lifetime.
- *
- * @param grip Object
- * The long string grip returned by the protocol.
- */
- threadLongString: function (grip) {
- return this._longString(grip, "_threadGrips");
- },
-
- /**
- * Get or create an ArrayBuffer client, checking the grip client cache if it
- * already exists.
- *
- * @param grip Object
- * The ArrayBuffer grip returned by the protocol.
- * @param gripCacheName String
- * The property name of the grip client cache to check for existing
- * clients in.
- */
- _arrayBuffer: function (grip, gripCacheName) {
- if (grip.actor in this[gripCacheName]) {
- return this[gripCacheName][grip.actor];
- }
-
- let client = new ArrayBufferClient(this.client, grip);
- this[gripCacheName][grip.actor] = client;
- return client;
- },
-
- /**
- * Return an instance of ArrayBufferClient for the given ArrayBuffer grip that
- * is scoped to the thread lifetime.
- *
- * @param grip Object
- * The ArrayBuffer grip returned by the protocol.
- */
- threadArrayBuffer: function (grip) {
- return this._arrayBuffer(grip, "_threadGrips");
- },
-
- /**
- * Clear and invalidate all the grip clients from the given cache.
- *
- * @param gripCacheName
- * The property name of the grip cache we want to clear.
- */
- _clearObjectClients: function (gripCacheName) {
- for (let id in this[gripCacheName]) {
- this[gripCacheName][id].valid = false;
- }
- this[gripCacheName] = {};
- },
-
- /**
- * Invalidate pause-lifetime grip clients and clear the list of current grip
- * clients.
- */
- _clearPauseGrips: function () {
- this._clearObjectClients("_pauseGrips");
- },
-
- /**
- * Invalidate thread-lifetime grip clients and clear the list of current grip
- * clients.
- */
- _clearThreadGrips: function () {
- this._clearObjectClients("_threadGrips");
- },
-
- /**
- * Handle thread state change by doing necessary cleanup and notifying all
- * registered listeners.
- */
- _onThreadState: function (packet) {
- this._state = ThreadStateTypes[packet.type];
- // The debugger UI may not be initialized yet so we want to keep
- // the packet around so it knows what to pause state to display
- // when it's initialized
- this._lastPausePacket = packet.type === "resumed" ? null : packet;
- this._clearFrames();
- this._clearPauseGrips();
- packet.type === ThreadStateTypes.detached && this._clearThreadGrips();
- this.client._eventsEnabled && this.emit(packet.type, packet);
- },
-
- getLastPausePacket: function () {
- return this._lastPausePacket;
- },
-
- /**
- * Return an EnvironmentClient instance for the given environment actor form.
- */
- environment: function (form) {
- return new EnvironmentClient(this.client, form);
- },
-
- /**
- * Return an instance of SourceClient for the given source actor form.
- */
- source: function (form) {
- if (form.actor in this._threadGrips) {
- return this._threadGrips[form.actor];
- }
-
- this._threadGrips[form.actor] = new SourceClient(this, form);
- return this._threadGrips[form.actor];
- },
-
- /**
- * Request the prototype and own properties of mutlipleObjects.
- *
- * @param onResponse function
- * Called with the request's response.
- * @param actors [string]
- * List of actor ID of the queried objects.
- */
- getPrototypesAndProperties: DebuggerClient.requester({
- type: "prototypesAndProperties",
- actors: arg(0)
- }),
-
- events: ["newSource"]
-};
-
-eventSource(ThreadClient.prototype);
-
-/**
- * Creates a tracing profiler client for the remote debugging protocol
- * server. This client is a front to the trace actor created on the
- * server side, hiding the protocol details in a traditional
- * JavaScript API.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param actor string
- * The actor ID for this thread.
- */
-function TraceClient(client, actor) {
- this._client = client;
- this._actor = actor;
- this._activeTraces = new Set();
- this._waitingPackets = new Map();
- this._expectedPacket = 0;
- this.request = this._client.request;
- this.events = [];
-}
-
-TraceClient.prototype = {
- get actor() {
- return this._actor;
- },
- get tracing() {
- return this._activeTraces.size > 0;
- },
-
- get _transport() {
- return this._client._transport;
- },
-
- /**
- * Detach from the trace actor.
- */
- detach: DebuggerClient.requester({
- type: "detach"
- }, {
- after: function (response) {
- this._client.unregisterClient(this);
- return response;
- },
- }),
-
- /**
- * Start a new trace.
- *
- * @param trace [string]
- * An array of trace types to be recorded by the new trace.
- *
- * @param name string
- * The name of the new trace.
- *
- * @param onResponse function
- * Called with the request's response.
- */
- startTrace: DebuggerClient.requester({
- type: "startTrace",
- name: arg(1),
- trace: arg(0)
- }, {
- after: function (response) {
- if (response.error) {
- return response;
- }
-
- if (!this.tracing) {
- this._waitingPackets.clear();
- this._expectedPacket = 0;
- }
- this._activeTraces.add(response.name);
-
- return response;
- },
- }),
-
- /**
- * End a trace. If a name is provided, stop the named
- * trace. Otherwise, stop the most recently started trace.
- *
- * @param name string
- * The name of the trace to stop.
- *
- * @param onResponse function
- * Called with the request's response.
- */
- stopTrace: DebuggerClient.requester({
- type: "stopTrace",
- name: arg(0)
- }, {
- after: function (response) {
- if (response.error) {
- return response;
- }
-
- this._activeTraces.delete(response.name);
-
- return response;
- },
- })
+module.exports = {
+ DebuggerClient,
+ EnvironmentClient,
+ LongStringClient,
+ ObjectClient,
+ RootClient,
};
-
-/**
- * Grip clients are used to retrieve information about the relevant object.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param grip object
- * A pause-lifetime object grip returned by the protocol.
- */
-function ObjectClient(client, grip) {
- this._grip = grip;
- this._client = client;
- this.request = this._client.request;
-}
-exports.ObjectClient = ObjectClient;
-
-ObjectClient.prototype = {
- get actor() {
- return this._grip.actor;
- },
- get _transport() {
- return this._client._transport;
- },
-
- valid: true,
-
- get isFrozen() {
- return this._grip.frozen;
- },
- get isSealed() {
- return this._grip.sealed;
- },
- get isExtensible() {
- return this._grip.extensible;
- },
-
- getDefinitionSite: DebuggerClient.requester({
- type: "definitionSite"
- }, {
- before: function (packet) {
- if (this._grip.class != "Function") {
- throw new Error("getDefinitionSite is only valid for function grips.");
- }
- return packet;
- }
- }),
-
- /**
- * Request the names of a function's formal parameters.
- *
- * @param onResponse function
- * Called with an object of the form:
- * { parameterNames:[<parameterName>, ...] }
- * where each <parameterName> is the name of a parameter.
- */
- getParameterNames: DebuggerClient.requester({
- type: "parameterNames"
- }, {
- before: function (packet) {
- if (this._grip.class !== "Function") {
- throw new Error("getParameterNames is only valid for function grips.");
- }
- return packet;
- },
- }),
-
- /**
- * Request the names of the properties defined on the object and not its
- * prototype.
- *
- * @param onResponse function Called with the request's response.
- */
- getOwnPropertyNames: DebuggerClient.requester({
- type: "ownPropertyNames"
- }),
-
- /**
- * Request the prototype and own properties of the object.
- *
- * @param onResponse function Called with the request's response.
- */
- getPrototypeAndProperties: DebuggerClient.requester({
- type: "prototypeAndProperties"
- }),
-
- /**
- * Request a PropertyIteratorClient instance to ease listing
- * properties for this object.
- *
- * @param options Object
- * A dictionary object with various boolean attributes:
- * - ignoreIndexedProperties Boolean
- * If true, filters out Array items.
- * e.g. properties names between `0` and `object.length`.
- * - ignoreNonIndexedProperties Boolean
- * If true, filters out items that aren't array items
- * e.g. properties names that are not a number between `0`
- * and `object.length`.
- * - sort Boolean
- * If true, the iterator will sort the properties by name
- * before dispatching them.
- * @param onResponse function Called with the client instance.
- */
- enumProperties: DebuggerClient.requester({
- type: "enumProperties",
- options: arg(0)
- }, {
- after: function (response) {
- if (response.iterator) {
- return { iterator: new PropertyIteratorClient(this._client, response.iterator) };
- }
- return response;
- },
- }),
-
- /**
- * Request a PropertyIteratorClient instance to enumerate entries in a
- * Map/Set-like object.
- *
- * @param onResponse function Called with the request's response.
- */
- enumEntries: DebuggerClient.requester({
- type: "enumEntries"
- }, {
- before: function (packet) {
- if (!["Map", "WeakMap", "Set", "WeakSet"].includes(this._grip.class)) {
- throw new Error("enumEntries is only valid for Map/Set-like grips.");
- }
- return packet;
- },
- after: function (response) {
- if (response.iterator) {
- return {
- iterator: new PropertyIteratorClient(this._client, response.iterator)
- };
- }
- return response;
- }
- }),
-
- /**
- * Request a SymbolIteratorClient instance to enumerate symbols in an object.
- *
- * @param onResponse function Called with the request's response.
- */
- enumSymbols: DebuggerClient.requester({
- type: "enumSymbols"
- }, {
- before: function (packet) {
- if (this._grip.type !== "object") {
- throw new Error("enumSymbols is only valid for objects grips.");
- }
- return packet;
- },
- after: function (response) {
- if (response.iterator) {
- return {
- iterator: new SymbolIteratorClient(this._client, response.iterator)
- };
- }
- return response;
- }
- }),
-
- /**
- * Request the property descriptor of the object's specified property.
- *
- * @param name string The name of the requested property.
- * @param onResponse function Called with the request's response.
- */
- getProperty: DebuggerClient.requester({
- type: "property",
- name: arg(0)
- }),
-
- /**
- * Request the prototype of the object.
- *
- * @param onResponse function Called with the request's response.
- */
- getPrototype: DebuggerClient.requester({
- type: "prototype"
- }),
-
- /**
- * Request the display string of the object.
- *
- * @param onResponse function Called with the request's response.
- */
- getDisplayString: DebuggerClient.requester({
- type: "displayString"
- }),
-
- /**
- * Request the scope of the object.
- *
- * @param onResponse function Called with the request's response.
- */
- getScope: DebuggerClient.requester({
- type: "scope"
- }, {
- before: function (packet) {
- if (this._grip.class !== "Function") {
- throw new Error("scope is only valid for function grips.");
- }
- return packet;
- },
- }),
-
- /**
- * Request the promises directly depending on the current promise.
- */
- getDependentPromises: DebuggerClient.requester({
- type: "dependentPromises"
- }, {
- before: function (packet) {
- if (this._grip.class !== "Promise") {
- throw new Error("getDependentPromises is only valid for promise " +
- "grips.");
- }
- return packet;
- }
- }),
-
- /**
- * Request the stack to the promise's allocation point.
- */
- getPromiseAllocationStack: DebuggerClient.requester({
- type: "allocationStack"
- }, {
- before: function (packet) {
- if (this._grip.class !== "Promise") {
- throw new Error("getAllocationStack is only valid for promise grips.");
- }
- return packet;
- }
- }),
-
- /**
- * Request the stack to the promise's fulfillment point.
- */
- getPromiseFulfillmentStack: DebuggerClient.requester({
- type: "fulfillmentStack"
- }, {
- before: function (packet) {
- if (this._grip.class !== "Promise") {
- throw new Error("getPromiseFulfillmentStack is only valid for " +
- "promise grips.");
- }
- return packet;
- }
- }),
-
- /**
- * Request the stack to the promise's rejection point.
- */
- getPromiseRejectionStack: DebuggerClient.requester({
- type: "rejectionStack"
- }, {
- before: function (packet) {
- if (this._grip.class !== "Promise") {
- throw new Error("getPromiseRejectionStack is only valid for " +
- "promise grips.");
- }
- return packet;
- }
- })
-};
-
-/**
- * A PropertyIteratorClient provides a way to access to property names and
- * values of an object efficiently, slice by slice.
- * Note that the properties can be sorted in the backend,
- * this is controled while creating the PropertyIteratorClient
- * from ObjectClient.enumProperties.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param grip Object
- * A PropertyIteratorActor grip returned by the protocol via
- * TabActor.enumProperties request.
- */
-function PropertyIteratorClient(client, grip) {
- this._grip = grip;
- this._client = client;
- this.request = this._client.request;
-}
-
-PropertyIteratorClient.prototype = {
- get actor() {
- return this._grip.actor;
- },
-
- /**
- * Get the total number of properties available in the iterator.
- */
- get count() {
- return this._grip.count;
- },
-
- /**
- * Get one or more property names that correspond to the positions in the
- * indexes parameter.
- *
- * @param indexes Array
- * An array of property indexes.
- * @param callback Function
- * The function called when we receive the property names.
- */
- names: DebuggerClient.requester({
- type: "names",
- indexes: arg(0)
- }, {}),
-
- /**
- * Get a set of following property value(s).
- *
- * @param start Number
- * The index of the first property to fetch.
- * @param count Number
- * The number of properties to fetch.
- * @param callback Function
- * The function called when we receive the property values.
- */
- slice: DebuggerClient.requester({
- type: "slice",
- start: arg(0),
- count: arg(1)
- }, {}),
-
- /**
- * Get all the property values.
- *
- * @param callback Function
- * The function called when we receive the property values.
- */
- all: DebuggerClient.requester({
- type: "all"
- }, {}),
-};
-
-/**
- * A SymbolIteratorClient provides a way to access to symbols
- * of an object efficiently, slice by slice.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param grip Object
- * A SymbolIteratorActor grip returned by the protocol via
- * TabActor.enumSymbols request.
- */
-function SymbolIteratorClient(client, grip) {
- this._grip = grip;
- this._client = client;
- this.request = this._client.request;
-}
-
-SymbolIteratorClient.prototype = {
- get actor() {
- return this._grip.actor;
- },
-
- /**
- * Get the total number of symbols available in the iterator.
- */
- get count() {
- return this._grip.count;
- },
-
- /**
- * Get a set of following symbols.
- *
- * @param start Number
- * The index of the first symbol to fetch.
- * @param count Number
- * The number of symbols to fetch.
- * @param callback Function
- * The function called when we receive the symbols.
- */
- slice: DebuggerClient.requester({
- type: "slice",
- start: arg(0),
- count: arg(1)
- }, {}),
-
- /**
- * Get all the symbols.
- *
- * @param callback Function
- * The function called when we receive the symbols.
- */
- all: DebuggerClient.requester({
- type: "all"
- }, {}),
-};
-
-/**
- * A ArrayBufferClient provides a way to access ArrayBuffer from the
- * debugger server.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param grip Object
- * A pause-lifetime ArrayBuffer grip returned by the protocol.
- */
-function ArrayBufferClient(client, grip) {
- this._grip = grip;
- this._client = client;
- this.request = this._client.request;
-}
-ArrayBufferClient.prototype = {
- get actor() {
- return this._grip.actor;
- },
- get length() {
- return this._grip.length;
- },
- get _transport() {
- return this._client._transport;
- },
-
- valid: true,
-
- slice: DebuggerClient.requester({
- type: "slice",
- start: arg(0),
- count: arg(1)
- }),
-};
-
-/**
- * A LongStringClient provides a way to access "very long" strings from the
- * debugger server.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param grip Object
- * A pause-lifetime long string grip returned by the protocol.
- */
-function LongStringClient(client, grip) {
- this._grip = grip;
- this._client = client;
- this.request = this._client.request;
-}
-exports.LongStringClient = LongStringClient;
-
-LongStringClient.prototype = {
- get actor() {
- return this._grip.actor;
- },
- get length() {
- return this._grip.length;
- },
- get initial() {
- return this._grip.initial;
- },
- get _transport() {
- return this._client._transport;
- },
-
- valid: true,
-
- /**
- * Get the substring of this LongString from start to end.
- *
- * @param start Number
- * The starting index.
- * @param end Number
- * The ending index.
- * @param callback Function
- * The function called when we receive the substring.
- */
- substring: DebuggerClient.requester({
- type: "substring",
- start: arg(0),
- end: arg(1)
- }),
-};
-
-/**
- * A SourceClient provides a way to access the source text of a script.
- *
- * @param client ThreadClient
- * The thread client parent.
- * @param form Object
- * The form sent across the remote debugging protocol.
- */
-function SourceClient(client, form) {
- this._form = form;
- this._isBlackBoxed = form.isBlackBoxed;
- this._isPrettyPrinted = form.isPrettyPrinted;
- this._activeThread = client;
- this._client = client.client;
-}
-
-SourceClient.prototype = {
- get _transport() {
- return this._client._transport;
- },
- get isBlackBoxed() {
- return this._isBlackBoxed;
- },
- get isPrettyPrinted() {
- return this._isPrettyPrinted;
- },
- get actor() {
- return this._form.actor;
- },
- get request() {
- return this._client.request;
- },
- get url() {
- return this._form.url;
- },
-
- /**
- * Black box this SourceClient's source.
- *
- * @param callback Function
- * The callback function called when we receive the response from the server.
- */
- blackBox: DebuggerClient.requester({
- type: "blackbox"
- }, {
- after: function (response) {
- if (!response.error) {
- this._isBlackBoxed = true;
- if (this._activeThread) {
- this._activeThread.emit("blackboxchange", this);
- }
- }
- return response;
- }
- }),
-
- /**
- * Un-black box this SourceClient's source.
- *
- * @param callback Function
- * The callback function called when we receive the response from the server.
- */
- unblackBox: DebuggerClient.requester({
- type: "unblackbox"
- }, {
- after: function (response) {
- if (!response.error) {
- this._isBlackBoxed = false;
- if (this._activeThread) {
- this._activeThread.emit("blackboxchange", this);
- }
- }
- return response;
- }
- }),
-
- /**
- * Get Executable Lines from a source
- *
- * @param callback Function
- * The callback function called when we receive the response from the server.
- */
- getExecutableLines: function (cb = noop) {
- let packet = {
- to: this._form.actor,
- type: "getExecutableLines"
- };
-
- return this._client.request(packet).then(res => {
- cb(res.lines);
- return res.lines;
- });
- },
-
- /**
- * Get a long string grip for this SourceClient's source.
- */
- source: function (callback = noop) {
- let packet = {
- to: this._form.actor,
- type: "source"
- };
- return this._client.request(packet).then(response => {
- return this._onSourceResponse(response, callback);
- });
- },
-
- /**
- * Pretty print this source's text.
- */
- prettyPrint: function (indent, callback = noop) {
- const packet = {
- to: this._form.actor,
- type: "prettyPrint",
- indent
- };
- return this._client.request(packet).then(response => {
- if (!response.error) {
- this._isPrettyPrinted = true;
- this._activeThread._clearFrames();
- this._activeThread.emit("prettyprintchange", this);
- }
- return this._onSourceResponse(response, callback);
- });
- },
-
- /**
- * Stop pretty printing this source's text.
- */
- disablePrettyPrint: function (callback = noop) {
- const packet = {
- to: this._form.actor,
- type: "disablePrettyPrint"
- };
- return this._client.request(packet).then(response => {
- if (!response.error) {
- this._isPrettyPrinted = false;
- this._activeThread._clearFrames();
- this._activeThread.emit("prettyprintchange", this);
- }
- return this._onSourceResponse(response, callback);
- });
- },
-
- _onSourceResponse: function (response, callback) {
- if (response.error) {
- callback(response);
- return response;
- }
-
- if (typeof response.source === "string") {
- callback(response);
- return response;
- }
-
- let { contentType, source } = response;
- if (source.type === "arrayBuffer") {
- let arrayBuffer = this._activeThread.threadArrayBuffer(source);
- return arrayBuffer.slice(0, arrayBuffer.length).then(function (resp) {
- if (resp.error) {
- callback(resp);
- return resp;
- }
- // Keeping str as a string, ArrayBuffer/Uint8Array will not survive
- // setIn/mergeIn operations.
- const str = atob(resp.encoded);
- let newResponse = {
- source: {
- binary: str,
- toString: () => "[wasm]",
- },
- contentType,
- };
- callback(newResponse);
- return newResponse;
- });
- }
-
- let longString = this._activeThread.threadLongString(source);
- return longString.substring(0, longString.length).then(function (resp) {
- if (resp.error) {
- callback(resp);
- return resp;
- }
-
- let newResponse = {
- source: resp.substring,
- contentType: contentType
- };
- callback(newResponse);
- return newResponse;
- });
- },
-
- /**
- * Request to set a breakpoint in the specified location.
- *
- * @param object location
- * The location and condition of the breakpoint in
- * the form of { line[, column, condition] }.
- * @param function onResponse
- * Called with the thread's response.
- */
- setBreakpoint: function ({ line, column, condition, noSliding }, onResponse = noop) {
- // A helper function that sets the breakpoint.
- let doSetBreakpoint = callback => {
- let root = this._client.mainRoot;
- let location = {
- line,
- column,
- };
-
- let packet = {
- to: this.actor,
- type: "setBreakpoint",
- location,
- condition,
- noSliding,
- };
-
- // Backwards compatibility: send the breakpoint request to the
- // thread if the server doesn't support Debugger.Source actors.
- if (!root.traits.debuggerSourceActors) {
- packet.to = this._activeThread.actor;
- packet.location.url = this.url;
- }
-
- return this._client.request(packet).then(response => {
- // Ignoring errors, since the user may be setting a breakpoint in a
- // dead script that will reappear on a page reload.
- let bpClient;
- if (response.actor) {
- bpClient = new BreakpointClient(
- this._client,
- this,
- response.actor,
- location,
- root.traits.conditionalBreakpoints ? condition : undefined
- );
- }
- onResponse(response, bpClient);
- if (callback) {
- callback();
- }
- return [response, bpClient];
- });
- };
-
- // If the debuggee is paused, just set the breakpoint.
- if (this._activeThread.paused) {
- return doSetBreakpoint();
- }
- // Otherwise, force a pause in order to set the breakpoint.
- return this._activeThread.interrupt().then(response => {
- if (response.error) {
- // Can't set the breakpoint if pausing failed.
- onResponse(response);
- return response;
- }
-
- const { type, why } = response;
- const cleanUp = type == "paused" && why.type == "interrupted"
- ? () => this._activeThread.resume()
- : noop;
-
- return doSetBreakpoint(cleanUp);
- });
- }
-};
-
-/**
- * Breakpoint clients are used to remove breakpoints that are no longer used.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param sourceClient SourceClient
- * The source where this breakpoint exists
- * @param actor string
- * The actor ID for this breakpoint.
- * @param location object
- * The location of the breakpoint. This is an object with two properties:
- * url and line.
- * @param condition string
- * The conditional expression of the breakpoint
- */
-function BreakpointClient(client, sourceClient, actor, location, condition) {
- this._client = client;
- this._actor = actor;
- this.location = location;
- this.location.actor = sourceClient.actor;
- this.location.url = sourceClient.url;
- this.source = sourceClient;
- this.request = this._client.request;
-
- // The condition property should only exist if it's a truthy value
- if (condition) {
- this.condition = condition;
- }
-}
-
-BreakpointClient.prototype = {
-
- _actor: null,
- get actor() {
- return this._actor;
- },
- get _transport() {
- return this._client._transport;
- },
-
- /**
- * Remove the breakpoint from the server.
- */
- remove: DebuggerClient.requester({
- type: "delete"
- }),
-
- /**
- * Determines if this breakpoint has a condition
- */
- hasCondition: function () {
- let root = this._client.mainRoot;
- // XXX bug 990137: We will remove support for client-side handling of
- // conditional breakpoints
- if (root.traits.conditionalBreakpoints) {
- return "condition" in this;
- }
- return "conditionalExpression" in this;
- },
-
- /**
- * Get the condition of this breakpoint. Currently we have to
- * support locally emulated conditional breakpoints until the
- * debugger servers are updated (see bug 990137). We used a
- * different property when moving it server-side to ensure that we
- * are testing the right code.
- */
- getCondition: function () {
- let root = this._client.mainRoot;
- if (root.traits.conditionalBreakpoints) {
- return this.condition;
- }
- return this.conditionalExpression;
- },
-
- /**
- * Set the condition of this breakpoint
- */
- setCondition: function (gThreadClient, condition) {
- let root = this._client.mainRoot;
- let deferred = promise.defer();
-
- if (root.traits.conditionalBreakpoints) {
- let info = {
- line: this.location.line,
- column: this.location.column,
- condition: condition
- };
-
- // Remove the current breakpoint and add a new one with the
- // condition.
- this.remove(response => {
- if (response && response.error) {
- deferred.reject(response);
- return;
- }
-
- this.source.setBreakpoint(info, (resp, newBreakpoint) => {
- if (resp && resp.error) {
- deferred.reject(resp);
- } else {
- deferred.resolve(newBreakpoint);
- }
- });
- });
- } else {
- // The property shouldn't even exist if the condition is blank
- if (condition === "") {
- delete this.conditionalExpression;
- } else {
- this.conditionalExpression = condition;
- }
- deferred.resolve(this);
- }
-
- return deferred.promise;
- }
-};
-
-eventSource(BreakpointClient.prototype);
-
-/**
- * Environment clients are used to manipulate the lexical environment actors.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param form Object
- * The form sent across the remote debugging protocol.
- */
-function EnvironmentClient(client, form) {
- this._client = client;
- this._form = form;
- this.request = this._client.request;
-}
-exports.EnvironmentClient = EnvironmentClient;
-
-EnvironmentClient.prototype = {
-
- get actor() {
- return this._form.actor;
- },
- get _transport() {
- return this._client._transport;
- },
-
- /**
- * Fetches the bindings introduced by this lexical environment.
- */
- getBindings: DebuggerClient.requester({
- type: "bindings"
- }),
-
- /**
- * Changes the value of the identifier whose name is name (a string) to that
- * represented by value (a grip).
- */
- assign: DebuggerClient.requester({
- type: "assign",
- name: arg(0),
- value: arg(1)
- })
-};
-
-eventSource(EnvironmentClient.prototype);
--- a/devtools/shared/client/moz.build
+++ b/devtools/shared/client/moz.build
@@ -1,10 +1,27 @@
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
DevToolsModules(
+ 'addon-client.js',
+ 'array-buffer-client.js',
+ 'breakpoint-client.js',
'connection-manager.js',
+ 'constants.js',
+ 'debugger-client.js',
+ 'environment-client.js',
+ 'event-source.js',
+ 'long-string-client.js',
'main.js',
-)
+ 'object-client.js',
+ 'property-iterator-client.js',
+ 'root-client.js',
+ 'source-client.js',
+ 'symbol-iterator-client.js',
+ 'tab-client.js',
+ 'thread-client.js',
+ 'trace-client.js',
+ 'worker-client.js',
+)
\ No newline at end of file
copy from devtools/shared/client/main.js
copy to devtools/shared/client/object-client.js
--- a/devtools/shared/client/main.js
+++ b/devtools/shared/client/object-client.js
@@ -1,2536 +1,31 @@
-/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
-/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
-const { Ci, Cu } = require("chrome");
-const DevToolsUtils = require("devtools/shared/DevToolsUtils");
-const { getStack, callFunctionWithAsyncStack } = require("devtools/shared/platform/stack");
-
-const promise = Cu.import("resource://devtools/shared/deprecated-sync-thenables.js", {}).Promise;
-
-loader.lazyRequireGetter(this, "EventEmitter", "devtools/shared/event-emitter");
-loader.lazyRequireGetter(this, "WebConsoleClient", "devtools/shared/webconsole/client", true);
-loader.lazyRequireGetter(this, "DebuggerSocket", "devtools/shared/security/socket", true);
-loader.lazyRequireGetter(this, "Authentication", "devtools/shared/security/auth");
-
-const noop = () => {};
-
-/**
- * TODO: Get rid of this API in favor of EventTarget (bug 1042642)
- *
- * Add simple event notification to a prototype object. Any object that has
- * some use for event notifications or the observer pattern in general can be
- * augmented with the necessary facilities by passing its prototype to this
- * function.
- *
- * @param proto object
- * The prototype object that will be modified.
- */
-function eventSource(proto) {
- /**
- * Add a listener to the event source for a given event.
- *
- * @param name string
- * The event to listen for.
- * @param listener function
- * Called when the event is fired. If the same listener
- * is added more than once, it will be called once per
- * addListener call.
- */
- proto.addListener = function (name, listener) {
- if (typeof listener != "function") {
- throw TypeError("Listeners must be functions.");
- }
-
- if (!this._listeners) {
- this._listeners = {};
- }
-
- this._getListeners(name).push(listener);
- };
-
- /**
- * Add a listener to the event source for a given event. The
- * listener will be removed after it is called for the first time.
- *
- * @param name string
- * The event to listen for.
- * @param listener function
- * Called when the event is fired.
- */
- proto.addOneTimeListener = function (name, listener) {
- let l = (...args) => {
- this.removeListener(name, l);
- listener.apply(null, args);
- };
- this.addListener(name, l);
- };
-
- /**
- * Remove a listener from the event source previously added with
- * addListener().
- *
- * @param name string
- * The event name used during addListener to add the listener.
- * @param listener function
- * The callback to remove. If addListener was called multiple
- * times, all instances will be removed.
- */
- proto.removeListener = function (name, listener) {
- if (!this._listeners || (listener && !this._listeners[name])) {
- return;
- }
-
- if (!listener) {
- this._listeners[name] = [];
- } else {
- this._listeners[name] =
- this._listeners[name].filter(l => l != listener);
- }
- };
-
- /**
- * Returns the listeners for the specified event name. If none are defined it
- * initializes an empty list and returns that.
- *
- * @param name string
- * The event name.
- */
- proto._getListeners = function (name) {
- if (name in this._listeners) {
- return this._listeners[name];
- }
- this._listeners[name] = [];
- return this._listeners[name];
- };
-
- /**
- * Notify listeners of an event.
- *
- * @param name string
- * The event to fire.
- * @param arguments
- * All arguments will be passed along to the listeners,
- * including the name argument.
- */
- proto.emit = function () {
- if (!this._listeners) {
- return;
- }
-
- let name = arguments[0];
- let listeners = this._getListeners(name).slice(0);
-
- for (let listener of listeners) {
- try {
- listener.apply(null, arguments);
- } catch (e) {
- // Prevent a bad listener from interfering with the others.
- DevToolsUtils.reportException("notify event '" + name + "'", e);
- }
- }
- };
-}
-
-/**
- * Set of protocol messages that affect thread state, and the
- * state the actor is in after each message.
- */
-const ThreadStateTypes = {
- "paused": "paused",
- "resumed": "attached",
- "detached": "detached",
- "running": "attached"
-};
-
-/**
- * Set of protocol messages that are sent by the server without a prior request
- * by the client.
- */
-const UnsolicitedNotifications = {
- "consoleAPICall": "consoleAPICall",
- "eventNotification": "eventNotification",
- "fileActivity": "fileActivity",
- "lastPrivateContextExited": "lastPrivateContextExited",
- "logMessage": "logMessage",
- "networkEvent": "networkEvent",
- "networkEventUpdate": "networkEventUpdate",
- "newGlobal": "newGlobal",
- "newScript": "newScript",
- "tabDetached": "tabDetached",
- "tabListChanged": "tabListChanged",
- "reflowActivity": "reflowActivity",
- "addonListChanged": "addonListChanged",
- "workerListChanged": "workerListChanged",
- "serviceWorkerRegistrationListChanged": "serviceWorkerRegistrationList",
- "tabNavigated": "tabNavigated",
- "frameUpdate": "frameUpdate",
- "pageError": "pageError",
- "documentLoad": "documentLoad",
- "enteredFrame": "enteredFrame",
- "exitedFrame": "exitedFrame",
- "appOpen": "appOpen",
- "appClose": "appClose",
- "appInstall": "appInstall",
- "appUninstall": "appUninstall",
- "evaluationResult": "evaluationResult",
- "newSource": "newSource",
- "updatedSource": "updatedSource",
- "inspectObject": "inspectObject"
-};
-
-/**
- * Set of pause types that are sent by the server and not as an immediate
- * response to a client request.
- */
-const UnsolicitedPauses = {
- "resumeLimit": "resumeLimit",
- "debuggerStatement": "debuggerStatement",
- "breakpoint": "breakpoint",
- "DOMEvent": "DOMEvent",
- "watchpoint": "watchpoint",
- "exception": "exception"
-};
-
-/**
- * Creates a client for the remote debugging protocol server. This client
- * provides the means to communicate with the server and exchange the messages
- * required by the protocol in a traditional JavaScript API.
- */
-const DebuggerClient = exports.DebuggerClient = function (transport) {
- this._transport = transport;
- this._transport.hooks = this;
-
- // Map actor ID to client instance for each actor type.
- this._clients = new Map();
-
- this._pendingRequests = new Map();
- this._activeRequests = new Map();
- this._eventsEnabled = true;
-
- this.traits = {};
-
- this.request = this.request.bind(this);
- this.localTransport = this._transport.onOutputStreamReady === undefined;
-
- /*
- * As the first thing on the connection, expect a greeting packet from
- * the connection's root actor.
- */
- this.mainRoot = null;
- this.expectReply("root", (packet) => {
- this.mainRoot = new RootClient(this, packet);
- this.emit("connected", packet.applicationType, packet.traits);
- });
-};
-
-/**
- * A declarative helper for defining methods that send requests to the server.
- *
- * @param packetSkeleton
- * The form of the packet to send. Can specify fields to be filled from
- * the parameters by using the |arg| function.
- * @param before
- * The function to call before sending the packet. Is passed the packet,
- * and the return value is used as the new packet. The |this| context is
- * the instance of the client object we are defining a method for.
- * @param after
- * The function to call after the response is received. It is passed the
- * response, and the return value is considered the new response that
- * will be passed to the callback. The |this| context is the instance of
- * the client object we are defining a method for.
- * @return Request
- * The `Request` object that is a Promise object and resolves once
- * we receive the response. (See request method for more details)
- */
-DebuggerClient.requester = function (packetSkeleton, config = {}) {
- let { before, after } = config;
- return DevToolsUtils.makeInfallible(function (...args) {
- let outgoingPacket = {
- to: packetSkeleton.to || this.actor
- };
-
- let maxPosition = -1;
- for (let k of Object.keys(packetSkeleton)) {
- if (packetSkeleton[k] instanceof DebuggerClient.Argument) {
- let { position } = packetSkeleton[k];
- outgoingPacket[k] = packetSkeleton[k].getArgument(args);
- maxPosition = Math.max(position, maxPosition);
- } else {
- outgoingPacket[k] = packetSkeleton[k];
- }
- }
-
- if (before) {
- outgoingPacket = before.call(this, outgoingPacket);
- }
-
- return this.request(outgoingPacket, DevToolsUtils.makeInfallible((response) => {
- if (after) {
- let { from } = response;
- response = after.call(this, response);
- if (!response.from) {
- response.from = from;
- }
- }
-
- // The callback is always the last parameter.
- let thisCallback = args[maxPosition + 1];
- if (thisCallback) {
- thisCallback(response);
- }
- return response;
- }, "DebuggerClient.requester request callback"));
- }, "DebuggerClient.requester");
-};
-
-function arg(pos) {
- return new DebuggerClient.Argument(pos);
-}
-
-DebuggerClient.Argument = function (position) {
- this.position = position;
-};
-
-DebuggerClient.Argument.prototype.getArgument = function (params) {
- if (!(this.position in params)) {
- throw new Error("Bad index into params: " + this.position);
- }
- return params[this.position];
-};
-
-// Expose these to save callers the trouble of importing DebuggerSocket
-DebuggerClient.socketConnect = function (options) {
- // Defined here instead of just copying the function to allow lazy-load
- return DebuggerSocket.connect(options);
-};
-DevToolsUtils.defineLazyGetter(DebuggerClient, "Authenticators", () => {
- return Authentication.Authenticators;
-});
-DevToolsUtils.defineLazyGetter(DebuggerClient, "AuthenticationResult", () => {
- return Authentication.AuthenticationResult;
-});
-
-DebuggerClient.prototype = {
- /**
- * Connect to the server and start exchanging protocol messages.
- *
- * @param onConnected function
- * If specified, will be called when the greeting packet is
- * received from the debugging server.
- *
- * @return Promise
- * Resolves once connected with an array whose first element
- * is the application type, by default "browser", and the second
- * element is the traits object (help figure out the features
- * and behaviors of the server we connect to. See RootActor).
- */
- connect: function (onConnected) {
- let deferred = promise.defer();
- this.emit("connect");
-
- // Also emit the event on the |DebuggerClient| object (not on the instance),
- // so it's possible to track all instances.
- EventEmitter.emit(DebuggerClient, "connect", this);
-
- this.addOneTimeListener("connected", (name, applicationType, traits) => {
- this.traits = traits;
- if (onConnected) {
- onConnected(applicationType, traits);
- }
- deferred.resolve([applicationType, traits]);
- });
-
- this._transport.ready();
- return deferred.promise;
- },
-
- /**
- * Shut down communication with the debugging server.
- *
- * @param onClosed function
- * If specified, will be called when the debugging connection
- * has been closed. This parameter is deprecated - please use
- * the returned Promise.
- * @return Promise
- * Resolves after the underlying transport is closed.
- */
- close: function (onClosed) {
- let deferred = promise.defer();
- if (onClosed) {
- deferred.promise.then(onClosed);
- }
-
- // Disable detach event notifications, because event handlers will be in a
- // cleared scope by the time they run.
- this._eventsEnabled = false;
-
- let cleanup = () => {
- this._transport.close();
- this._transport = null;
- };
-
- // If the connection is already closed,
- // there is no need to detach client
- // as we won't be able to send any message.
- if (this._closed) {
- cleanup();
- deferred.resolve();
- return deferred.promise;
- }
-
- this.addOneTimeListener("closed", deferred.resolve);
-
- // Call each client's `detach` method by calling
- // lastly registered ones first to give a chance
- // to detach child clients first.
- let clients = [...this._clients.values()];
- this._clients.clear();
- const detachClients = () => {
- let client = clients.pop();
- if (!client) {
- // All clients detached.
- cleanup();
- return;
- }
- if (client.detach) {
- client.detach(detachClients);
- return;
- }
- detachClients();
- };
- detachClients();
-
- return deferred.promise;
- },
-
- /*
- * This function exists only to preserve DebuggerClient's interface;
- * new code should say 'client.mainRoot.listTabs()'.
- */
- listTabs: function (onResponse) {
- return this.mainRoot.listTabs(onResponse);
- },
-
- /*
- * This function exists only to preserve DebuggerClient's interface;
- * new code should say 'client.mainRoot.listAddons()'.
- */
- listAddons: function (onResponse) {
- return this.mainRoot.listAddons(onResponse);
- },
-
- getTab: function (filter) {
- return this.mainRoot.getTab(filter);
- },
-
- /**
- * Attach to a tab actor.
- *
- * @param string tabActor
- * The actor ID for the tab to attach.
- * @param function onResponse
- * Called with the response packet and a TabClient
- * (which will be undefined on error).
- */
- attachTab: function (tabActor, onResponse = noop) {
- if (this._clients.has(tabActor)) {
- let cachedTab = this._clients.get(tabActor);
- let cachedResponse = {
- cacheDisabled: cachedTab.cacheDisabled,
- javascriptEnabled: cachedTab.javascriptEnabled,
- traits: cachedTab.traits,
- };
- DevToolsUtils.executeSoon(() => onResponse(cachedResponse, cachedTab));
- return promise.resolve([cachedResponse, cachedTab]);
- }
-
- let packet = {
- to: tabActor,
- type: "attach"
- };
- return this.request(packet).then(response => {
- let tabClient;
- if (!response.error) {
- tabClient = new TabClient(this, response);
- this.registerClient(tabClient);
- }
- onResponse(response, tabClient);
- return [response, tabClient];
- });
- },
-
- attachWorker: function (workerActor, onResponse = noop) {
- let workerClient = this._clients.get(workerActor);
- if (workerClient !== undefined) {
- let response = {
- from: workerClient.actor,
- type: "attached",
- url: workerClient.url
- };
- DevToolsUtils.executeSoon(() => onResponse(response, workerClient));
- return promise.resolve([response, workerClient]);
- }
-
- return this.request({ to: workerActor, type: "attach" }).then(response => {
- if (response.error) {
- onResponse(response, null);
- return [response, null];
- }
-
- workerClient = new WorkerClient(this, response);
- this.registerClient(workerClient);
- onResponse(response, workerClient);
- return [response, workerClient];
- });
- },
-
- /**
- * Attach to an addon actor.
- *
- * @param string addonActor
- * The actor ID for the addon to attach.
- * @param function onResponse
- * Called with the response packet and a AddonClient
- * (which will be undefined on error).
- */
- attachAddon: function (addonActor, onResponse = noop) {
- let packet = {
- to: addonActor,
- type: "attach"
- };
- return this.request(packet).then(response => {
- let addonClient;
- if (!response.error) {
- addonClient = new AddonClient(this, addonActor);
- this.registerClient(addonClient);
- this.activeAddon = addonClient;
- }
- onResponse(response, addonClient);
- return [response, addonClient];
- });
- },
-
- /**
- * Attach to a Web Console actor.
- *
- * @param string consoleActor
- * The ID for the console actor to attach to.
- * @param array listeners
- * The console listeners you want to start.
- * @param function onResponse
- * Called with the response packet and a WebConsoleClient
- * instance (which will be undefined on error).
- */
- attachConsole:
- function (consoleActor, listeners, onResponse = noop) {
- let packet = {
- to: consoleActor,
- type: "startListeners",
- listeners: listeners,
- };
-
- return this.request(packet).then(response => {
- let consoleClient;
- if (!response.error) {
- if (this._clients.has(consoleActor)) {
- consoleClient = this._clients.get(consoleActor);
- } else {
- consoleClient = new WebConsoleClient(this, response);
- this.registerClient(consoleClient);
- }
- }
- onResponse(response, consoleClient);
- return [response, consoleClient];
- });
- },
-
- /**
- * Attach to a global-scoped thread actor for chrome debugging.
- *
- * @param string threadActor
- * The actor ID for the thread to attach.
- * @param function onResponse
- * Called with the response packet and a ThreadClient
- * (which will be undefined on error).
- * @param object options
- * Configuration options.
- * - useSourceMaps: whether to use source maps or not.
- */
- attachThread: function (threadActor, onResponse = noop, options = {}) {
- if (this._clients.has(threadActor)) {
- let client = this._clients.get(threadActor);
- DevToolsUtils.executeSoon(() => onResponse({}, client));
- return promise.resolve([{}, client]);
- }
-
- let packet = {
- to: threadActor,
- type: "attach",
- options,
- };
- return this.request(packet).then(response => {
- let threadClient;
- if (!response.error) {
- threadClient = new ThreadClient(this, threadActor);
- this.registerClient(threadClient);
- }
- onResponse(response, threadClient);
- return [response, threadClient];
- });
- },
-
- /**
- * Attach to a trace actor.
- *
- * @param string traceActor
- * The actor ID for the tracer to attach.
- * @param function onResponse
- * Called with the response packet and a TraceClient
- * (which will be undefined on error).
- */
- attachTracer: function (traceActor, onResponse = noop) {
- if (this._clients.has(traceActor)) {
- let client = this._clients.get(traceActor);
- DevToolsUtils.executeSoon(() => onResponse({}, client));
- return promise.resolve([{}, client]);
- }
-
- let packet = {
- to: traceActor,
- type: "attach"
- };
- return this.request(packet).then(response => {
- let traceClient;
- if (!response.error) {
- traceClient = new TraceClient(this, traceActor);
- this.registerClient(traceClient);
- }
- onResponse(response, traceClient);
- return [response, traceClient];
- });
- },
-
- /**
- * Fetch the ChromeActor for the main process or ChildProcessActor for a
- * a given child process ID.
- *
- * @param number id
- * The ID for the process to attach (returned by `listProcesses`).
- * Connected to the main process if omitted, or is 0.
- */
- getProcess: function (id) {
- let packet = {
- to: "root",
- type: "getProcess"
- };
- if (typeof (id) == "number") {
- packet.id = id;
- }
- return this.request(packet);
- },
-
- /**
- * Release an object actor.
- *
- * @param string actor
- * The actor ID to send the request to.
- * @param onResponse function
- * If specified, will be called with the response packet when
- * debugging server responds.
- */
- release: DebuggerClient.requester({
- to: arg(0),
- type: "release"
- }),
-
- /**
- * Send a request to the debugging server.
- *
- * @param packet object
- * A JSON packet to send to the debugging server.
- * @param onResponse function
- * If specified, will be called with the JSON response packet when
- * debugging server responds.
- * @return Request
- * This object emits a number of events to allow you to respond to
- * different parts of the request lifecycle.
- * It is also a Promise object, with a `then` method, that is resolved
- * whenever a JSON or a Bulk response is received; and is rejected
- * if the response is an error.
- * Note: This return value can be ignored if you are using JSON alone,
- * because the callback provided in |onResponse| will be bound to the
- * "json-reply" event automatically.
- *
- * Events emitted:
- * * json-reply: The server replied with a JSON packet, which is
- * passed as event data.
- * * bulk-reply: The server replied with bulk data, which you can read
- * using the event data object containing:
- * * actor: Name of actor that received the packet
- * * type: Name of actor's method that was called on receipt
- * * length: Size of the data to be read
- * * stream: This input stream should only be used directly if you
- * can ensure that you will read exactly |length| bytes
- * and will not close the stream when reading is complete
- * * done: If you use the stream directly (instead of |copyTo|
- * below), you must signal completion by resolving /
- * rejecting this deferred. If it's rejected, the
- * transport will be closed. If an Error is supplied as a
- * rejection value, it will be logged via |dumpn|. If you
- * do use |copyTo|, resolving is taken care of for you
- * when copying completes.
- * * copyTo: A helper function for getting your data out of the
- * stream that meets the stream handling requirements
- * above, and has the following signature:
- * @param output nsIAsyncOutputStream
- * The stream to copy to.
- * @return Promise
- * The promise is resolved when copying completes or
- * rejected if any (unexpected) errors occur.
- * This object also emits "progress" events for each chunk
- * that is copied. See stream-utils.js.
- */
- request: function (packet, onResponse) {
- if (!this.mainRoot) {
- throw Error("Have not yet received a hello packet from the server.");
- }
- let type = packet.type || "";
- if (!packet.to) {
- throw Error("'" + type + "' request packet has no destination.");
- }
-
- // The onResponse callback might modify the response, so we need to call
- // it and resolve the promise with its result if it's truthy.
- const safeOnResponse = response => {
- if (!onResponse) {
- return response;
- }
- return onResponse(response) || response;
- };
-
- if (this._closed) {
- let msg = "'" + type + "' request packet to " +
- "'" + packet.to + "' " +
- "can't be sent as the connection is closed.";
- let resp = { error: "connectionClosed", message: msg };
- return promise.reject(safeOnResponse(resp));
- }
-
- let request = new Request(packet);
- request.format = "json";
- request.stack = getStack();
-
- // Implement a Promise like API on the returned object
- // that resolves/rejects on request response
- let deferred = promise.defer();
- function listenerJson(resp) {
- removeRequestListeners();
- if (resp.error) {
- deferred.reject(safeOnResponse(resp));
- } else {
- deferred.resolve(safeOnResponse(resp));
- }
- }
- function listenerBulk(resp) {
- removeRequestListeners();
- deferred.resolve(safeOnResponse(resp));
- }
-
- const removeRequestListeners = () => {
- request.off("json-reply", listenerJson);
- request.off("bulk-reply", listenerBulk);
- };
-
- request.on("json-reply", listenerJson);
- request.on("bulk-reply", listenerBulk);
-
- this._sendOrQueueRequest(request);
- request.then = deferred.promise.then.bind(deferred.promise);
-
- return request;
- },
-
- /**
- * Transmit streaming data via a bulk request.
- *
- * This method initiates the bulk send process by queuing up the header data.
- * The caller receives eventual access to a stream for writing.
- *
- * Since this opens up more options for how the server might respond (it could
- * send back either JSON or bulk data), and the returned Request object emits
- * events for different stages of the request process that you may want to
- * react to.
- *
- * @param request Object
- * This is modeled after the format of JSON packets above, but does not
- * actually contain the data, but is instead just a routing header:
- * * actor: Name of actor that will receive the packet
- * * type: Name of actor's method that should be called on receipt
- * * length: Size of the data to be sent
- * @return Request
- * This object emits a number of events to allow you to respond to
- * different parts of the request lifecycle.
- *
- * Events emitted:
- * * bulk-send-ready: Ready to send bulk data to the server, using the
- * event data object containing:
- * * stream: This output stream should only be used directly if
- * you can ensure that you will write exactly |length|
- * bytes and will not close the stream when writing is
- * complete
- * * done: If you use the stream directly (instead of |copyFrom|
- * below), you must signal completion by resolving /
- * rejecting this deferred. If it's rejected, the
- * transport will be closed. If an Error is supplied as
- * a rejection value, it will be logged via |dumpn|. If
- * you do use |copyFrom|, resolving is taken care of for
- * you when copying completes.
- * * copyFrom: A helper function for getting your data onto the
- * stream that meets the stream handling requirements
- * above, and has the following signature:
- * @param input nsIAsyncInputStream
- * The stream to copy from.
- * @return Promise
- * The promise is resolved when copying completes or
- * rejected if any (unexpected) errors occur.
- * This object also emits "progress" events for each chunk
- * that is copied. See stream-utils.js.
- * * json-reply: The server replied with a JSON packet, which is
- * passed as event data.
- * * bulk-reply: The server replied with bulk data, which you can read
- * using the event data object containing:
- * * actor: Name of actor that received the packet
- * * type: Name of actor's method that was called on receipt
- * * length: Size of the data to be read
- * * stream: This input stream should only be used directly if you
- * can ensure that you will read exactly |length| bytes
- * and will not close the stream when reading is complete
- * * done: If you use the stream directly (instead of |copyTo|
- * below), you must signal completion by resolving /
- * rejecting this deferred. If it's rejected, the
- * transport will be closed. If an Error is supplied as a
- * rejection value, it will be logged via |dumpn|. If you
- * do use |copyTo|, resolving is taken care of for you
- * when copying completes.
- * * copyTo: A helper function for getting your data out of the
- * stream that meets the stream handling requirements
- * above, and has the following signature:
- * @param output nsIAsyncOutputStream
- * The stream to copy to.
- * @return Promise
- * The promise is resolved when copying completes or
- * rejected if any (unexpected) errors occur.
- * This object also emits "progress" events for each chunk
- * that is copied. See stream-utils.js.
- */
- startBulkRequest: function (request) {
- if (!this.traits.bulk) {
- throw Error("Server doesn't support bulk transfers");
- }
- if (!this.mainRoot) {
- throw Error("Have not yet received a hello packet from the server.");
- }
- if (!request.type) {
- throw Error("Bulk packet is missing the required 'type' field.");
- }
- if (!request.actor) {
- throw Error("'" + request.type + "' bulk packet has no destination.");
- }
- if (!request.length) {
- throw Error("'" + request.type + "' bulk packet has no length.");
- }
-
- request = new Request(request);
- request.format = "bulk";
-
- this._sendOrQueueRequest(request);
-
- return request;
- },
-
- /**
- * If a new request can be sent immediately, do so. Otherwise, queue it.
- */
- _sendOrQueueRequest(request) {
- let actor = request.actor;
- if (!this._activeRequests.has(actor)) {
- this._sendRequest(request);
- } else {
- this._queueRequest(request);
- }
- },
-
- /**
- * Send a request.
- * @throws Error if there is already an active request in flight for the same
- * actor.
- */
- _sendRequest(request) {
- let actor = request.actor;
- this.expectReply(actor, request);
-
- if (request.format === "json") {
- this._transport.send(request.request);
- return;
- }
-
- this._transport.startBulkSend(request.request).then((...args) => {
- request.emit("bulk-send-ready", ...args);
- });
- },
-
- /**
- * Queue a request to be sent later. Queues are only drained when an in
- * flight request to a given actor completes.
- */
- _queueRequest(request) {
- let actor = request.actor;
- let queue = this._pendingRequests.get(actor) || [];
- queue.push(request);
- this._pendingRequests.set(actor, queue);
- },
-
- /**
- * Attempt the next request to a given actor (if any).
- */
- _attemptNextRequest(actor) {
- if (this._activeRequests.has(actor)) {
- return;
- }
- let queue = this._pendingRequests.get(actor);
- if (!queue) {
- return;
- }
- let request = queue.shift();
- if (queue.length === 0) {
- this._pendingRequests.delete(actor);
- }
- this._sendRequest(request);
- },
-
- /**
- * Arrange to hand the next reply from |actor| to the handler bound to
- * |request|.
- *
- * DebuggerClient.prototype.request / startBulkRequest usually takes care of
- * establishing the handler for a given request, but in rare cases (well,
- * greetings from new root actors, is the only case at the moment) we must be
- * prepared for a "reply" that doesn't correspond to any request we sent.
- */
- expectReply: function (actor, request) {
- if (this._activeRequests.has(actor)) {
- throw Error("clashing handlers for next reply from " + actor);
- }
-
- // If a handler is passed directly (as it is with the handler for the root
- // actor greeting), create a dummy request to bind this to.
- if (typeof request === "function") {
- let handler = request;
- request = new Request();
- request.on("json-reply", handler);
- }
-
- this._activeRequests.set(actor, request);
- },
-
- // Transport hooks.
-
- /**
- * Called by DebuggerTransport to dispatch incoming packets as appropriate.
- *
- * @param packet object
- * The incoming packet.
- */
- onPacket: function (packet) {
- if (!packet.from) {
- DevToolsUtils.reportException(
- "onPacket",
- new Error("Server did not specify an actor, dropping packet: " +
- JSON.stringify(packet)));
- return;
- }
-
- // If we have a registered Front for this actor, let it handle the packet
- // and skip all the rest of this unpleasantness.
- let front = this.getActor(packet.from);
- if (front) {
- front.onPacket(packet);
- return;
- }
-
- // Check for "forwardingCancelled" here instead of using a client to handle it.
- // This is necessary because we might receive this event while the client is closing,
- // and the clients have already been removed by that point.
- if (this.mainRoot &&
- packet.from == this.mainRoot.actor &&
- packet.type == "forwardingCancelled") {
- this.purgeRequests(packet.prefix);
- return;
- }
-
- if (this._clients.has(packet.from) && packet.type) {
- let client = this._clients.get(packet.from);
- let type = packet.type;
- if (client.events.indexOf(type) != -1) {
- client.emit(type, packet);
- // we ignore the rest, as the client is expected to handle this packet.
- return;
- }
- }
-
- let activeRequest;
- // See if we have a handler function waiting for a reply from this
- // actor. (Don't count unsolicited notifications or pauses as
- // replies.)
- if (this._activeRequests.has(packet.from) &&
- !(packet.type in UnsolicitedNotifications) &&
- !(packet.type == ThreadStateTypes.paused &&
- packet.why.type in UnsolicitedPauses)) {
- activeRequest = this._activeRequests.get(packet.from);
- this._activeRequests.delete(packet.from);
- }
-
- // If there is a subsequent request for the same actor, hand it off to the
- // transport. Delivery of packets on the other end is always async, even
- // in the local transport case.
- this._attemptNextRequest(packet.from);
-
- // Packets that indicate thread state changes get special treatment.
- if (packet.type in ThreadStateTypes &&
- this._clients.has(packet.from) &&
- typeof this._clients.get(packet.from)._onThreadState == "function") {
- this._clients.get(packet.from)._onThreadState(packet);
- }
-
- // TODO: Bug 1151156 - Remove once Gecko 40 is on b2g-stable.
- if (!this.traits.noNeedToFakeResumptionOnNavigation) {
- // On navigation the server resumes, so the client must resume as well.
- // We achieve that by generating a fake resumption packet that triggers
- // the client's thread state change listeners.
- if (packet.type == UnsolicitedNotifications.tabNavigated &&
- this._clients.has(packet.from) &&
- this._clients.get(packet.from).thread) {
- let thread = this._clients.get(packet.from).thread;
- let resumption = { from: thread._actor, type: "resumed" };
- thread._onThreadState(resumption);
- }
- }
-
- // Only try to notify listeners on events, not responses to requests
- // that lack a packet type.
- if (packet.type) {
- this.emit(packet.type, packet);
- }
-
- if (activeRequest) {
- let emitReply = () => activeRequest.emit("json-reply", packet);
- if (activeRequest.stack) {
- callFunctionWithAsyncStack(emitReply, activeRequest.stack,
- "DevTools RDP");
- } else {
- emitReply();
- }
- }
- },
-
- /**
- * Called by the DebuggerTransport to dispatch incoming bulk packets as
- * appropriate.
- *
- * @param packet object
- * The incoming packet, which contains:
- * * actor: Name of actor that will receive the packet
- * * type: Name of actor's method that should be called on receipt
- * * length: Size of the data to be read
- * * stream: This input stream should only be used directly if you can
- * ensure that you will read exactly |length| bytes and will
- * not close the stream when reading is complete
- * * done: If you use the stream directly (instead of |copyTo|
- * below), you must signal completion by resolving /
- * rejecting this deferred. If it's rejected, the transport
- * will be closed. If an Error is supplied as a rejection
- * value, it will be logged via |dumpn|. If you do use
- * |copyTo|, resolving is taken care of for you when copying
- * completes.
- * * copyTo: A helper function for getting your data out of the stream
- * that meets the stream handling requirements above, and has
- * the following signature:
- * @param output nsIAsyncOutputStream
- * The stream to copy to.
- * @return Promise
- * The promise is resolved when copying completes or rejected
- * if any (unexpected) errors occur.
- * This object also emits "progress" events for each chunk
- * that is copied. See stream-utils.js.
- */
- onBulkPacket: function (packet) {
- let { actor } = packet;
-
- if (!actor) {
- DevToolsUtils.reportException(
- "onBulkPacket",
- new Error("Server did not specify an actor, dropping bulk packet: " +
- JSON.stringify(packet)));
- return;
- }
-
- // See if we have a handler function waiting for a reply from this
- // actor.
- if (!this._activeRequests.has(actor)) {
- return;
- }
-
- let activeRequest = this._activeRequests.get(actor);
- this._activeRequests.delete(actor);
-
- // If there is a subsequent request for the same actor, hand it off to the
- // transport. Delivery of packets on the other end is always async, even
- // in the local transport case.
- this._attemptNextRequest(actor);
-
- activeRequest.emit("bulk-reply", packet);
- },
-
- /**
- * Called by DebuggerTransport when the underlying stream is closed.
- *
- * @param status nsresult
- * The status code that corresponds to the reason for closing
- * the stream.
- */
- onClosed: function () {
- this._closed = true;
- this.emit("closed");
-
- this.purgeRequests();
-
- // The |_pools| array on the client-side currently is used only by
- // protocol.js to store active fronts, mirroring the actor pools found in
- // the server. So, read all usages of "pool" as "protocol.js front".
- //
- // In the normal case where we shutdown cleanly, the toolbox tells each tool
- // to close, and they each call |destroy| on any fronts they were using.
- // When |destroy| or |cleanup| is called on a protocol.js front, it also
- // removes itself from the |_pools| array. Once the toolbox has shutdown,
- // the connection is closed, and we reach here. All fronts (should have
- // been) |destroy|ed, so |_pools| should empty.
- //
- // If the connection instead aborts unexpectedly, we may end up here with
- // all fronts used during the life of the connection. So, we call |cleanup|
- // on them clear their state, reject pending requests, and remove themselves
- // from |_pools|. This saves the toolbox from hanging indefinitely, in case
- // it waits for some server response before shutdown that will now never
- // arrive.
- for (let pool of this._pools) {
- pool.cleanup();
- }
- },
-
- /**
- * Purge pending and active requests in this client.
- *
- * @param prefix string (optional)
- * If a prefix is given, only requests for actor IDs that start with the prefix
- * will be cleaned up. This is useful when forwarding of a portion of requests
- * is cancelled on the server.
- */
- purgeRequests(prefix = "") {
- let reject = function (type, request) {
- // Server can send packets on its own and client only pass a callback
- // to expectReply, so that there is no request object.
- let msg;
- if (request.request) {
- msg = "'" + request.request.type + "' " + type + " request packet" +
- " to '" + request.actor + "' " +
- "can't be sent as the connection just closed.";
- } else {
- msg = "server side packet can't be received as the connection just closed.";
- }
- let packet = { error: "connectionClosed", message: msg };
- request.emit("json-reply", packet);
- };
-
- let pendingRequestsToReject = [];
- this._pendingRequests.forEach((requests, actor) => {
- if (!actor.startsWith(prefix)) {
- return;
- }
- this._pendingRequests.delete(actor);
- pendingRequestsToReject = pendingRequestsToReject.concat(requests);
- });
- pendingRequestsToReject.forEach(request => reject("pending", request));
-
- let activeRequestsToReject = [];
- this._activeRequests.forEach((request, actor) => {
- if (!actor.startsWith(prefix)) {
- return;
- }
- this._activeRequests.delete(actor);
- activeRequestsToReject = activeRequestsToReject.concat(request);
- });
- activeRequestsToReject.forEach(request => reject("active", request));
- },
-
- /**
- * Search for all requests in process for this client, including those made via
- * protocol.js and wait all of them to complete. Since the requests seen when this is
- * first called may in turn trigger more requests, we keep recursing through this
- * function until there is no more activity.
- *
- * This is a fairly heavy weight process, so it's only meant to be used in tests.
- *
- * @return Promise
- * Resolved when all requests have settled.
- */
- waitForRequestsToSettle() {
- let requests = [];
-
- // Gather all pending and active requests in this client
- // The request object supports a Promise API for completion (it has .then())
- this._pendingRequests.forEach(requestsForActor => {
- // Each value is an array of pending requests
- requests = requests.concat(requestsForActor);
- });
- this._activeRequests.forEach(requestForActor => {
- // Each value is a single active request
- requests = requests.concat(requestForActor);
- });
-
- // protocol.js
- // Use a Set because some fronts (like domwalker) seem to have multiple parents.
- let fronts = new Set();
- let poolsToVisit = [...this._pools];
-
- // With protocol.js, each front can potentially have it's own pools containing child
- // fronts, forming a tree. Descend through all the pools to locate all child fronts.
- while (poolsToVisit.length) {
- let pool = poolsToVisit.shift();
- fronts.add(pool);
- for (let child of pool.poolChildren()) {
- poolsToVisit.push(child);
- }
- }
-
- // For each front, wait for its requests to settle
- for (let front of fronts) {
- if (front.hasRequests()) {
- requests.push(front.waitForRequestsToSettle());
- }
- }
-
- // Abort early if there are no requests
- if (!requests.length) {
- return Promise.resolve();
- }
-
- return DevToolsUtils.settleAll(requests).catch(() => {
- // One of the requests might have failed, but ignore that situation here and pipe
- // both success and failure through the same path. The important part is just that
- // we waited.
- }).then(() => {
- // Repeat, more requests may have started in response to those we just waited for
- return this.waitForRequestsToSettle();
- });
- },
-
- registerClient: function (client) {
- let actorID = client.actor;
- if (!actorID) {
- throw new Error("DebuggerServer.registerClient expects " +
- "a client instance with an `actor` attribute.");
- }
- if (!Array.isArray(client.events)) {
- throw new Error("DebuggerServer.registerClient expects " +
- "a client instance with an `events` attribute " +
- "that is an array.");
- }
- if (client.events.length > 0 && typeof (client.emit) != "function") {
- throw new Error("DebuggerServer.registerClient expects " +
- "a client instance with non-empty `events` array to" +
- "have an `emit` function.");
- }
- if (this._clients.has(actorID)) {
- throw new Error("DebuggerServer.registerClient already registered " +
- "a client for this actor.");
- }
- this._clients.set(actorID, client);
- },
-
- unregisterClient: function (client) {
- let actorID = client.actor;
- if (!actorID) {
- throw new Error("DebuggerServer.unregisterClient expects " +
- "a Client instance with a `actor` attribute.");
- }
- this._clients.delete(actorID);
- },
-
- /**
- * Actor lifetime management, echos the server's actor pools.
- */
- __pools: null,
- get _pools() {
- if (this.__pools) {
- return this.__pools;
- }
- this.__pools = new Set();
- return this.__pools;
- },
-
- addActorPool: function (pool) {
- this._pools.add(pool);
- },
- removeActorPool: function (pool) {
- this._pools.delete(pool);
- },
- getActor: function (actorID) {
- let pool = this.poolFor(actorID);
- return pool ? pool.get(actorID) : null;
- },
-
- poolFor: function (actorID) {
- for (let pool of this._pools) {
- if (pool.has(actorID)) {
- return pool;
- }
- }
- return null;
- },
-
- /**
- * Currently attached addon.
- */
- activeAddon: null
-};
-
-eventSource(DebuggerClient.prototype);
-
-class Request extends EventEmitter {
- constructor(request) {
- super();
- this.request = request;
- }
-
- get actor() {
- return this.request.to || this.request.actor;
- }
-}
-
-/**
- * Creates a tab client for the remote debugging protocol server. This client
- * is a front to the tab actor created in the server side, hiding the protocol
- * details in a traditional JavaScript API.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param form object
- * The protocol form for this tab.
- */
-function TabClient(client, form) {
- this.client = client;
- this._actor = form.from;
- this._threadActor = form.threadActor;
- this.javascriptEnabled = form.javascriptEnabled;
- this.cacheDisabled = form.cacheDisabled;
- this.thread = null;
- this.request = this.client.request;
- this.traits = form.traits || {};
- this.events = ["workerListChanged"];
-}
-
-TabClient.prototype = {
- get actor() {
- return this._actor;
- },
- get _transport() {
- return this.client._transport;
- },
-
- /**
- * Attach to a thread actor.
- *
- * @param object options
- * Configuration options.
- * - useSourceMaps: whether to use source maps or not.
- * @param function onResponse
- * Called with the response packet and a ThreadClient
- * (which will be undefined on error).
- */
- attachThread: function (options = {}, onResponse = noop) {
- if (this.thread) {
- DevToolsUtils.executeSoon(() => onResponse({}, this.thread));
- return promise.resolve([{}, this.thread]);
- }
-
- let packet = {
- to: this._threadActor,
- type: "attach",
- options,
- };
- return this.request(packet).then(response => {
- if (!response.error) {
- this.thread = new ThreadClient(this, this._threadActor);
- this.client.registerClient(this.thread);
- }
- onResponse(response, this.thread);
- return [response, this.thread];
- });
- },
-
- /**
- * Detach the client from the tab actor.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- detach: DebuggerClient.requester({
- type: "detach"
- }, {
- before: function (packet) {
- if (this.thread) {
- this.thread.detach();
- }
- return packet;
- },
- after: function (response) {
- this.client.unregisterClient(this);
- return response;
- },
- }),
-
- /**
- * Bring the window to the front.
- */
- focus: DebuggerClient.requester({
- type: "focus"
- }, {}),
-
- /**
- * Reload the page in this tab.
- *
- * @param [optional] object options
- * An object with a `force` property indicating whether or not
- * this reload should skip the cache
- */
- reload: function (options = { force: false }) {
- return this._reload(options);
- },
- _reload: DebuggerClient.requester({
- type: "reload",
- options: arg(0)
- }),
-
- /**
- * Navigate to another URL.
- *
- * @param string url
- * The URL to navigate to.
- */
- navigateTo: DebuggerClient.requester({
- type: "navigateTo",
- url: arg(0)
- }),
-
- /**
- * Reconfigure the tab actor.
- *
- * @param object options
- * A dictionary object of the new options to use in the tab actor.
- * @param function onResponse
- * Called with the response packet.
- */
- reconfigure: DebuggerClient.requester({
- type: "reconfigure",
- options: arg(0)
- }),
-
- listWorkers: DebuggerClient.requester({
- type: "listWorkers"
- }),
-
- attachWorker: function (workerActor, onResponse) {
- return this.client.attachWorker(workerActor, onResponse);
- },
-};
-
-eventSource(TabClient.prototype);
-
-function WorkerClient(client, form) {
- this.client = client;
- this._actor = form.from;
- this._isClosed = false;
- this._url = form.url;
-
- this._onClose = this._onClose.bind(this);
-
- this.addListener("close", this._onClose);
-
- this.traits = {};
-}
-
-WorkerClient.prototype = {
- get _transport() {
- return this.client._transport;
- },
-
- get request() {
- return this.client.request;
- },
-
- get actor() {
- return this._actor;
- },
-
- get url() {
- return this._url;
- },
-
- get isClosed() {
- return this._isClosed;
- },
-
- detach: DebuggerClient.requester({ type: "detach" }, {
- after: function (response) {
- if (this.thread) {
- this.client.unregisterClient(this.thread);
- }
- this.client.unregisterClient(this);
- return response;
- },
- }),
-
- attachThread: function (options = {}, onResponse = noop) {
- if (this.thread) {
- let response = [{
- type: "connected",
- threadActor: this.thread._actor,
- consoleActor: this.consoleActor,
- }, this.thread];
- DevToolsUtils.executeSoon(() => onResponse(response));
- return response;
- }
-
- // The connect call on server doesn't attach the thread as of version 44.
- return this.request({
- to: this._actor,
- type: "connect",
- options,
- }).then(connectResponse => {
- if (connectResponse.error) {
- onResponse(connectResponse, null);
- return [connectResponse, null];
- }
-
- return this.request({
- to: connectResponse.threadActor,
- type: "attach",
- options,
- }).then(attachResponse => {
- if (attachResponse.error) {
- onResponse(attachResponse, null);
- }
-
- this.thread = new ThreadClient(this, connectResponse.threadActor);
- this.consoleActor = connectResponse.consoleActor;
- this.client.registerClient(this.thread);
-
- onResponse(connectResponse, this.thread);
- return [connectResponse, this.thread];
- });
- }, error => {
- onResponse(error, null);
- });
- },
-
- _onClose: function () {
- this.removeListener("close", this._onClose);
-
- if (this.thread) {
- this.client.unregisterClient(this.thread);
- }
- this.client.unregisterClient(this);
- this._isClosed = true;
- },
-
- reconfigure: function () {
- return Promise.resolve();
- },
-
- events: ["close"]
-};
-
-eventSource(WorkerClient.prototype);
-
-function AddonClient(client, actor) {
- this._client = client;
- this._actor = actor;
- this.request = this._client.request;
- this.events = [];
-}
-
-AddonClient.prototype = {
- get actor() {
- return this._actor;
- },
- get _transport() {
- return this._client._transport;
- },
-
- /**
- * Detach the client from the addon actor.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- detach: DebuggerClient.requester({
- type: "detach"
- }, {
- after: function (response) {
- if (this._client.activeAddon === this) {
- this._client.activeAddon = null;
- }
- this._client.unregisterClient(this);
- return response;
- },
- })
-};
-
-/**
- * A RootClient object represents a root actor on the server. Each
- * DebuggerClient keeps a RootClient instance representing the root actor
- * for the initial connection; DebuggerClient's 'listTabs' and
- * 'listChildProcesses' methods forward to that root actor.
- *
- * @param client object
- * The client connection to which this actor belongs.
- * @param greeting string
- * The greeting packet from the root actor we're to represent.
- *
- * Properties of a RootClient instance:
- *
- * @property actor string
- * The name of this child's root actor.
- * @property applicationType string
- * The application type, as given in the root actor's greeting packet.
- * @property traits object
- * The traits object, as given in the root actor's greeting packet.
- */
-function RootClient(client, greeting) {
- this._client = client;
- this.actor = greeting.from;
- this.applicationType = greeting.applicationType;
- this.traits = greeting.traits;
-}
-exports.RootClient = RootClient;
-
-RootClient.prototype = {
- constructor: RootClient,
-
- /**
- * Gets the "root" form, which lists all the global actors that affect the entire
- * browser. This can replace usages of `listTabs` that only wanted the global actors
- * and didn't actually care about tabs.
- */
- getRoot: DebuggerClient.requester({ type: "getRoot" }),
-
- /**
- * List the open tabs.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- listTabs: DebuggerClient.requester({ type: "listTabs" }),
-
- /**
- * List the installed addons.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- listAddons: DebuggerClient.requester({ type: "listAddons" }),
-
- /**
- * List the registered workers.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- listWorkers: DebuggerClient.requester({ type: "listWorkers" }),
-
- /**
- * List the registered service workers.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- listServiceWorkerRegistrations: DebuggerClient.requester({
- type: "listServiceWorkerRegistrations"
- }),
-
- /**
- * List the running processes.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- listProcesses: DebuggerClient.requester({ type: "listProcesses" }),
-
- /**
- * Fetch the TabActor for the currently selected tab, or for a specific
- * tab given as first parameter.
- *
- * @param [optional] object filter
- * A dictionary object with following optional attributes:
- * - outerWindowID: used to match tabs in parent process
- * - tabId: used to match tabs in child processes
- * - tab: a reference to xul:tab element
- * If nothing is specified, returns the actor for the currently
- * selected tab.
- */
- getTab: function (filter) {
- let packet = {
- to: this.actor,
- type: "getTab"
- };
-
- if (filter) {
- if (typeof (filter.outerWindowID) == "number") {
- packet.outerWindowID = filter.outerWindowID;
- } else if (typeof (filter.tabId) == "number") {
- packet.tabId = filter.tabId;
- } else if ("tab" in filter) {
- let browser = filter.tab.linkedBrowser;
- if (browser.frameLoader.tabParent) {
- // Tabs in child process
- packet.tabId = browser.frameLoader.tabParent.tabId;
- } else if (browser.outerWindowID) {
- // <xul:browser> tabs in parent process
- packet.outerWindowID = browser.outerWindowID;
- } else {
- // <iframe mozbrowser> tabs in parent process
- let windowUtils = browser.contentWindow
- .QueryInterface(Ci.nsIInterfaceRequestor)
- .getInterface(Ci.nsIDOMWindowUtils);
- packet.outerWindowID = windowUtils.outerWindowID;
- }
- } else {
- // Throw if a filter object have been passed but without
- // any clearly idenfified filter.
- throw new Error("Unsupported argument given to getTab request");
- }
- }
-
- return this.request(packet);
- },
-
- /**
- * Fetch the WindowActor for a specific window, like a browser window in
- * Firefox, but it can be used to reach any window in the process.
- *
- * @param number outerWindowID
- * The outerWindowID of the top level window you are looking for.
- */
- getWindow: function ({ outerWindowID }) {
- if (!outerWindowID) {
- throw new Error("Must specify outerWindowID");
- }
-
- let packet = {
- to: this.actor,
- type: "getWindow",
- outerWindowID,
- };
-
- return this.request(packet);
- },
-
- /**
- * Description of protocol's actors and methods.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- protocolDescription: DebuggerClient.requester({ type: "protocolDescription" }),
-
- /*
- * Methods constructed by DebuggerClient.requester require these forwards
- * on their 'this'.
- */
- get _transport() {
- return this._client._transport;
- },
- get request() {
- return this._client.request;
- }
-};
-
-/**
- * Creates a thread client for the remote debugging protocol server. This client
- * is a front to the thread actor created in the server side, hiding the
- * protocol details in a traditional JavaScript API.
- *
- * @param client DebuggerClient|TabClient
- * The parent of the thread (tab for tab-scoped debuggers, DebuggerClient
- * for chrome debuggers).
- * @param actor string
- * The actor ID for this thread.
- */
-function ThreadClient(client, actor) {
- this._parent = client;
- this.client = client instanceof DebuggerClient ? client : client.client;
- this._actor = actor;
- this._frameCache = [];
- this._scriptCache = {};
- this._pauseGrips = {};
- this._threadGrips = {};
- this.request = this.client.request;
-}
-
-ThreadClient.prototype = {
- _state: "paused",
- get state() {
- return this._state;
- },
- get paused() {
- return this._state === "paused";
- },
-
- _pauseOnExceptions: false,
- _ignoreCaughtExceptions: false,
- _pauseOnDOMEvents: null,
-
- _actor: null,
- get actor() {
- return this._actor;
- },
-
- get _transport() {
- return this.client._transport;
- },
-
- _assertPaused: function (command) {
- if (!this.paused) {
- throw Error(command + " command sent while not paused. Currently " + this._state);
- }
- },
-
- /**
- * Resume a paused thread. If the optional limit parameter is present, then
- * the thread will also pause when that limit is reached.
- *
- * @param [optional] object limit
- * An object with a type property set to the appropriate limit (next,
- * step, or finish) per the remote debugging protocol specification.
- * Use null to specify no limit.
- * @param function onResponse
- * Called with the response packet.
- */
- _doResume: DebuggerClient.requester({
- type: "resume",
- resumeLimit: arg(0)
- }, {
- before: function (packet) {
- this._assertPaused("resume");
-
- // Put the client in a tentative "resuming" state so we can prevent
- // further requests that should only be sent in the paused state.
- this._previousState = this._state;
- this._state = "resuming";
-
- if (this._pauseOnExceptions) {
- packet.pauseOnExceptions = this._pauseOnExceptions;
- }
- if (this._ignoreCaughtExceptions) {
- packet.ignoreCaughtExceptions = this._ignoreCaughtExceptions;
- }
- if (this._pauseOnDOMEvents) {
- packet.pauseOnDOMEvents = this._pauseOnDOMEvents;
- }
- return packet;
- },
- after: function (response) {
- if (response.error && this._state == "resuming") {
- // There was an error resuming, update the state to the new one
- // reported by the server, if given (only on wrongState), otherwise
- // reset back to the previous state.
- if (response.state) {
- this._state = ThreadStateTypes[response.state];
- } else {
- this._state = this._previousState;
- }
- }
- delete this._previousState;
- return response;
- },
- }),
-
- /**
- * Reconfigure the thread actor.
- *
- * @param object options
- * A dictionary object of the new options to use in the thread actor.
- * @param function onResponse
- * Called with the response packet.
- */
- reconfigure: DebuggerClient.requester({
- type: "reconfigure",
- options: arg(0)
- }),
-
- /**
- * Resume a paused thread.
- */
- resume: function (onResponse) {
- return this._doResume(null, onResponse);
- },
-
- /**
- * Resume then pause without stepping.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- resumeThenPause: function (onResponse) {
- return this._doResume({ type: "break" }, onResponse);
- },
-
- /**
- * Step over a function call.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- stepOver: function (onResponse) {
- return this._doResume({ type: "next" }, onResponse);
- },
-
- /**
- * Step into a function call.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- stepIn: function (onResponse) {
- return this._doResume({ type: "step" }, onResponse);
- },
-
- /**
- * Step out of a function call.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- stepOut: function (onResponse) {
- return this._doResume({ type: "finish" }, onResponse);
- },
-
- /**
- * Immediately interrupt a running thread.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- interrupt: function (onResponse) {
- return this._doInterrupt(null, onResponse);
- },
-
- /**
- * Pause execution right before the next JavaScript bytecode is executed.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- breakOnNext: function (onResponse) {
- return this._doInterrupt("onNext", onResponse);
- },
-
- /**
- * Interrupt a running thread.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- _doInterrupt: DebuggerClient.requester({
- type: "interrupt",
- when: arg(0)
- }),
-
- /**
- * Enable or disable pausing when an exception is thrown.
- *
- * @param boolean pauseOnExceptions
- * Enables pausing if true, disables otherwise.
- * @param boolean ignoreCaughtExceptions
- * Whether to ignore caught exceptions
- * @param function onResponse
- * Called with the response packet.
- */
- pauseOnExceptions: function (pauseOnExceptions,
- ignoreCaughtExceptions,
- onResponse = noop) {
- this._pauseOnExceptions = pauseOnExceptions;
- this._ignoreCaughtExceptions = ignoreCaughtExceptions;
-
- // Otherwise send the flag using a standard resume request.
- if (!this.paused) {
- return this.interrupt(response => {
- if (response.error) {
- // Can't continue if pausing failed.
- onResponse(response);
- return response;
- }
- return this.resume(onResponse);
- });
- }
-
- onResponse();
- return promise.resolve();
- },
-
- /**
- * Enable pausing when the specified DOM events are triggered. Disabling
- * pausing on an event can be realized by calling this method with the updated
- * array of events that doesn't contain it.
- *
- * @param array|string events
- * An array of strings, representing the DOM event types to pause on,
- * or "*" to pause on all DOM events. Pass an empty array to
- * completely disable pausing on DOM events.
- * @param function onResponse
- * Called with the response packet in a future turn of the event loop.
- */
- pauseOnDOMEvents: function (events, onResponse = noop) {
- this._pauseOnDOMEvents = events;
- // If the debuggee is paused, the value of the array will be communicated in
- // the next resumption. Otherwise we have to force a pause in order to send
- // the array.
- if (this.paused) {
- DevToolsUtils.executeSoon(() => onResponse({}));
- return {};
- }
- return this.interrupt(response => {
- // Can't continue if pausing failed.
- if (response.error) {
- onResponse(response);
- return response;
- }
- return this.resume(onResponse);
- });
- },
-
- /**
- * Send a clientEvaluate packet to the debuggee. Response
- * will be a resume packet.
- *
- * @param string frame
- * The actor ID of the frame where the evaluation should take place.
- * @param string expression
- * The expression that will be evaluated in the scope of the frame
- * above.
- * @param function onResponse
- * Called with the response packet.
- */
- eval: DebuggerClient.requester({
- type: "clientEvaluate",
- frame: arg(0),
- expression: arg(1)
- }, {
- before: function (packet) {
- this._assertPaused("eval");
- // Put the client in a tentative "resuming" state so we can prevent
- // further requests that should only be sent in the paused state.
- this._state = "resuming";
- return packet;
- },
- after: function (response) {
- if (response.error) {
- // There was an error resuming, back to paused state.
- this._state = "paused";
- }
- return response;
- },
- }),
-
- /**
- * Detach from the thread actor.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- detach: DebuggerClient.requester({
- type: "detach"
- }, {
- after: function (response) {
- this.client.unregisterClient(this);
- this._parent.thread = null;
- return response;
- },
- }),
-
- /**
- * Release multiple thread-lifetime object actors. If any pause-lifetime
- * actors are included in the request, a |notReleasable| error will return,
- * but all the thread-lifetime ones will have been released.
- *
- * @param array actors
- * An array with actor IDs to release.
- */
- releaseMany: DebuggerClient.requester({
- type: "releaseMany",
- actors: arg(0),
- }),
-
- /**
- * Promote multiple pause-lifetime object actors to thread-lifetime ones.
- *
- * @param array actors
- * An array with actor IDs to promote.
- */
- threadGrips: DebuggerClient.requester({
- type: "threadGrips",
- actors: arg(0)
- }),
-
- /**
- * Return the event listeners defined on the page.
- *
- * @param onResponse Function
- * Called with the thread's response.
- */
- eventListeners: DebuggerClient.requester({
- type: "eventListeners"
- }),
-
- /**
- * Request the loaded sources for the current thread.
- *
- * @param onResponse Function
- * Called with the thread's response.
- */
- getSources: DebuggerClient.requester({
- type: "sources"
- }),
-
- /**
- * Clear the thread's source script cache. A scriptscleared event
- * will be sent.
- */
- _clearScripts: function () {
- if (Object.keys(this._scriptCache).length > 0) {
- this._scriptCache = {};
- this.emit("scriptscleared");
- }
- },
-
- /**
- * Request frames from the callstack for the current thread.
- *
- * @param start integer
- * The number of the youngest stack frame to return (the youngest
- * frame is 0).
- * @param count integer
- * The maximum number of frames to return, or null to return all
- * frames.
- * @param onResponse function
- * Called with the thread's response.
- */
- getFrames: DebuggerClient.requester({
- type: "frames",
- start: arg(0),
- count: arg(1)
- }),
-
- /**
- * An array of cached frames. Clients can observe the framesadded and
- * framescleared event to keep up to date on changes to this cache,
- * and can fill it using the fillFrames method.
- */
- get cachedFrames() {
- return this._frameCache;
- },
-
- /**
- * true if there are more stack frames available on the server.
- */
- get moreFrames() {
- return this.paused && (!this._frameCache || this._frameCache.length == 0
- || !this._frameCache[this._frameCache.length - 1].oldest);
- },
-
- /**
- * Request the frame environment.
- *
- * @param frameId string
- */
- getEnvironment: function (frameId) {
- return this.request({ to: frameId, type: "getEnvironment" });
- },
-
- /**
- * Ensure that at least total stack frames have been loaded in the
- * ThreadClient's stack frame cache. A framesadded event will be
- * sent when the stack frame cache is updated.
- *
- * @param total number
- * The minimum number of stack frames to be included.
- * @param callback function
- * Optional callback function called when frames have been loaded
- * @returns true if a framesadded notification should be expected.
- */
- fillFrames: function (total, callback = noop) {
- this._assertPaused("fillFrames");
- if (this._frameCache.length >= total) {
- return false;
- }
-
- let numFrames = this._frameCache.length;
-
- this.getFrames(numFrames, total - numFrames, (response) => {
- if (response.error) {
- callback(response);
- return;
- }
-
- let threadGrips = DevToolsUtils.values(this._threadGrips);
-
- for (let i in response.frames) {
- let frame = response.frames[i];
- if (!frame.where.source) {
- // Older servers use urls instead, so we need to resolve
- // them to source actors
- for (let grip of threadGrips) {
- if (grip instanceof SourceClient && grip.url === frame.url) {
- frame.where.source = grip._form;
- }
- }
- }
-
- this._frameCache[frame.depth] = frame;
- }
-
- // If we got as many frames as we asked for, there might be more
- // frames available.
- this.emit("framesadded");
-
- callback(response);
- });
-
- return true;
- },
-
- /**
- * Clear the thread's stack frame cache. A framescleared event
- * will be sent.
- */
- _clearFrames: function () {
- if (this._frameCache.length > 0) {
- this._frameCache = [];
- this.emit("framescleared");
- }
- },
-
- /**
- * Return a ObjectClient object for the given object grip.
- *
- * @param grip object
- * A pause-lifetime object grip returned by the protocol.
- */
- pauseGrip: function (grip) {
- if (grip.actor in this._pauseGrips) {
- return this._pauseGrips[grip.actor];
- }
-
- let client = new ObjectClient(this.client, grip);
- this._pauseGrips[grip.actor] = client;
- return client;
- },
-
- /**
- * Get or create a long string client, checking the grip client cache if it
- * already exists.
- *
- * @param grip Object
- * The long string grip returned by the protocol.
- * @param gripCacheName String
- * The property name of the grip client cache to check for existing
- * clients in.
- */
- _longString: function (grip, gripCacheName) {
- if (grip.actor in this[gripCacheName]) {
- return this[gripCacheName][grip.actor];
- }
-
- let client = new LongStringClient(this.client, grip);
- this[gripCacheName][grip.actor] = client;
- return client;
- },
-
- /**
- * Return an instance of LongStringClient for the given long string grip that
- * is scoped to the current pause.
- *
- * @param grip Object
- * The long string grip returned by the protocol.
- */
- pauseLongString: function (grip) {
- return this._longString(grip, "_pauseGrips");
- },
-
- /**
- * Return an instance of LongStringClient for the given long string grip that
- * is scoped to the thread lifetime.
- *
- * @param grip Object
- * The long string grip returned by the protocol.
- */
- threadLongString: function (grip) {
- return this._longString(grip, "_threadGrips");
- },
-
- /**
- * Get or create an ArrayBuffer client, checking the grip client cache if it
- * already exists.
- *
- * @param grip Object
- * The ArrayBuffer grip returned by the protocol.
- * @param gripCacheName String
- * The property name of the grip client cache to check for existing
- * clients in.
- */
- _arrayBuffer: function (grip, gripCacheName) {
- if (grip.actor in this[gripCacheName]) {
- return this[gripCacheName][grip.actor];
- }
-
- let client = new ArrayBufferClient(this.client, grip);
- this[gripCacheName][grip.actor] = client;
- return client;
- },
-
- /**
- * Return an instance of ArrayBufferClient for the given ArrayBuffer grip that
- * is scoped to the thread lifetime.
- *
- * @param grip Object
- * The ArrayBuffer grip returned by the protocol.
- */
- threadArrayBuffer: function (grip) {
- return this._arrayBuffer(grip, "_threadGrips");
- },
-
- /**
- * Clear and invalidate all the grip clients from the given cache.
- *
- * @param gripCacheName
- * The property name of the grip cache we want to clear.
- */
- _clearObjectClients: function (gripCacheName) {
- for (let id in this[gripCacheName]) {
- this[gripCacheName][id].valid = false;
- }
- this[gripCacheName] = {};
- },
-
- /**
- * Invalidate pause-lifetime grip clients and clear the list of current grip
- * clients.
- */
- _clearPauseGrips: function () {
- this._clearObjectClients("_pauseGrips");
- },
-
- /**
- * Invalidate thread-lifetime grip clients and clear the list of current grip
- * clients.
- */
- _clearThreadGrips: function () {
- this._clearObjectClients("_threadGrips");
- },
-
- /**
- * Handle thread state change by doing necessary cleanup and notifying all
- * registered listeners.
- */
- _onThreadState: function (packet) {
- this._state = ThreadStateTypes[packet.type];
- // The debugger UI may not be initialized yet so we want to keep
- // the packet around so it knows what to pause state to display
- // when it's initialized
- this._lastPausePacket = packet.type === "resumed" ? null : packet;
- this._clearFrames();
- this._clearPauseGrips();
- packet.type === ThreadStateTypes.detached && this._clearThreadGrips();
- this.client._eventsEnabled && this.emit(packet.type, packet);
- },
-
- getLastPausePacket: function () {
- return this._lastPausePacket;
- },
-
- /**
- * Return an EnvironmentClient instance for the given environment actor form.
- */
- environment: function (form) {
- return new EnvironmentClient(this.client, form);
- },
-
- /**
- * Return an instance of SourceClient for the given source actor form.
- */
- source: function (form) {
- if (form.actor in this._threadGrips) {
- return this._threadGrips[form.actor];
- }
-
- this._threadGrips[form.actor] = new SourceClient(this, form);
- return this._threadGrips[form.actor];
- },
-
- /**
- * Request the prototype and own properties of mutlipleObjects.
- *
- * @param onResponse function
- * Called with the request's response.
- * @param actors [string]
- * List of actor ID of the queried objects.
- */
- getPrototypesAndProperties: DebuggerClient.requester({
- type: "prototypesAndProperties",
- actors: arg(0)
- }),
-
- events: ["newSource"]
-};
-
-eventSource(ThreadClient.prototype);
-
-/**
- * Creates a tracing profiler client for the remote debugging protocol
- * server. This client is a front to the trace actor created on the
- * server side, hiding the protocol details in a traditional
- * JavaScript API.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param actor string
- * The actor ID for this thread.
- */
-function TraceClient(client, actor) {
- this._client = client;
- this._actor = actor;
- this._activeTraces = new Set();
- this._waitingPackets = new Map();
- this._expectedPacket = 0;
- this.request = this._client.request;
- this.events = [];
-}
-
-TraceClient.prototype = {
- get actor() {
- return this._actor;
- },
- get tracing() {
- return this._activeTraces.size > 0;
- },
-
- get _transport() {
- return this._client._transport;
- },
-
- /**
- * Detach from the trace actor.
- */
- detach: DebuggerClient.requester({
- type: "detach"
- }, {
- after: function (response) {
- this._client.unregisterClient(this);
- return response;
- },
- }),
-
- /**
- * Start a new trace.
- *
- * @param trace [string]
- * An array of trace types to be recorded by the new trace.
- *
- * @param name string
- * The name of the new trace.
- *
- * @param onResponse function
- * Called with the request's response.
- */
- startTrace: DebuggerClient.requester({
- type: "startTrace",
- name: arg(1),
- trace: arg(0)
- }, {
- after: function (response) {
- if (response.error) {
- return response;
- }
-
- if (!this.tracing) {
- this._waitingPackets.clear();
- this._expectedPacket = 0;
- }
- this._activeTraces.add(response.name);
-
- return response;
- },
- }),
-
- /**
- * End a trace. If a name is provided, stop the named
- * trace. Otherwise, stop the most recently started trace.
- *
- * @param name string
- * The name of the trace to stop.
- *
- * @param onResponse function
- * Called with the request's response.
- */
- stopTrace: DebuggerClient.requester({
- type: "stopTrace",
- name: arg(0)
- }, {
- after: function (response) {
- if (response.error) {
- return response;
- }
-
- this._activeTraces.delete(response.name);
-
- return response;
- },
- })
-};
+const {arg, DebuggerClient} = require("./debugger-client");
+loader.lazyRequireGetter(this, "PropertyIteratorClient", "devtools/shared/client/property-iterator-client");
+loader.lazyRequireGetter(this, "SymbolIteratorClient", "devtools/shared/client/symbol-iterator-client");
/**
* Grip clients are used to retrieve information about the relevant object.
*
* @param client DebuggerClient
* The debugger client parent.
* @param grip object
* A pause-lifetime object grip returned by the protocol.
*/
function ObjectClient(client, grip) {
this._grip = grip;
this._client = client;
this.request = this._client.request;
}
-exports.ObjectClient = ObjectClient;
ObjectClient.prototype = {
get actor() {
return this._grip.actor;
},
get _transport() {
return this._client._transport;
},
@@ -2775,652 +270,9 @@ ObjectClient.prototype = {
throw new Error("getPromiseRejectionStack is only valid for " +
"promise grips.");
}
return packet;
}
})
};
-/**
- * A PropertyIteratorClient provides a way to access to property names and
- * values of an object efficiently, slice by slice.
- * Note that the properties can be sorted in the backend,
- * this is controled while creating the PropertyIteratorClient
- * from ObjectClient.enumProperties.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param grip Object
- * A PropertyIteratorActor grip returned by the protocol via
- * TabActor.enumProperties request.
- */
-function PropertyIteratorClient(client, grip) {
- this._grip = grip;
- this._client = client;
- this.request = this._client.request;
-}
-
-PropertyIteratorClient.prototype = {
- get actor() {
- return this._grip.actor;
- },
-
- /**
- * Get the total number of properties available in the iterator.
- */
- get count() {
- return this._grip.count;
- },
-
- /**
- * Get one or more property names that correspond to the positions in the
- * indexes parameter.
- *
- * @param indexes Array
- * An array of property indexes.
- * @param callback Function
- * The function called when we receive the property names.
- */
- names: DebuggerClient.requester({
- type: "names",
- indexes: arg(0)
- }, {}),
-
- /**
- * Get a set of following property value(s).
- *
- * @param start Number
- * The index of the first property to fetch.
- * @param count Number
- * The number of properties to fetch.
- * @param callback Function
- * The function called when we receive the property values.
- */
- slice: DebuggerClient.requester({
- type: "slice",
- start: arg(0),
- count: arg(1)
- }, {}),
-
- /**
- * Get all the property values.
- *
- * @param callback Function
- * The function called when we receive the property values.
- */
- all: DebuggerClient.requester({
- type: "all"
- }, {}),
-};
-
-/**
- * A SymbolIteratorClient provides a way to access to symbols
- * of an object efficiently, slice by slice.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param grip Object
- * A SymbolIteratorActor grip returned by the protocol via
- * TabActor.enumSymbols request.
- */
-function SymbolIteratorClient(client, grip) {
- this._grip = grip;
- this._client = client;
- this.request = this._client.request;
-}
-
-SymbolIteratorClient.prototype = {
- get actor() {
- return this._grip.actor;
- },
-
- /**
- * Get the total number of symbols available in the iterator.
- */
- get count() {
- return this._grip.count;
- },
-
- /**
- * Get a set of following symbols.
- *
- * @param start Number
- * The index of the first symbol to fetch.
- * @param count Number
- * The number of symbols to fetch.
- * @param callback Function
- * The function called when we receive the symbols.
- */
- slice: DebuggerClient.requester({
- type: "slice",
- start: arg(0),
- count: arg(1)
- }, {}),
-
- /**
- * Get all the symbols.
- *
- * @param callback Function
- * The function called when we receive the symbols.
- */
- all: DebuggerClient.requester({
- type: "all"
- }, {}),
-};
-
-/**
- * A ArrayBufferClient provides a way to access ArrayBuffer from the
- * debugger server.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param grip Object
- * A pause-lifetime ArrayBuffer grip returned by the protocol.
- */
-function ArrayBufferClient(client, grip) {
- this._grip = grip;
- this._client = client;
- this.request = this._client.request;
-}
-ArrayBufferClient.prototype = {
- get actor() {
- return this._grip.actor;
- },
- get length() {
- return this._grip.length;
- },
- get _transport() {
- return this._client._transport;
- },
-
- valid: true,
-
- slice: DebuggerClient.requester({
- type: "slice",
- start: arg(0),
- count: arg(1)
- }),
-};
-
-/**
- * A LongStringClient provides a way to access "very long" strings from the
- * debugger server.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param grip Object
- * A pause-lifetime long string grip returned by the protocol.
- */
-function LongStringClient(client, grip) {
- this._grip = grip;
- this._client = client;
- this.request = this._client.request;
-}
-exports.LongStringClient = LongStringClient;
-
-LongStringClient.prototype = {
- get actor() {
- return this._grip.actor;
- },
- get length() {
- return this._grip.length;
- },
- get initial() {
- return this._grip.initial;
- },
- get _transport() {
- return this._client._transport;
- },
-
- valid: true,
-
- /**
- * Get the substring of this LongString from start to end.
- *
- * @param start Number
- * The starting index.
- * @param end Number
- * The ending index.
- * @param callback Function
- * The function called when we receive the substring.
- */
- substring: DebuggerClient.requester({
- type: "substring",
- start: arg(0),
- end: arg(1)
- }),
-};
-
-/**
- * A SourceClient provides a way to access the source text of a script.
- *
- * @param client ThreadClient
- * The thread client parent.
- * @param form Object
- * The form sent across the remote debugging protocol.
- */
-function SourceClient(client, form) {
- this._form = form;
- this._isBlackBoxed = form.isBlackBoxed;
- this._isPrettyPrinted = form.isPrettyPrinted;
- this._activeThread = client;
- this._client = client.client;
-}
-
-SourceClient.prototype = {
- get _transport() {
- return this._client._transport;
- },
- get isBlackBoxed() {
- return this._isBlackBoxed;
- },
- get isPrettyPrinted() {
- return this._isPrettyPrinted;
- },
- get actor() {
- return this._form.actor;
- },
- get request() {
- return this._client.request;
- },
- get url() {
- return this._form.url;
- },
-
- /**
- * Black box this SourceClient's source.
- *
- * @param callback Function
- * The callback function called when we receive the response from the server.
- */
- blackBox: DebuggerClient.requester({
- type: "blackbox"
- }, {
- after: function (response) {
- if (!response.error) {
- this._isBlackBoxed = true;
- if (this._activeThread) {
- this._activeThread.emit("blackboxchange", this);
- }
- }
- return response;
- }
- }),
-
- /**
- * Un-black box this SourceClient's source.
- *
- * @param callback Function
- * The callback function called when we receive the response from the server.
- */
- unblackBox: DebuggerClient.requester({
- type: "unblackbox"
- }, {
- after: function (response) {
- if (!response.error) {
- this._isBlackBoxed = false;
- if (this._activeThread) {
- this._activeThread.emit("blackboxchange", this);
- }
- }
- return response;
- }
- }),
-
- /**
- * Get Executable Lines from a source
- *
- * @param callback Function
- * The callback function called when we receive the response from the server.
- */
- getExecutableLines: function (cb = noop) {
- let packet = {
- to: this._form.actor,
- type: "getExecutableLines"
- };
-
- return this._client.request(packet).then(res => {
- cb(res.lines);
- return res.lines;
- });
- },
-
- /**
- * Get a long string grip for this SourceClient's source.
- */
- source: function (callback = noop) {
- let packet = {
- to: this._form.actor,
- type: "source"
- };
- return this._client.request(packet).then(response => {
- return this._onSourceResponse(response, callback);
- });
- },
-
- /**
- * Pretty print this source's text.
- */
- prettyPrint: function (indent, callback = noop) {
- const packet = {
- to: this._form.actor,
- type: "prettyPrint",
- indent
- };
- return this._client.request(packet).then(response => {
- if (!response.error) {
- this._isPrettyPrinted = true;
- this._activeThread._clearFrames();
- this._activeThread.emit("prettyprintchange", this);
- }
- return this._onSourceResponse(response, callback);
- });
- },
-
- /**
- * Stop pretty printing this source's text.
- */
- disablePrettyPrint: function (callback = noop) {
- const packet = {
- to: this._form.actor,
- type: "disablePrettyPrint"
- };
- return this._client.request(packet).then(response => {
- if (!response.error) {
- this._isPrettyPrinted = false;
- this._activeThread._clearFrames();
- this._activeThread.emit("prettyprintchange", this);
- }
- return this._onSourceResponse(response, callback);
- });
- },
-
- _onSourceResponse: function (response, callback) {
- if (response.error) {
- callback(response);
- return response;
- }
-
- if (typeof response.source === "string") {
- callback(response);
- return response;
- }
-
- let { contentType, source } = response;
- if (source.type === "arrayBuffer") {
- let arrayBuffer = this._activeThread.threadArrayBuffer(source);
- return arrayBuffer.slice(0, arrayBuffer.length).then(function (resp) {
- if (resp.error) {
- callback(resp);
- return resp;
- }
- // Keeping str as a string, ArrayBuffer/Uint8Array will not survive
- // setIn/mergeIn operations.
- const str = atob(resp.encoded);
- let newResponse = {
- source: {
- binary: str,
- toString: () => "[wasm]",
- },
- contentType,
- };
- callback(newResponse);
- return newResponse;
- });
- }
-
- let longString = this._activeThread.threadLongString(source);
- return longString.substring(0, longString.length).then(function (resp) {
- if (resp.error) {
- callback(resp);
- return resp;
- }
-
- let newResponse = {
- source: resp.substring,
- contentType: contentType
- };
- callback(newResponse);
- return newResponse;
- });
- },
-
- /**
- * Request to set a breakpoint in the specified location.
- *
- * @param object location
- * The location and condition of the breakpoint in
- * the form of { line[, column, condition] }.
- * @param function onResponse
- * Called with the thread's response.
- */
- setBreakpoint: function ({ line, column, condition, noSliding }, onResponse = noop) {
- // A helper function that sets the breakpoint.
- let doSetBreakpoint = callback => {
- let root = this._client.mainRoot;
- let location = {
- line,
- column,
- };
-
- let packet = {
- to: this.actor,
- type: "setBreakpoint",
- location,
- condition,
- noSliding,
- };
-
- // Backwards compatibility: send the breakpoint request to the
- // thread if the server doesn't support Debugger.Source actors.
- if (!root.traits.debuggerSourceActors) {
- packet.to = this._activeThread.actor;
- packet.location.url = this.url;
- }
-
- return this._client.request(packet).then(response => {
- // Ignoring errors, since the user may be setting a breakpoint in a
- // dead script that will reappear on a page reload.
- let bpClient;
- if (response.actor) {
- bpClient = new BreakpointClient(
- this._client,
- this,
- response.actor,
- location,
- root.traits.conditionalBreakpoints ? condition : undefined
- );
- }
- onResponse(response, bpClient);
- if (callback) {
- callback();
- }
- return [response, bpClient];
- });
- };
-
- // If the debuggee is paused, just set the breakpoint.
- if (this._activeThread.paused) {
- return doSetBreakpoint();
- }
- // Otherwise, force a pause in order to set the breakpoint.
- return this._activeThread.interrupt().then(response => {
- if (response.error) {
- // Can't set the breakpoint if pausing failed.
- onResponse(response);
- return response;
- }
-
- const { type, why } = response;
- const cleanUp = type == "paused" && why.type == "interrupted"
- ? () => this._activeThread.resume()
- : noop;
-
- return doSetBreakpoint(cleanUp);
- });
- }
-};
-
-/**
- * Breakpoint clients are used to remove breakpoints that are no longer used.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param sourceClient SourceClient
- * The source where this breakpoint exists
- * @param actor string
- * The actor ID for this breakpoint.
- * @param location object
- * The location of the breakpoint. This is an object with two properties:
- * url and line.
- * @param condition string
- * The conditional expression of the breakpoint
- */
-function BreakpointClient(client, sourceClient, actor, location, condition) {
- this._client = client;
- this._actor = actor;
- this.location = location;
- this.location.actor = sourceClient.actor;
- this.location.url = sourceClient.url;
- this.source = sourceClient;
- this.request = this._client.request;
-
- // The condition property should only exist if it's a truthy value
- if (condition) {
- this.condition = condition;
- }
-}
-
-BreakpointClient.prototype = {
-
- _actor: null,
- get actor() {
- return this._actor;
- },
- get _transport() {
- return this._client._transport;
- },
-
- /**
- * Remove the breakpoint from the server.
- */
- remove: DebuggerClient.requester({
- type: "delete"
- }),
-
- /**
- * Determines if this breakpoint has a condition
- */
- hasCondition: function () {
- let root = this._client.mainRoot;
- // XXX bug 990137: We will remove support for client-side handling of
- // conditional breakpoints
- if (root.traits.conditionalBreakpoints) {
- return "condition" in this;
- }
- return "conditionalExpression" in this;
- },
-
- /**
- * Get the condition of this breakpoint. Currently we have to
- * support locally emulated conditional breakpoints until the
- * debugger servers are updated (see bug 990137). We used a
- * different property when moving it server-side to ensure that we
- * are testing the right code.
- */
- getCondition: function () {
- let root = this._client.mainRoot;
- if (root.traits.conditionalBreakpoints) {
- return this.condition;
- }
- return this.conditionalExpression;
- },
-
- /**
- * Set the condition of this breakpoint
- */
- setCondition: function (gThreadClient, condition) {
- let root = this._client.mainRoot;
- let deferred = promise.defer();
-
- if (root.traits.conditionalBreakpoints) {
- let info = {
- line: this.location.line,
- column: this.location.column,
- condition: condition
- };
-
- // Remove the current breakpoint and add a new one with the
- // condition.
- this.remove(response => {
- if (response && response.error) {
- deferred.reject(response);
- return;
- }
-
- this.source.setBreakpoint(info, (resp, newBreakpoint) => {
- if (resp && resp.error) {
- deferred.reject(resp);
- } else {
- deferred.resolve(newBreakpoint);
- }
- });
- });
- } else {
- // The property shouldn't even exist if the condition is blank
- if (condition === "") {
- delete this.conditionalExpression;
- } else {
- this.conditionalExpression = condition;
- }
- deferred.resolve(this);
- }
-
- return deferred.promise;
- }
-};
-
-eventSource(BreakpointClient.prototype);
-
-/**
- * Environment clients are used to manipulate the lexical environment actors.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param form Object
- * The form sent across the remote debugging protocol.
- */
-function EnvironmentClient(client, form) {
- this._client = client;
- this._form = form;
- this.request = this._client.request;
-}
-exports.EnvironmentClient = EnvironmentClient;
-
-EnvironmentClient.prototype = {
-
- get actor() {
- return this._form.actor;
- },
- get _transport() {
- return this._client._transport;
- },
-
- /**
- * Fetches the bindings introduced by this lexical environment.
- */
- getBindings: DebuggerClient.requester({
- type: "bindings"
- }),
-
- /**
- * Changes the value of the identifier whose name is name (a string) to that
- * represented by value (a grip).
- */
- assign: DebuggerClient.requester({
- type: "assign",
- name: arg(0),
- value: arg(1)
- })
-};
-
-eventSource(EnvironmentClient.prototype);
+module.exports = ObjectClient;
copy from devtools/shared/client/main.js
copy to devtools/shared/client/property-iterator-client.js
--- a/devtools/shared/client/main.js
+++ b/devtools/shared/client/property-iterator-client.js
@@ -1,2789 +1,15 @@
-/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
-/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
-const { Ci, Cu } = require("chrome");
-const DevToolsUtils = require("devtools/shared/DevToolsUtils");
-const { getStack, callFunctionWithAsyncStack } = require("devtools/shared/platform/stack");
-
-const promise = Cu.import("resource://devtools/shared/deprecated-sync-thenables.js", {}).Promise;
-
-loader.lazyRequireGetter(this, "EventEmitter", "devtools/shared/event-emitter");
-loader.lazyRequireGetter(this, "WebConsoleClient", "devtools/shared/webconsole/client", true);
-loader.lazyRequireGetter(this, "DebuggerSocket", "devtools/shared/security/socket", true);
-loader.lazyRequireGetter(this, "Authentication", "devtools/shared/security/auth");
-
-const noop = () => {};
-
-/**
- * TODO: Get rid of this API in favor of EventTarget (bug 1042642)
- *
- * Add simple event notification to a prototype object. Any object that has
- * some use for event notifications or the observer pattern in general can be
- * augmented with the necessary facilities by passing its prototype to this
- * function.
- *
- * @param proto object
- * The prototype object that will be modified.
- */
-function eventSource(proto) {
- /**
- * Add a listener to the event source for a given event.
- *
- * @param name string
- * The event to listen for.
- * @param listener function
- * Called when the event is fired. If the same listener
- * is added more than once, it will be called once per
- * addListener call.
- */
- proto.addListener = function (name, listener) {
- if (typeof listener != "function") {
- throw TypeError("Listeners must be functions.");
- }
-
- if (!this._listeners) {
- this._listeners = {};
- }
-
- this._getListeners(name).push(listener);
- };
-
- /**
- * Add a listener to the event source for a given event. The
- * listener will be removed after it is called for the first time.
- *
- * @param name string
- * The event to listen for.
- * @param listener function
- * Called when the event is fired.
- */
- proto.addOneTimeListener = function (name, listener) {
- let l = (...args) => {
- this.removeListener(name, l);
- listener.apply(null, args);
- };
- this.addListener(name, l);
- };
-
- /**
- * Remove a listener from the event source previously added with
- * addListener().
- *
- * @param name string
- * The event name used during addListener to add the listener.
- * @param listener function
- * The callback to remove. If addListener was called multiple
- * times, all instances will be removed.
- */
- proto.removeListener = function (name, listener) {
- if (!this._listeners || (listener && !this._listeners[name])) {
- return;
- }
-
- if (!listener) {
- this._listeners[name] = [];
- } else {
- this._listeners[name] =
- this._listeners[name].filter(l => l != listener);
- }
- };
-
- /**
- * Returns the listeners for the specified event name. If none are defined it
- * initializes an empty list and returns that.
- *
- * @param name string
- * The event name.
- */
- proto._getListeners = function (name) {
- if (name in this._listeners) {
- return this._listeners[name];
- }
- this._listeners[name] = [];
- return this._listeners[name];
- };
-
- /**
- * Notify listeners of an event.
- *
- * @param name string
- * The event to fire.
- * @param arguments
- * All arguments will be passed along to the listeners,
- * including the name argument.
- */
- proto.emit = function () {
- if (!this._listeners) {
- return;
- }
-
- let name = arguments[0];
- let listeners = this._getListeners(name).slice(0);
-
- for (let listener of listeners) {
- try {
- listener.apply(null, arguments);
- } catch (e) {
- // Prevent a bad listener from interfering with the others.
- DevToolsUtils.reportException("notify event '" + name + "'", e);
- }
- }
- };
-}
-
-/**
- * Set of protocol messages that affect thread state, and the
- * state the actor is in after each message.
- */
-const ThreadStateTypes = {
- "paused": "paused",
- "resumed": "attached",
- "detached": "detached",
- "running": "attached"
-};
-
-/**
- * Set of protocol messages that are sent by the server without a prior request
- * by the client.
- */
-const UnsolicitedNotifications = {
- "consoleAPICall": "consoleAPICall",
- "eventNotification": "eventNotification",
- "fileActivity": "fileActivity",
- "lastPrivateContextExited": "lastPrivateContextExited",
- "logMessage": "logMessage",
- "networkEvent": "networkEvent",
- "networkEventUpdate": "networkEventUpdate",
- "newGlobal": "newGlobal",
- "newScript": "newScript",
- "tabDetached": "tabDetached",
- "tabListChanged": "tabListChanged",
- "reflowActivity": "reflowActivity",
- "addonListChanged": "addonListChanged",
- "workerListChanged": "workerListChanged",
- "serviceWorkerRegistrationListChanged": "serviceWorkerRegistrationList",
- "tabNavigated": "tabNavigated",
- "frameUpdate": "frameUpdate",
- "pageError": "pageError",
- "documentLoad": "documentLoad",
- "enteredFrame": "enteredFrame",
- "exitedFrame": "exitedFrame",
- "appOpen": "appOpen",
- "appClose": "appClose",
- "appInstall": "appInstall",
- "appUninstall": "appUninstall",
- "evaluationResult": "evaluationResult",
- "newSource": "newSource",
- "updatedSource": "updatedSource",
- "inspectObject": "inspectObject"
-};
-
-/**
- * Set of pause types that are sent by the server and not as an immediate
- * response to a client request.
- */
-const UnsolicitedPauses = {
- "resumeLimit": "resumeLimit",
- "debuggerStatement": "debuggerStatement",
- "breakpoint": "breakpoint",
- "DOMEvent": "DOMEvent",
- "watchpoint": "watchpoint",
- "exception": "exception"
-};
-
-/**
- * Creates a client for the remote debugging protocol server. This client
- * provides the means to communicate with the server and exchange the messages
- * required by the protocol in a traditional JavaScript API.
- */
-const DebuggerClient = exports.DebuggerClient = function (transport) {
- this._transport = transport;
- this._transport.hooks = this;
-
- // Map actor ID to client instance for each actor type.
- this._clients = new Map();
-
- this._pendingRequests = new Map();
- this._activeRequests = new Map();
- this._eventsEnabled = true;
-
- this.traits = {};
-
- this.request = this.request.bind(this);
- this.localTransport = this._transport.onOutputStreamReady === undefined;
-
- /*
- * As the first thing on the connection, expect a greeting packet from
- * the connection's root actor.
- */
- this.mainRoot = null;
- this.expectReply("root", (packet) => {
- this.mainRoot = new RootClient(this, packet);
- this.emit("connected", packet.applicationType, packet.traits);
- });
-};
-
-/**
- * A declarative helper for defining methods that send requests to the server.
- *
- * @param packetSkeleton
- * The form of the packet to send. Can specify fields to be filled from
- * the parameters by using the |arg| function.
- * @param before
- * The function to call before sending the packet. Is passed the packet,
- * and the return value is used as the new packet. The |this| context is
- * the instance of the client object we are defining a method for.
- * @param after
- * The function to call after the response is received. It is passed the
- * response, and the return value is considered the new response that
- * will be passed to the callback. The |this| context is the instance of
- * the client object we are defining a method for.
- * @return Request
- * The `Request` object that is a Promise object and resolves once
- * we receive the response. (See request method for more details)
- */
-DebuggerClient.requester = function (packetSkeleton, config = {}) {
- let { before, after } = config;
- return DevToolsUtils.makeInfallible(function (...args) {
- let outgoingPacket = {
- to: packetSkeleton.to || this.actor
- };
-
- let maxPosition = -1;
- for (let k of Object.keys(packetSkeleton)) {
- if (packetSkeleton[k] instanceof DebuggerClient.Argument) {
- let { position } = packetSkeleton[k];
- outgoingPacket[k] = packetSkeleton[k].getArgument(args);
- maxPosition = Math.max(position, maxPosition);
- } else {
- outgoingPacket[k] = packetSkeleton[k];
- }
- }
-
- if (before) {
- outgoingPacket = before.call(this, outgoingPacket);
- }
-
- return this.request(outgoingPacket, DevToolsUtils.makeInfallible((response) => {
- if (after) {
- let { from } = response;
- response = after.call(this, response);
- if (!response.from) {
- response.from = from;
- }
- }
-
- // The callback is always the last parameter.
- let thisCallback = args[maxPosition + 1];
- if (thisCallback) {
- thisCallback(response);
- }
- return response;
- }, "DebuggerClient.requester request callback"));
- }, "DebuggerClient.requester");
-};
-
-function arg(pos) {
- return new DebuggerClient.Argument(pos);
-}
-
-DebuggerClient.Argument = function (position) {
- this.position = position;
-};
-
-DebuggerClient.Argument.prototype.getArgument = function (params) {
- if (!(this.position in params)) {
- throw new Error("Bad index into params: " + this.position);
- }
- return params[this.position];
-};
-
-// Expose these to save callers the trouble of importing DebuggerSocket
-DebuggerClient.socketConnect = function (options) {
- // Defined here instead of just copying the function to allow lazy-load
- return DebuggerSocket.connect(options);
-};
-DevToolsUtils.defineLazyGetter(DebuggerClient, "Authenticators", () => {
- return Authentication.Authenticators;
-});
-DevToolsUtils.defineLazyGetter(DebuggerClient, "AuthenticationResult", () => {
- return Authentication.AuthenticationResult;
-});
-
-DebuggerClient.prototype = {
- /**
- * Connect to the server and start exchanging protocol messages.
- *
- * @param onConnected function
- * If specified, will be called when the greeting packet is
- * received from the debugging server.
- *
- * @return Promise
- * Resolves once connected with an array whose first element
- * is the application type, by default "browser", and the second
- * element is the traits object (help figure out the features
- * and behaviors of the server we connect to. See RootActor).
- */
- connect: function (onConnected) {
- let deferred = promise.defer();
- this.emit("connect");
-
- // Also emit the event on the |DebuggerClient| object (not on the instance),
- // so it's possible to track all instances.
- EventEmitter.emit(DebuggerClient, "connect", this);
-
- this.addOneTimeListener("connected", (name, applicationType, traits) => {
- this.traits = traits;
- if (onConnected) {
- onConnected(applicationType, traits);
- }
- deferred.resolve([applicationType, traits]);
- });
-
- this._transport.ready();
- return deferred.promise;
- },
-
- /**
- * Shut down communication with the debugging server.
- *
- * @param onClosed function
- * If specified, will be called when the debugging connection
- * has been closed. This parameter is deprecated - please use
- * the returned Promise.
- * @return Promise
- * Resolves after the underlying transport is closed.
- */
- close: function (onClosed) {
- let deferred = promise.defer();
- if (onClosed) {
- deferred.promise.then(onClosed);
- }
-
- // Disable detach event notifications, because event handlers will be in a
- // cleared scope by the time they run.
- this._eventsEnabled = false;
-
- let cleanup = () => {
- this._transport.close();
- this._transport = null;
- };
-
- // If the connection is already closed,
- // there is no need to detach client
- // as we won't be able to send any message.
- if (this._closed) {
- cleanup();
- deferred.resolve();
- return deferred.promise;
- }
-
- this.addOneTimeListener("closed", deferred.resolve);
-
- // Call each client's `detach` method by calling
- // lastly registered ones first to give a chance
- // to detach child clients first.
- let clients = [...this._clients.values()];
- this._clients.clear();
- const detachClients = () => {
- let client = clients.pop();
- if (!client) {
- // All clients detached.
- cleanup();
- return;
- }
- if (client.detach) {
- client.detach(detachClients);
- return;
- }
- detachClients();
- };
- detachClients();
-
- return deferred.promise;
- },
-
- /*
- * This function exists only to preserve DebuggerClient's interface;
- * new code should say 'client.mainRoot.listTabs()'.
- */
- listTabs: function (onResponse) {
- return this.mainRoot.listTabs(onResponse);
- },
-
- /*
- * This function exists only to preserve DebuggerClient's interface;
- * new code should say 'client.mainRoot.listAddons()'.
- */
- listAddons: function (onResponse) {
- return this.mainRoot.listAddons(onResponse);
- },
-
- getTab: function (filter) {
- return this.mainRoot.getTab(filter);
- },
-
- /**
- * Attach to a tab actor.
- *
- * @param string tabActor
- * The actor ID for the tab to attach.
- * @param function onResponse
- * Called with the response packet and a TabClient
- * (which will be undefined on error).
- */
- attachTab: function (tabActor, onResponse = noop) {
- if (this._clients.has(tabActor)) {
- let cachedTab = this._clients.get(tabActor);
- let cachedResponse = {
- cacheDisabled: cachedTab.cacheDisabled,
- javascriptEnabled: cachedTab.javascriptEnabled,
- traits: cachedTab.traits,
- };
- DevToolsUtils.executeSoon(() => onResponse(cachedResponse, cachedTab));
- return promise.resolve([cachedResponse, cachedTab]);
- }
-
- let packet = {
- to: tabActor,
- type: "attach"
- };
- return this.request(packet).then(response => {
- let tabClient;
- if (!response.error) {
- tabClient = new TabClient(this, response);
- this.registerClient(tabClient);
- }
- onResponse(response, tabClient);
- return [response, tabClient];
- });
- },
-
- attachWorker: function (workerActor, onResponse = noop) {
- let workerClient = this._clients.get(workerActor);
- if (workerClient !== undefined) {
- let response = {
- from: workerClient.actor,
- type: "attached",
- url: workerClient.url
- };
- DevToolsUtils.executeSoon(() => onResponse(response, workerClient));
- return promise.resolve([response, workerClient]);
- }
-
- return this.request({ to: workerActor, type: "attach" }).then(response => {
- if (response.error) {
- onResponse(response, null);
- return [response, null];
- }
-
- workerClient = new WorkerClient(this, response);
- this.registerClient(workerClient);
- onResponse(response, workerClient);
- return [response, workerClient];
- });
- },
-
- /**
- * Attach to an addon actor.
- *
- * @param string addonActor
- * The actor ID for the addon to attach.
- * @param function onResponse
- * Called with the response packet and a AddonClient
- * (which will be undefined on error).
- */
- attachAddon: function (addonActor, onResponse = noop) {
- let packet = {
- to: addonActor,
- type: "attach"
- };
- return this.request(packet).then(response => {
- let addonClient;
- if (!response.error) {
- addonClient = new AddonClient(this, addonActor);
- this.registerClient(addonClient);
- this.activeAddon = addonClient;
- }
- onResponse(response, addonClient);
- return [response, addonClient];
- });
- },
-
- /**
- * Attach to a Web Console actor.
- *
- * @param string consoleActor
- * The ID for the console actor to attach to.
- * @param array listeners
- * The console listeners you want to start.
- * @param function onResponse
- * Called with the response packet and a WebConsoleClient
- * instance (which will be undefined on error).
- */
- attachConsole:
- function (consoleActor, listeners, onResponse = noop) {
- let packet = {
- to: consoleActor,
- type: "startListeners",
- listeners: listeners,
- };
-
- return this.request(packet).then(response => {
- let consoleClient;
- if (!response.error) {
- if (this._clients.has(consoleActor)) {
- consoleClient = this._clients.get(consoleActor);
- } else {
- consoleClient = new WebConsoleClient(this, response);
- this.registerClient(consoleClient);
- }
- }
- onResponse(response, consoleClient);
- return [response, consoleClient];
- });
- },
-
- /**
- * Attach to a global-scoped thread actor for chrome debugging.
- *
- * @param string threadActor
- * The actor ID for the thread to attach.
- * @param function onResponse
- * Called with the response packet and a ThreadClient
- * (which will be undefined on error).
- * @param object options
- * Configuration options.
- * - useSourceMaps: whether to use source maps or not.
- */
- attachThread: function (threadActor, onResponse = noop, options = {}) {
- if (this._clients.has(threadActor)) {
- let client = this._clients.get(threadActor);
- DevToolsUtils.executeSoon(() => onResponse({}, client));
- return promise.resolve([{}, client]);
- }
-
- let packet = {
- to: threadActor,
- type: "attach",
- options,
- };
- return this.request(packet).then(response => {
- let threadClient;
- if (!response.error) {
- threadClient = new ThreadClient(this, threadActor);
- this.registerClient(threadClient);
- }
- onResponse(response, threadClient);
- return [response, threadClient];
- });
- },
-
- /**
- * Attach to a trace actor.
- *
- * @param string traceActor
- * The actor ID for the tracer to attach.
- * @param function onResponse
- * Called with the response packet and a TraceClient
- * (which will be undefined on error).
- */
- attachTracer: function (traceActor, onResponse = noop) {
- if (this._clients.has(traceActor)) {
- let client = this._clients.get(traceActor);
- DevToolsUtils.executeSoon(() => onResponse({}, client));
- return promise.resolve([{}, client]);
- }
-
- let packet = {
- to: traceActor,
- type: "attach"
- };
- return this.request(packet).then(response => {
- let traceClient;
- if (!response.error) {
- traceClient = new TraceClient(this, traceActor);
- this.registerClient(traceClient);
- }
- onResponse(response, traceClient);
- return [response, traceClient];
- });
- },
-
- /**
- * Fetch the ChromeActor for the main process or ChildProcessActor for a
- * a given child process ID.
- *
- * @param number id
- * The ID for the process to attach (returned by `listProcesses`).
- * Connected to the main process if omitted, or is 0.
- */
- getProcess: function (id) {
- let packet = {
- to: "root",
- type: "getProcess"
- };
- if (typeof (id) == "number") {
- packet.id = id;
- }
- return this.request(packet);
- },
-
- /**
- * Release an object actor.
- *
- * @param string actor
- * The actor ID to send the request to.
- * @param onResponse function
- * If specified, will be called with the response packet when
- * debugging server responds.
- */
- release: DebuggerClient.requester({
- to: arg(0),
- type: "release"
- }),
-
- /**
- * Send a request to the debugging server.
- *
- * @param packet object
- * A JSON packet to send to the debugging server.
- * @param onResponse function
- * If specified, will be called with the JSON response packet when
- * debugging server responds.
- * @return Request
- * This object emits a number of events to allow you to respond to
- * different parts of the request lifecycle.
- * It is also a Promise object, with a `then` method, that is resolved
- * whenever a JSON or a Bulk response is received; and is rejected
- * if the response is an error.
- * Note: This return value can be ignored if you are using JSON alone,
- * because the callback provided in |onResponse| will be bound to the
- * "json-reply" event automatically.
- *
- * Events emitted:
- * * json-reply: The server replied with a JSON packet, which is
- * passed as event data.
- * * bulk-reply: The server replied with bulk data, which you can read
- * using the event data object containing:
- * * actor: Name of actor that received the packet
- * * type: Name of actor's method that was called on receipt
- * * length: Size of the data to be read
- * * stream: This input stream should only be used directly if you
- * can ensure that you will read exactly |length| bytes
- * and will not close the stream when reading is complete
- * * done: If you use the stream directly (instead of |copyTo|
- * below), you must signal completion by resolving /
- * rejecting this deferred. If it's rejected, the
- * transport will be closed. If an Error is supplied as a
- * rejection value, it will be logged via |dumpn|. If you
- * do use |copyTo|, resolving is taken care of for you
- * when copying completes.
- * * copyTo: A helper function for getting your data out of the
- * stream that meets the stream handling requirements
- * above, and has the following signature:
- * @param output nsIAsyncOutputStream
- * The stream to copy to.
- * @return Promise
- * The promise is resolved when copying completes or
- * rejected if any (unexpected) errors occur.
- * This object also emits "progress" events for each chunk
- * that is copied. See stream-utils.js.
- */
- request: function (packet, onResponse) {
- if (!this.mainRoot) {
- throw Error("Have not yet received a hello packet from the server.");
- }
- let type = packet.type || "";
- if (!packet.to) {
- throw Error("'" + type + "' request packet has no destination.");
- }
-
- // The onResponse callback might modify the response, so we need to call
- // it and resolve the promise with its result if it's truthy.
- const safeOnResponse = response => {
- if (!onResponse) {
- return response;
- }
- return onResponse(response) || response;
- };
-
- if (this._closed) {
- let msg = "'" + type + "' request packet to " +
- "'" + packet.to + "' " +
- "can't be sent as the connection is closed.";
- let resp = { error: "connectionClosed", message: msg };
- return promise.reject(safeOnResponse(resp));
- }
-
- let request = new Request(packet);
- request.format = "json";
- request.stack = getStack();
-
- // Implement a Promise like API on the returned object
- // that resolves/rejects on request response
- let deferred = promise.defer();
- function listenerJson(resp) {
- removeRequestListeners();
- if (resp.error) {
- deferred.reject(safeOnResponse(resp));
- } else {
- deferred.resolve(safeOnResponse(resp));
- }
- }
- function listenerBulk(resp) {
- removeRequestListeners();
- deferred.resolve(safeOnResponse(resp));
- }
-
- const removeRequestListeners = () => {
- request.off("json-reply", listenerJson);
- request.off("bulk-reply", listenerBulk);
- };
-
- request.on("json-reply", listenerJson);
- request.on("bulk-reply", listenerBulk);
-
- this._sendOrQueueRequest(request);
- request.then = deferred.promise.then.bind(deferred.promise);
-
- return request;
- },
-
- /**
- * Transmit streaming data via a bulk request.
- *
- * This method initiates the bulk send process by queuing up the header data.
- * The caller receives eventual access to a stream for writing.
- *
- * Since this opens up more options for how the server might respond (it could
- * send back either JSON or bulk data), and the returned Request object emits
- * events for different stages of the request process that you may want to
- * react to.
- *
- * @param request Object
- * This is modeled after the format of JSON packets above, but does not
- * actually contain the data, but is instead just a routing header:
- * * actor: Name of actor that will receive the packet
- * * type: Name of actor's method that should be called on receipt
- * * length: Size of the data to be sent
- * @return Request
- * This object emits a number of events to allow you to respond to
- * different parts of the request lifecycle.
- *
- * Events emitted:
- * * bulk-send-ready: Ready to send bulk data to the server, using the
- * event data object containing:
- * * stream: This output stream should only be used directly if
- * you can ensure that you will write exactly |length|
- * bytes and will not close the stream when writing is
- * complete
- * * done: If you use the stream directly (instead of |copyFrom|
- * below), you must signal completion by resolving /
- * rejecting this deferred. If it's rejected, the
- * transport will be closed. If an Error is supplied as
- * a rejection value, it will be logged via |dumpn|. If
- * you do use |copyFrom|, resolving is taken care of for
- * you when copying completes.
- * * copyFrom: A helper function for getting your data onto the
- * stream that meets the stream handling requirements
- * above, and has the following signature:
- * @param input nsIAsyncInputStream
- * The stream to copy from.
- * @return Promise
- * The promise is resolved when copying completes or
- * rejected if any (unexpected) errors occur.
- * This object also emits "progress" events for each chunk
- * that is copied. See stream-utils.js.
- * * json-reply: The server replied with a JSON packet, which is
- * passed as event data.
- * * bulk-reply: The server replied with bulk data, which you can read
- * using the event data object containing:
- * * actor: Name of actor that received the packet
- * * type: Name of actor's method that was called on receipt
- * * length: Size of the data to be read
- * * stream: This input stream should only be used directly if you
- * can ensure that you will read exactly |length| bytes
- * and will not close the stream when reading is complete
- * * done: If you use the stream directly (instead of |copyTo|
- * below), you must signal completion by resolving /
- * rejecting this deferred. If it's rejected, the
- * transport will be closed. If an Error is supplied as a
- * rejection value, it will be logged via |dumpn|. If you
- * do use |copyTo|, resolving is taken care of for you
- * when copying completes.
- * * copyTo: A helper function for getting your data out of the
- * stream that meets the stream handling requirements
- * above, and has the following signature:
- * @param output nsIAsyncOutputStream
- * The stream to copy to.
- * @return Promise
- * The promise is resolved when copying completes or
- * rejected if any (unexpected) errors occur.
- * This object also emits "progress" events for each chunk
- * that is copied. See stream-utils.js.
- */
- startBulkRequest: function (request) {
- if (!this.traits.bulk) {
- throw Error("Server doesn't support bulk transfers");
- }
- if (!this.mainRoot) {
- throw Error("Have not yet received a hello packet from the server.");
- }
- if (!request.type) {
- throw Error("Bulk packet is missing the required 'type' field.");
- }
- if (!request.actor) {
- throw Error("'" + request.type + "' bulk packet has no destination.");
- }
- if (!request.length) {
- throw Error("'" + request.type + "' bulk packet has no length.");
- }
-
- request = new Request(request);
- request.format = "bulk";
-
- this._sendOrQueueRequest(request);
-
- return request;
- },
-
- /**
- * If a new request can be sent immediately, do so. Otherwise, queue it.
- */
- _sendOrQueueRequest(request) {
- let actor = request.actor;
- if (!this._activeRequests.has(actor)) {
- this._sendRequest(request);
- } else {
- this._queueRequest(request);
- }
- },
-
- /**
- * Send a request.
- * @throws Error if there is already an active request in flight for the same
- * actor.
- */
- _sendRequest(request) {
- let actor = request.actor;
- this.expectReply(actor, request);
-
- if (request.format === "json") {
- this._transport.send(request.request);
- return;
- }
-
- this._transport.startBulkSend(request.request).then((...args) => {
- request.emit("bulk-send-ready", ...args);
- });
- },
-
- /**
- * Queue a request to be sent later. Queues are only drained when an in
- * flight request to a given actor completes.
- */
- _queueRequest(request) {
- let actor = request.actor;
- let queue = this._pendingRequests.get(actor) || [];
- queue.push(request);
- this._pendingRequests.set(actor, queue);
- },
-
- /**
- * Attempt the next request to a given actor (if any).
- */
- _attemptNextRequest(actor) {
- if (this._activeRequests.has(actor)) {
- return;
- }
- let queue = this._pendingRequests.get(actor);
- if (!queue) {
- return;
- }
- let request = queue.shift();
- if (queue.length === 0) {
- this._pendingRequests.delete(actor);
- }
- this._sendRequest(request);
- },
-
- /**
- * Arrange to hand the next reply from |actor| to the handler bound to
- * |request|.
- *
- * DebuggerClient.prototype.request / startBulkRequest usually takes care of
- * establishing the handler for a given request, but in rare cases (well,
- * greetings from new root actors, is the only case at the moment) we must be
- * prepared for a "reply" that doesn't correspond to any request we sent.
- */
- expectReply: function (actor, request) {
- if (this._activeRequests.has(actor)) {
- throw Error("clashing handlers for next reply from " + actor);
- }
-
- // If a handler is passed directly (as it is with the handler for the root
- // actor greeting), create a dummy request to bind this to.
- if (typeof request === "function") {
- let handler = request;
- request = new Request();
- request.on("json-reply", handler);
- }
-
- this._activeRequests.set(actor, request);
- },
-
- // Transport hooks.
-
- /**
- * Called by DebuggerTransport to dispatch incoming packets as appropriate.
- *
- * @param packet object
- * The incoming packet.
- */
- onPacket: function (packet) {
- if (!packet.from) {
- DevToolsUtils.reportException(
- "onPacket",
- new Error("Server did not specify an actor, dropping packet: " +
- JSON.stringify(packet)));
- return;
- }
-
- // If we have a registered Front for this actor, let it handle the packet
- // and skip all the rest of this unpleasantness.
- let front = this.getActor(packet.from);
- if (front) {
- front.onPacket(packet);
- return;
- }
-
- // Check for "forwardingCancelled" here instead of using a client to handle it.
- // This is necessary because we might receive this event while the client is closing,
- // and the clients have already been removed by that point.
- if (this.mainRoot &&
- packet.from == this.mainRoot.actor &&
- packet.type == "forwardingCancelled") {
- this.purgeRequests(packet.prefix);
- return;
- }
-
- if (this._clients.has(packet.from) && packet.type) {
- let client = this._clients.get(packet.from);
- let type = packet.type;
- if (client.events.indexOf(type) != -1) {
- client.emit(type, packet);
- // we ignore the rest, as the client is expected to handle this packet.
- return;
- }
- }
-
- let activeRequest;
- // See if we have a handler function waiting for a reply from this
- // actor. (Don't count unsolicited notifications or pauses as
- // replies.)
- if (this._activeRequests.has(packet.from) &&
- !(packet.type in UnsolicitedNotifications) &&
- !(packet.type == ThreadStateTypes.paused &&
- packet.why.type in UnsolicitedPauses)) {
- activeRequest = this._activeRequests.get(packet.from);
- this._activeRequests.delete(packet.from);
- }
-
- // If there is a subsequent request for the same actor, hand it off to the
- // transport. Delivery of packets on the other end is always async, even
- // in the local transport case.
- this._attemptNextRequest(packet.from);
-
- // Packets that indicate thread state changes get special treatment.
- if (packet.type in ThreadStateTypes &&
- this._clients.has(packet.from) &&
- typeof this._clients.get(packet.from)._onThreadState == "function") {
- this._clients.get(packet.from)._onThreadState(packet);
- }
-
- // TODO: Bug 1151156 - Remove once Gecko 40 is on b2g-stable.
- if (!this.traits.noNeedToFakeResumptionOnNavigation) {
- // On navigation the server resumes, so the client must resume as well.
- // We achieve that by generating a fake resumption packet that triggers
- // the client's thread state change listeners.
- if (packet.type == UnsolicitedNotifications.tabNavigated &&
- this._clients.has(packet.from) &&
- this._clients.get(packet.from).thread) {
- let thread = this._clients.get(packet.from).thread;
- let resumption = { from: thread._actor, type: "resumed" };
- thread._onThreadState(resumption);
- }
- }
-
- // Only try to notify listeners on events, not responses to requests
- // that lack a packet type.
- if (packet.type) {
- this.emit(packet.type, packet);
- }
-
- if (activeRequest) {
- let emitReply = () => activeRequest.emit("json-reply", packet);
- if (activeRequest.stack) {
- callFunctionWithAsyncStack(emitReply, activeRequest.stack,
- "DevTools RDP");
- } else {
- emitReply();
- }
- }
- },
-
- /**
- * Called by the DebuggerTransport to dispatch incoming bulk packets as
- * appropriate.
- *
- * @param packet object
- * The incoming packet, which contains:
- * * actor: Name of actor that will receive the packet
- * * type: Name of actor's method that should be called on receipt
- * * length: Size of the data to be read
- * * stream: This input stream should only be used directly if you can
- * ensure that you will read exactly |length| bytes and will
- * not close the stream when reading is complete
- * * done: If you use the stream directly (instead of |copyTo|
- * below), you must signal completion by resolving /
- * rejecting this deferred. If it's rejected, the transport
- * will be closed. If an Error is supplied as a rejection
- * value, it will be logged via |dumpn|. If you do use
- * |copyTo|, resolving is taken care of for you when copying
- * completes.
- * * copyTo: A helper function for getting your data out of the stream
- * that meets the stream handling requirements above, and has
- * the following signature:
- * @param output nsIAsyncOutputStream
- * The stream to copy to.
- * @return Promise
- * The promise is resolved when copying completes or rejected
- * if any (unexpected) errors occur.
- * This object also emits "progress" events for each chunk
- * that is copied. See stream-utils.js.
- */
- onBulkPacket: function (packet) {
- let { actor } = packet;
-
- if (!actor) {
- DevToolsUtils.reportException(
- "onBulkPacket",
- new Error("Server did not specify an actor, dropping bulk packet: " +
- JSON.stringify(packet)));
- return;
- }
-
- // See if we have a handler function waiting for a reply from this
- // actor.
- if (!this._activeRequests.has(actor)) {
- return;
- }
-
- let activeRequest = this._activeRequests.get(actor);
- this._activeRequests.delete(actor);
-
- // If there is a subsequent request for the same actor, hand it off to the
- // transport. Delivery of packets on the other end is always async, even
- // in the local transport case.
- this._attemptNextRequest(actor);
-
- activeRequest.emit("bulk-reply", packet);
- },
-
- /**
- * Called by DebuggerTransport when the underlying stream is closed.
- *
- * @param status nsresult
- * The status code that corresponds to the reason for closing
- * the stream.
- */
- onClosed: function () {
- this._closed = true;
- this.emit("closed");
-
- this.purgeRequests();
-
- // The |_pools| array on the client-side currently is used only by
- // protocol.js to store active fronts, mirroring the actor pools found in
- // the server. So, read all usages of "pool" as "protocol.js front".
- //
- // In the normal case where we shutdown cleanly, the toolbox tells each tool
- // to close, and they each call |destroy| on any fronts they were using.
- // When |destroy| or |cleanup| is called on a protocol.js front, it also
- // removes itself from the |_pools| array. Once the toolbox has shutdown,
- // the connection is closed, and we reach here. All fronts (should have
- // been) |destroy|ed, so |_pools| should empty.
- //
- // If the connection instead aborts unexpectedly, we may end up here with
- // all fronts used during the life of the connection. So, we call |cleanup|
- // on them clear their state, reject pending requests, and remove themselves
- // from |_pools|. This saves the toolbox from hanging indefinitely, in case
- // it waits for some server response before shutdown that will now never
- // arrive.
- for (let pool of this._pools) {
- pool.cleanup();
- }
- },
-
- /**
- * Purge pending and active requests in this client.
- *
- * @param prefix string (optional)
- * If a prefix is given, only requests for actor IDs that start with the prefix
- * will be cleaned up. This is useful when forwarding of a portion of requests
- * is cancelled on the server.
- */
- purgeRequests(prefix = "") {
- let reject = function (type, request) {
- // Server can send packets on its own and client only pass a callback
- // to expectReply, so that there is no request object.
- let msg;
- if (request.request) {
- msg = "'" + request.request.type + "' " + type + " request packet" +
- " to '" + request.actor + "' " +
- "can't be sent as the connection just closed.";
- } else {
- msg = "server side packet can't be received as the connection just closed.";
- }
- let packet = { error: "connectionClosed", message: msg };
- request.emit("json-reply", packet);
- };
-
- let pendingRequestsToReject = [];
- this._pendingRequests.forEach((requests, actor) => {
- if (!actor.startsWith(prefix)) {
- return;
- }
- this._pendingRequests.delete(actor);
- pendingRequestsToReject = pendingRequestsToReject.concat(requests);
- });
- pendingRequestsToReject.forEach(request => reject("pending", request));
-
- let activeRequestsToReject = [];
- this._activeRequests.forEach((request, actor) => {
- if (!actor.startsWith(prefix)) {
- return;
- }
- this._activeRequests.delete(actor);
- activeRequestsToReject = activeRequestsToReject.concat(request);
- });
- activeRequestsToReject.forEach(request => reject("active", request));
- },
-
- /**
- * Search for all requests in process for this client, including those made via
- * protocol.js and wait all of them to complete. Since the requests seen when this is
- * first called may in turn trigger more requests, we keep recursing through this
- * function until there is no more activity.
- *
- * This is a fairly heavy weight process, so it's only meant to be used in tests.
- *
- * @return Promise
- * Resolved when all requests have settled.
- */
- waitForRequestsToSettle() {
- let requests = [];
-
- // Gather all pending and active requests in this client
- // The request object supports a Promise API for completion (it has .then())
- this._pendingRequests.forEach(requestsForActor => {
- // Each value is an array of pending requests
- requests = requests.concat(requestsForActor);
- });
- this._activeRequests.forEach(requestForActor => {
- // Each value is a single active request
- requests = requests.concat(requestForActor);
- });
-
- // protocol.js
- // Use a Set because some fronts (like domwalker) seem to have multiple parents.
- let fronts = new Set();
- let poolsToVisit = [...this._pools];
-
- // With protocol.js, each front can potentially have it's own pools containing child
- // fronts, forming a tree. Descend through all the pools to locate all child fronts.
- while (poolsToVisit.length) {
- let pool = poolsToVisit.shift();
- fronts.add(pool);
- for (let child of pool.poolChildren()) {
- poolsToVisit.push(child);
- }
- }
-
- // For each front, wait for its requests to settle
- for (let front of fronts) {
- if (front.hasRequests()) {
- requests.push(front.waitForRequestsToSettle());
- }
- }
-
- // Abort early if there are no requests
- if (!requests.length) {
- return Promise.resolve();
- }
-
- return DevToolsUtils.settleAll(requests).catch(() => {
- // One of the requests might have failed, but ignore that situation here and pipe
- // both success and failure through the same path. The important part is just that
- // we waited.
- }).then(() => {
- // Repeat, more requests may have started in response to those we just waited for
- return this.waitForRequestsToSettle();
- });
- },
-
- registerClient: function (client) {
- let actorID = client.actor;
- if (!actorID) {
- throw new Error("DebuggerServer.registerClient expects " +
- "a client instance with an `actor` attribute.");
- }
- if (!Array.isArray(client.events)) {
- throw new Error("DebuggerServer.registerClient expects " +
- "a client instance with an `events` attribute " +
- "that is an array.");
- }
- if (client.events.length > 0 && typeof (client.emit) != "function") {
- throw new Error("DebuggerServer.registerClient expects " +
- "a client instance with non-empty `events` array to" +
- "have an `emit` function.");
- }
- if (this._clients.has(actorID)) {
- throw new Error("DebuggerServer.registerClient already registered " +
- "a client for this actor.");
- }
- this._clients.set(actorID, client);
- },
-
- unregisterClient: function (client) {
- let actorID = client.actor;
- if (!actorID) {
- throw new Error("DebuggerServer.unregisterClient expects " +
- "a Client instance with a `actor` attribute.");
- }
- this._clients.delete(actorID);
- },
-
- /**
- * Actor lifetime management, echos the server's actor pools.
- */
- __pools: null,
- get _pools() {
- if (this.__pools) {
- return this.__pools;
- }
- this.__pools = new Set();
- return this.__pools;
- },
-
- addActorPool: function (pool) {
- this._pools.add(pool);
- },
- removeActorPool: function (pool) {
- this._pools.delete(pool);
- },
- getActor: function (actorID) {
- let pool = this.poolFor(actorID);
- return pool ? pool.get(actorID) : null;
- },
-
- poolFor: function (actorID) {
- for (let pool of this._pools) {
- if (pool.has(actorID)) {
- return pool;
- }
- }
- return null;
- },
-
- /**
- * Currently attached addon.
- */
- activeAddon: null
-};
-
-eventSource(DebuggerClient.prototype);
-
-class Request extends EventEmitter {
- constructor(request) {
- super();
- this.request = request;
- }
-
- get actor() {
- return this.request.to || this.request.actor;
- }
-}
-
-/**
- * Creates a tab client for the remote debugging protocol server. This client
- * is a front to the tab actor created in the server side, hiding the protocol
- * details in a traditional JavaScript API.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param form object
- * The protocol form for this tab.
- */
-function TabClient(client, form) {
- this.client = client;
- this._actor = form.from;
- this._threadActor = form.threadActor;
- this.javascriptEnabled = form.javascriptEnabled;
- this.cacheDisabled = form.cacheDisabled;
- this.thread = null;
- this.request = this.client.request;
- this.traits = form.traits || {};
- this.events = ["workerListChanged"];
-}
-
-TabClient.prototype = {
- get actor() {
- return this._actor;
- },
- get _transport() {
- return this.client._transport;
- },
-
- /**
- * Attach to a thread actor.
- *
- * @param object options
- * Configuration options.
- * - useSourceMaps: whether to use source maps or not.
- * @param function onResponse
- * Called with the response packet and a ThreadClient
- * (which will be undefined on error).
- */
- attachThread: function (options = {}, onResponse = noop) {
- if (this.thread) {
- DevToolsUtils.executeSoon(() => onResponse({}, this.thread));
- return promise.resolve([{}, this.thread]);
- }
-
- let packet = {
- to: this._threadActor,
- type: "attach",
- options,
- };
- return this.request(packet).then(response => {
- if (!response.error) {
- this.thread = new ThreadClient(this, this._threadActor);
- this.client.registerClient(this.thread);
- }
- onResponse(response, this.thread);
- return [response, this.thread];
- });
- },
-
- /**
- * Detach the client from the tab actor.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- detach: DebuggerClient.requester({
- type: "detach"
- }, {
- before: function (packet) {
- if (this.thread) {
- this.thread.detach();
- }
- return packet;
- },
- after: function (response) {
- this.client.unregisterClient(this);
- return response;
- },
- }),
-
- /**
- * Bring the window to the front.
- */
- focus: DebuggerClient.requester({
- type: "focus"
- }, {}),
-
- /**
- * Reload the page in this tab.
- *
- * @param [optional] object options
- * An object with a `force` property indicating whether or not
- * this reload should skip the cache
- */
- reload: function (options = { force: false }) {
- return this._reload(options);
- },
- _reload: DebuggerClient.requester({
- type: "reload",
- options: arg(0)
- }),
-
- /**
- * Navigate to another URL.
- *
- * @param string url
- * The URL to navigate to.
- */
- navigateTo: DebuggerClient.requester({
- type: "navigateTo",
- url: arg(0)
- }),
-
- /**
- * Reconfigure the tab actor.
- *
- * @param object options
- * A dictionary object of the new options to use in the tab actor.
- * @param function onResponse
- * Called with the response packet.
- */
- reconfigure: DebuggerClient.requester({
- type: "reconfigure",
- options: arg(0)
- }),
-
- listWorkers: DebuggerClient.requester({
- type: "listWorkers"
- }),
-
- attachWorker: function (workerActor, onResponse) {
- return this.client.attachWorker(workerActor, onResponse);
- },
-};
-
-eventSource(TabClient.prototype);
-
-function WorkerClient(client, form) {
- this.client = client;
- this._actor = form.from;
- this._isClosed = false;
- this._url = form.url;
-
- this._onClose = this._onClose.bind(this);
-
- this.addListener("close", this._onClose);
-
- this.traits = {};
-}
-
-WorkerClient.prototype = {
- get _transport() {
- return this.client._transport;
- },
-
- get request() {
- return this.client.request;
- },
-
- get actor() {
- return this._actor;
- },
-
- get url() {
- return this._url;
- },
-
- get isClosed() {
- return this._isClosed;
- },
-
- detach: DebuggerClient.requester({ type: "detach" }, {
- after: function (response) {
- if (this.thread) {
- this.client.unregisterClient(this.thread);
- }
- this.client.unregisterClient(this);
- return response;
- },
- }),
-
- attachThread: function (options = {}, onResponse = noop) {
- if (this.thread) {
- let response = [{
- type: "connected",
- threadActor: this.thread._actor,
- consoleActor: this.consoleActor,
- }, this.thread];
- DevToolsUtils.executeSoon(() => onResponse(response));
- return response;
- }
-
- // The connect call on server doesn't attach the thread as of version 44.
- return this.request({
- to: this._actor,
- type: "connect",
- options,
- }).then(connectResponse => {
- if (connectResponse.error) {
- onResponse(connectResponse, null);
- return [connectResponse, null];
- }
-
- return this.request({
- to: connectResponse.threadActor,
- type: "attach",
- options,
- }).then(attachResponse => {
- if (attachResponse.error) {
- onResponse(attachResponse, null);
- }
-
- this.thread = new ThreadClient(this, connectResponse.threadActor);
- this.consoleActor = connectResponse.consoleActor;
- this.client.registerClient(this.thread);
-
- onResponse(connectResponse, this.thread);
- return [connectResponse, this.thread];
- });
- }, error => {
- onResponse(error, null);
- });
- },
-
- _onClose: function () {
- this.removeListener("close", this._onClose);
-
- if (this.thread) {
- this.client.unregisterClient(this.thread);
- }
- this.client.unregisterClient(this);
- this._isClosed = true;
- },
-
- reconfigure: function () {
- return Promise.resolve();
- },
-
- events: ["close"]
-};
-
-eventSource(WorkerClient.prototype);
-
-function AddonClient(client, actor) {
- this._client = client;
- this._actor = actor;
- this.request = this._client.request;
- this.events = [];
-}
-
-AddonClient.prototype = {
- get actor() {
- return this._actor;
- },
- get _transport() {
- return this._client._transport;
- },
-
- /**
- * Detach the client from the addon actor.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- detach: DebuggerClient.requester({
- type: "detach"
- }, {
- after: function (response) {
- if (this._client.activeAddon === this) {
- this._client.activeAddon = null;
- }
- this._client.unregisterClient(this);
- return response;
- },
- })
-};
-
-/**
- * A RootClient object represents a root actor on the server. Each
- * DebuggerClient keeps a RootClient instance representing the root actor
- * for the initial connection; DebuggerClient's 'listTabs' and
- * 'listChildProcesses' methods forward to that root actor.
- *
- * @param client object
- * The client connection to which this actor belongs.
- * @param greeting string
- * The greeting packet from the root actor we're to represent.
- *
- * Properties of a RootClient instance:
- *
- * @property actor string
- * The name of this child's root actor.
- * @property applicationType string
- * The application type, as given in the root actor's greeting packet.
- * @property traits object
- * The traits object, as given in the root actor's greeting packet.
- */
-function RootClient(client, greeting) {
- this._client = client;
- this.actor = greeting.from;
- this.applicationType = greeting.applicationType;
- this.traits = greeting.traits;
-}
-exports.RootClient = RootClient;
-
-RootClient.prototype = {
- constructor: RootClient,
-
- /**
- * Gets the "root" form, which lists all the global actors that affect the entire
- * browser. This can replace usages of `listTabs` that only wanted the global actors
- * and didn't actually care about tabs.
- */
- getRoot: DebuggerClient.requester({ type: "getRoot" }),
-
- /**
- * List the open tabs.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- listTabs: DebuggerClient.requester({ type: "listTabs" }),
-
- /**
- * List the installed addons.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- listAddons: DebuggerClient.requester({ type: "listAddons" }),
-
- /**
- * List the registered workers.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- listWorkers: DebuggerClient.requester({ type: "listWorkers" }),
-
- /**
- * List the registered service workers.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- listServiceWorkerRegistrations: DebuggerClient.requester({
- type: "listServiceWorkerRegistrations"
- }),
-
- /**
- * List the running processes.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- listProcesses: DebuggerClient.requester({ type: "listProcesses" }),
-
- /**
- * Fetch the TabActor for the currently selected tab, or for a specific
- * tab given as first parameter.
- *
- * @param [optional] object filter
- * A dictionary object with following optional attributes:
- * - outerWindowID: used to match tabs in parent process
- * - tabId: used to match tabs in child processes
- * - tab: a reference to xul:tab element
- * If nothing is specified, returns the actor for the currently
- * selected tab.
- */
- getTab: function (filter) {
- let packet = {
- to: this.actor,
- type: "getTab"
- };
-
- if (filter) {
- if (typeof (filter.outerWindowID) == "number") {
- packet.outerWindowID = filter.outerWindowID;
- } else if (typeof (filter.tabId) == "number") {
- packet.tabId = filter.tabId;
- } else if ("tab" in filter) {
- let browser = filter.tab.linkedBrowser;
- if (browser.frameLoader.tabParent) {
- // Tabs in child process
- packet.tabId = browser.frameLoader.tabParent.tabId;
- } else if (browser.outerWindowID) {
- // <xul:browser> tabs in parent process
- packet.outerWindowID = browser.outerWindowID;
- } else {
- // <iframe mozbrowser> tabs in parent process
- let windowUtils = browser.contentWindow
- .QueryInterface(Ci.nsIInterfaceRequestor)
- .getInterface(Ci.nsIDOMWindowUtils);
- packet.outerWindowID = windowUtils.outerWindowID;
- }
- } else {
- // Throw if a filter object have been passed but without
- // any clearly idenfified filter.
- throw new Error("Unsupported argument given to getTab request");
- }
- }
-
- return this.request(packet);
- },
-
- /**
- * Fetch the WindowActor for a specific window, like a browser window in
- * Firefox, but it can be used to reach any window in the process.
- *
- * @param number outerWindowID
- * The outerWindowID of the top level window you are looking for.
- */
- getWindow: function ({ outerWindowID }) {
- if (!outerWindowID) {
- throw new Error("Must specify outerWindowID");
- }
-
- let packet = {
- to: this.actor,
- type: "getWindow",
- outerWindowID,
- };
-
- return this.request(packet);
- },
-
- /**
- * Description of protocol's actors and methods.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- protocolDescription: DebuggerClient.requester({ type: "protocolDescription" }),
-
- /*
- * Methods constructed by DebuggerClient.requester require these forwards
- * on their 'this'.
- */
- get _transport() {
- return this._client._transport;
- },
- get request() {
- return this._client.request;
- }
-};
-
-/**
- * Creates a thread client for the remote debugging protocol server. This client
- * is a front to the thread actor created in the server side, hiding the
- * protocol details in a traditional JavaScript API.
- *
- * @param client DebuggerClient|TabClient
- * The parent of the thread (tab for tab-scoped debuggers, DebuggerClient
- * for chrome debuggers).
- * @param actor string
- * The actor ID for this thread.
- */
-function ThreadClient(client, actor) {
- this._parent = client;
- this.client = client instanceof DebuggerClient ? client : client.client;
- this._actor = actor;
- this._frameCache = [];
- this._scriptCache = {};
- this._pauseGrips = {};
- this._threadGrips = {};
- this.request = this.client.request;
-}
-
-ThreadClient.prototype = {
- _state: "paused",
- get state() {
- return this._state;
- },
- get paused() {
- return this._state === "paused";
- },
-
- _pauseOnExceptions: false,
- _ignoreCaughtExceptions: false,
- _pauseOnDOMEvents: null,
-
- _actor: null,
- get actor() {
- return this._actor;
- },
-
- get _transport() {
- return this.client._transport;
- },
-
- _assertPaused: function (command) {
- if (!this.paused) {
- throw Error(command + " command sent while not paused. Currently " + this._state);
- }
- },
-
- /**
- * Resume a paused thread. If the optional limit parameter is present, then
- * the thread will also pause when that limit is reached.
- *
- * @param [optional] object limit
- * An object with a type property set to the appropriate limit (next,
- * step, or finish) per the remote debugging protocol specification.
- * Use null to specify no limit.
- * @param function onResponse
- * Called with the response packet.
- */
- _doResume: DebuggerClient.requester({
- type: "resume",
- resumeLimit: arg(0)
- }, {
- before: function (packet) {
- this._assertPaused("resume");
-
- // Put the client in a tentative "resuming" state so we can prevent
- // further requests that should only be sent in the paused state.
- this._previousState = this._state;
- this._state = "resuming";
-
- if (this._pauseOnExceptions) {
- packet.pauseOnExceptions = this._pauseOnExceptions;
- }
- if (this._ignoreCaughtExceptions) {
- packet.ignoreCaughtExceptions = this._ignoreCaughtExceptions;
- }
- if (this._pauseOnDOMEvents) {
- packet.pauseOnDOMEvents = this._pauseOnDOMEvents;
- }
- return packet;
- },
- after: function (response) {
- if (response.error && this._state == "resuming") {
- // There was an error resuming, update the state to the new one
- // reported by the server, if given (only on wrongState), otherwise
- // reset back to the previous state.
- if (response.state) {
- this._state = ThreadStateTypes[response.state];
- } else {
- this._state = this._previousState;
- }
- }
- delete this._previousState;
- return response;
- },
- }),
-
- /**
- * Reconfigure the thread actor.
- *
- * @param object options
- * A dictionary object of the new options to use in the thread actor.
- * @param function onResponse
- * Called with the response packet.
- */
- reconfigure: DebuggerClient.requester({
- type: "reconfigure",
- options: arg(0)
- }),
-
- /**
- * Resume a paused thread.
- */
- resume: function (onResponse) {
- return this._doResume(null, onResponse);
- },
-
- /**
- * Resume then pause without stepping.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- resumeThenPause: function (onResponse) {
- return this._doResume({ type: "break" }, onResponse);
- },
-
- /**
- * Step over a function call.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- stepOver: function (onResponse) {
- return this._doResume({ type: "next" }, onResponse);
- },
-
- /**
- * Step into a function call.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- stepIn: function (onResponse) {
- return this._doResume({ type: "step" }, onResponse);
- },
-
- /**
- * Step out of a function call.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- stepOut: function (onResponse) {
- return this._doResume({ type: "finish" }, onResponse);
- },
-
- /**
- * Immediately interrupt a running thread.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- interrupt: function (onResponse) {
- return this._doInterrupt(null, onResponse);
- },
-
- /**
- * Pause execution right before the next JavaScript bytecode is executed.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- breakOnNext: function (onResponse) {
- return this._doInterrupt("onNext", onResponse);
- },
-
- /**
- * Interrupt a running thread.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- _doInterrupt: DebuggerClient.requester({
- type: "interrupt",
- when: arg(0)
- }),
-
- /**
- * Enable or disable pausing when an exception is thrown.
- *
- * @param boolean pauseOnExceptions
- * Enables pausing if true, disables otherwise.
- * @param boolean ignoreCaughtExceptions
- * Whether to ignore caught exceptions
- * @param function onResponse
- * Called with the response packet.
- */
- pauseOnExceptions: function (pauseOnExceptions,
- ignoreCaughtExceptions,
- onResponse = noop) {
- this._pauseOnExceptions = pauseOnExceptions;
- this._ignoreCaughtExceptions = ignoreCaughtExceptions;
-
- // Otherwise send the flag using a standard resume request.
- if (!this.paused) {
- return this.interrupt(response => {
- if (response.error) {
- // Can't continue if pausing failed.
- onResponse(response);
- return response;
- }
- return this.resume(onResponse);
- });
- }
-
- onResponse();
- return promise.resolve();
- },
-
- /**
- * Enable pausing when the specified DOM events are triggered. Disabling
- * pausing on an event can be realized by calling this method with the updated
- * array of events that doesn't contain it.
- *
- * @param array|string events
- * An array of strings, representing the DOM event types to pause on,
- * or "*" to pause on all DOM events. Pass an empty array to
- * completely disable pausing on DOM events.
- * @param function onResponse
- * Called with the response packet in a future turn of the event loop.
- */
- pauseOnDOMEvents: function (events, onResponse = noop) {
- this._pauseOnDOMEvents = events;
- // If the debuggee is paused, the value of the array will be communicated in
- // the next resumption. Otherwise we have to force a pause in order to send
- // the array.
- if (this.paused) {
- DevToolsUtils.executeSoon(() => onResponse({}));
- return {};
- }
- return this.interrupt(response => {
- // Can't continue if pausing failed.
- if (response.error) {
- onResponse(response);
- return response;
- }
- return this.resume(onResponse);
- });
- },
-
- /**
- * Send a clientEvaluate packet to the debuggee. Response
- * will be a resume packet.
- *
- * @param string frame
- * The actor ID of the frame where the evaluation should take place.
- * @param string expression
- * The expression that will be evaluated in the scope of the frame
- * above.
- * @param function onResponse
- * Called with the response packet.
- */
- eval: DebuggerClient.requester({
- type: "clientEvaluate",
- frame: arg(0),
- expression: arg(1)
- }, {
- before: function (packet) {
- this._assertPaused("eval");
- // Put the client in a tentative "resuming" state so we can prevent
- // further requests that should only be sent in the paused state.
- this._state = "resuming";
- return packet;
- },
- after: function (response) {
- if (response.error) {
- // There was an error resuming, back to paused state.
- this._state = "paused";
- }
- return response;
- },
- }),
-
- /**
- * Detach from the thread actor.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- detach: DebuggerClient.requester({
- type: "detach"
- }, {
- after: function (response) {
- this.client.unregisterClient(this);
- this._parent.thread = null;
- return response;
- },
- }),
-
- /**
- * Release multiple thread-lifetime object actors. If any pause-lifetime
- * actors are included in the request, a |notReleasable| error will return,
- * but all the thread-lifetime ones will have been released.
- *
- * @param array actors
- * An array with actor IDs to release.
- */
- releaseMany: DebuggerClient.requester({
- type: "releaseMany",
- actors: arg(0),
- }),
-
- /**
- * Promote multiple pause-lifetime object actors to thread-lifetime ones.
- *
- * @param array actors
- * An array with actor IDs to promote.
- */
- threadGrips: DebuggerClient.requester({
- type: "threadGrips",
- actors: arg(0)
- }),
-
- /**
- * Return the event listeners defined on the page.
- *
- * @param onResponse Function
- * Called with the thread's response.
- */
- eventListeners: DebuggerClient.requester({
- type: "eventListeners"
- }),
-
- /**
- * Request the loaded sources for the current thread.
- *
- * @param onResponse Function
- * Called with the thread's response.
- */
- getSources: DebuggerClient.requester({
- type: "sources"
- }),
-
- /**
- * Clear the thread's source script cache. A scriptscleared event
- * will be sent.
- */
- _clearScripts: function () {
- if (Object.keys(this._scriptCache).length > 0) {
- this._scriptCache = {};
- this.emit("scriptscleared");
- }
- },
-
- /**
- * Request frames from the callstack for the current thread.
- *
- * @param start integer
- * The number of the youngest stack frame to return (the youngest
- * frame is 0).
- * @param count integer
- * The maximum number of frames to return, or null to return all
- * frames.
- * @param onResponse function
- * Called with the thread's response.
- */
- getFrames: DebuggerClient.requester({
- type: "frames",
- start: arg(0),
- count: arg(1)
- }),
-
- /**
- * An array of cached frames. Clients can observe the framesadded and
- * framescleared event to keep up to date on changes to this cache,
- * and can fill it using the fillFrames method.
- */
- get cachedFrames() {
- return this._frameCache;
- },
-
- /**
- * true if there are more stack frames available on the server.
- */
- get moreFrames() {
- return this.paused && (!this._frameCache || this._frameCache.length == 0
- || !this._frameCache[this._frameCache.length - 1].oldest);
- },
-
- /**
- * Request the frame environment.
- *
- * @param frameId string
- */
- getEnvironment: function (frameId) {
- return this.request({ to: frameId, type: "getEnvironment" });
- },
-
- /**
- * Ensure that at least total stack frames have been loaded in the
- * ThreadClient's stack frame cache. A framesadded event will be
- * sent when the stack frame cache is updated.
- *
- * @param total number
- * The minimum number of stack frames to be included.
- * @param callback function
- * Optional callback function called when frames have been loaded
- * @returns true if a framesadded notification should be expected.
- */
- fillFrames: function (total, callback = noop) {
- this._assertPaused("fillFrames");
- if (this._frameCache.length >= total) {
- return false;
- }
-
- let numFrames = this._frameCache.length;
-
- this.getFrames(numFrames, total - numFrames, (response) => {
- if (response.error) {
- callback(response);
- return;
- }
-
- let threadGrips = DevToolsUtils.values(this._threadGrips);
-
- for (let i in response.frames) {
- let frame = response.frames[i];
- if (!frame.where.source) {
- // Older servers use urls instead, so we need to resolve
- // them to source actors
- for (let grip of threadGrips) {
- if (grip instanceof SourceClient && grip.url === frame.url) {
- frame.where.source = grip._form;
- }
- }
- }
-
- this._frameCache[frame.depth] = frame;
- }
-
- // If we got as many frames as we asked for, there might be more
- // frames available.
- this.emit("framesadded");
-
- callback(response);
- });
-
- return true;
- },
-
- /**
- * Clear the thread's stack frame cache. A framescleared event
- * will be sent.
- */
- _clearFrames: function () {
- if (this._frameCache.length > 0) {
- this._frameCache = [];
- this.emit("framescleared");
- }
- },
-
- /**
- * Return a ObjectClient object for the given object grip.
- *
- * @param grip object
- * A pause-lifetime object grip returned by the protocol.
- */
- pauseGrip: function (grip) {
- if (grip.actor in this._pauseGrips) {
- return this._pauseGrips[grip.actor];
- }
-
- let client = new ObjectClient(this.client, grip);
- this._pauseGrips[grip.actor] = client;
- return client;
- },
-
- /**
- * Get or create a long string client, checking the grip client cache if it
- * already exists.
- *
- * @param grip Object
- * The long string grip returned by the protocol.
- * @param gripCacheName String
- * The property name of the grip client cache to check for existing
- * clients in.
- */
- _longString: function (grip, gripCacheName) {
- if (grip.actor in this[gripCacheName]) {
- return this[gripCacheName][grip.actor];
- }
-
- let client = new LongStringClient(this.client, grip);
- this[gripCacheName][grip.actor] = client;
- return client;
- },
-
- /**
- * Return an instance of LongStringClient for the given long string grip that
- * is scoped to the current pause.
- *
- * @param grip Object
- * The long string grip returned by the protocol.
- */
- pauseLongString: function (grip) {
- return this._longString(grip, "_pauseGrips");
- },
-
- /**
- * Return an instance of LongStringClient for the given long string grip that
- * is scoped to the thread lifetime.
- *
- * @param grip Object
- * The long string grip returned by the protocol.
- */
- threadLongString: function (grip) {
- return this._longString(grip, "_threadGrips");
- },
-
- /**
- * Get or create an ArrayBuffer client, checking the grip client cache if it
- * already exists.
- *
- * @param grip Object
- * The ArrayBuffer grip returned by the protocol.
- * @param gripCacheName String
- * The property name of the grip client cache to check for existing
- * clients in.
- */
- _arrayBuffer: function (grip, gripCacheName) {
- if (grip.actor in this[gripCacheName]) {
- return this[gripCacheName][grip.actor];
- }
-
- let client = new ArrayBufferClient(this.client, grip);
- this[gripCacheName][grip.actor] = client;
- return client;
- },
-
- /**
- * Return an instance of ArrayBufferClient for the given ArrayBuffer grip that
- * is scoped to the thread lifetime.
- *
- * @param grip Object
- * The ArrayBuffer grip returned by the protocol.
- */
- threadArrayBuffer: function (grip) {
- return this._arrayBuffer(grip, "_threadGrips");
- },
-
- /**
- * Clear and invalidate all the grip clients from the given cache.
- *
- * @param gripCacheName
- * The property name of the grip cache we want to clear.
- */
- _clearObjectClients: function (gripCacheName) {
- for (let id in this[gripCacheName]) {
- this[gripCacheName][id].valid = false;
- }
- this[gripCacheName] = {};
- },
-
- /**
- * Invalidate pause-lifetime grip clients and clear the list of current grip
- * clients.
- */
- _clearPauseGrips: function () {
- this._clearObjectClients("_pauseGrips");
- },
-
- /**
- * Invalidate thread-lifetime grip clients and clear the list of current grip
- * clients.
- */
- _clearThreadGrips: function () {
- this._clearObjectClients("_threadGrips");
- },
-
- /**
- * Handle thread state change by doing necessary cleanup and notifying all
- * registered listeners.
- */
- _onThreadState: function (packet) {
- this._state = ThreadStateTypes[packet.type];
- // The debugger UI may not be initialized yet so we want to keep
- // the packet around so it knows what to pause state to display
- // when it's initialized
- this._lastPausePacket = packet.type === "resumed" ? null : packet;
- this._clearFrames();
- this._clearPauseGrips();
- packet.type === ThreadStateTypes.detached && this._clearThreadGrips();
- this.client._eventsEnabled && this.emit(packet.type, packet);
- },
-
- getLastPausePacket: function () {
- return this._lastPausePacket;
- },
-
- /**
- * Return an EnvironmentClient instance for the given environment actor form.
- */
- environment: function (form) {
- return new EnvironmentClient(this.client, form);
- },
-
- /**
- * Return an instance of SourceClient for the given source actor form.
- */
- source: function (form) {
- if (form.actor in this._threadGrips) {
- return this._threadGrips[form.actor];
- }
-
- this._threadGrips[form.actor] = new SourceClient(this, form);
- return this._threadGrips[form.actor];
- },
-
- /**
- * Request the prototype and own properties of mutlipleObjects.
- *
- * @param onResponse function
- * Called with the request's response.
- * @param actors [string]
- * List of actor ID of the queried objects.
- */
- getPrototypesAndProperties: DebuggerClient.requester({
- type: "prototypesAndProperties",
- actors: arg(0)
- }),
-
- events: ["newSource"]
-};
-
-eventSource(ThreadClient.prototype);
-
-/**
- * Creates a tracing profiler client for the remote debugging protocol
- * server. This client is a front to the trace actor created on the
- * server side, hiding the protocol details in a traditional
- * JavaScript API.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param actor string
- * The actor ID for this thread.
- */
-function TraceClient(client, actor) {
- this._client = client;
- this._actor = actor;
- this._activeTraces = new Set();
- this._waitingPackets = new Map();
- this._expectedPacket = 0;
- this.request = this._client.request;
- this.events = [];
-}
-
-TraceClient.prototype = {
- get actor() {
- return this._actor;
- },
- get tracing() {
- return this._activeTraces.size > 0;
- },
-
- get _transport() {
- return this._client._transport;
- },
-
- /**
- * Detach from the trace actor.
- */
- detach: DebuggerClient.requester({
- type: "detach"
- }, {
- after: function (response) {
- this._client.unregisterClient(this);
- return response;
- },
- }),
-
- /**
- * Start a new trace.
- *
- * @param trace [string]
- * An array of trace types to be recorded by the new trace.
- *
- * @param name string
- * The name of the new trace.
- *
- * @param onResponse function
- * Called with the request's response.
- */
- startTrace: DebuggerClient.requester({
- type: "startTrace",
- name: arg(1),
- trace: arg(0)
- }, {
- after: function (response) {
- if (response.error) {
- return response;
- }
-
- if (!this.tracing) {
- this._waitingPackets.clear();
- this._expectedPacket = 0;
- }
- this._activeTraces.add(response.name);
-
- return response;
- },
- }),
-
- /**
- * End a trace. If a name is provided, stop the named
- * trace. Otherwise, stop the most recently started trace.
- *
- * @param name string
- * The name of the trace to stop.
- *
- * @param onResponse function
- * Called with the request's response.
- */
- stopTrace: DebuggerClient.requester({
- type: "stopTrace",
- name: arg(0)
- }, {
- after: function (response) {
- if (response.error) {
- return response;
- }
-
- this._activeTraces.delete(response.name);
-
- return response;
- },
- })
-};
-
-/**
- * Grip clients are used to retrieve information about the relevant object.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param grip object
- * A pause-lifetime object grip returned by the protocol.
- */
-function ObjectClient(client, grip) {
- this._grip = grip;
- this._client = client;
- this.request = this._client.request;
-}
-exports.ObjectClient = ObjectClient;
-
-ObjectClient.prototype = {
- get actor() {
- return this._grip.actor;
- },
- get _transport() {
- return this._client._transport;
- },
-
- valid: true,
-
- get isFrozen() {
- return this._grip.frozen;
- },
- get isSealed() {
- return this._grip.sealed;
- },
- get isExtensible() {
- return this._grip.extensible;
- },
-
- getDefinitionSite: DebuggerClient.requester({
- type: "definitionSite"
- }, {
- before: function (packet) {
- if (this._grip.class != "Function") {
- throw new Error("getDefinitionSite is only valid for function grips.");
- }
- return packet;
- }
- }),
-
- /**
- * Request the names of a function's formal parameters.
- *
- * @param onResponse function
- * Called with an object of the form:
- * { parameterNames:[<parameterName>, ...] }
- * where each <parameterName> is the name of a parameter.
- */
- getParameterNames: DebuggerClient.requester({
- type: "parameterNames"
- }, {
- before: function (packet) {
- if (this._grip.class !== "Function") {
- throw new Error("getParameterNames is only valid for function grips.");
- }
- return packet;
- },
- }),
-
- /**
- * Request the names of the properties defined on the object and not its
- * prototype.
- *
- * @param onResponse function Called with the request's response.
- */
- getOwnPropertyNames: DebuggerClient.requester({
- type: "ownPropertyNames"
- }),
-
- /**
- * Request the prototype and own properties of the object.
- *
- * @param onResponse function Called with the request's response.
- */
- getPrototypeAndProperties: DebuggerClient.requester({
- type: "prototypeAndProperties"
- }),
-
- /**
- * Request a PropertyIteratorClient instance to ease listing
- * properties for this object.
- *
- * @param options Object
- * A dictionary object with various boolean attributes:
- * - ignoreIndexedProperties Boolean
- * If true, filters out Array items.
- * e.g. properties names between `0` and `object.length`.
- * - ignoreNonIndexedProperties Boolean
- * If true, filters out items that aren't array items
- * e.g. properties names that are not a number between `0`
- * and `object.length`.
- * - sort Boolean
- * If true, the iterator will sort the properties by name
- * before dispatching them.
- * @param onResponse function Called with the client instance.
- */
- enumProperties: DebuggerClient.requester({
- type: "enumProperties",
- options: arg(0)
- }, {
- after: function (response) {
- if (response.iterator) {
- return { iterator: new PropertyIteratorClient(this._client, response.iterator) };
- }
- return response;
- },
- }),
-
- /**
- * Request a PropertyIteratorClient instance to enumerate entries in a
- * Map/Set-like object.
- *
- * @param onResponse function Called with the request's response.
- */
- enumEntries: DebuggerClient.requester({
- type: "enumEntries"
- }, {
- before: function (packet) {
- if (!["Map", "WeakMap", "Set", "WeakSet"].includes(this._grip.class)) {
- throw new Error("enumEntries is only valid for Map/Set-like grips.");
- }
- return packet;
- },
- after: function (response) {
- if (response.iterator) {
- return {
- iterator: new PropertyIteratorClient(this._client, response.iterator)
- };
- }
- return response;
- }
- }),
-
- /**
- * Request a SymbolIteratorClient instance to enumerate symbols in an object.
- *
- * @param onResponse function Called with the request's response.
- */
- enumSymbols: DebuggerClient.requester({
- type: "enumSymbols"
- }, {
- before: function (packet) {
- if (this._grip.type !== "object") {
- throw new Error("enumSymbols is only valid for objects grips.");
- }
- return packet;
- },
- after: function (response) {
- if (response.iterator) {
- return {
- iterator: new SymbolIteratorClient(this._client, response.iterator)
- };
- }
- return response;
- }
- }),
-
- /**
- * Request the property descriptor of the object's specified property.
- *
- * @param name string The name of the requested property.
- * @param onResponse function Called with the request's response.
- */
- getProperty: DebuggerClient.requester({
- type: "property",
- name: arg(0)
- }),
-
- /**
- * Request the prototype of the object.
- *
- * @param onResponse function Called with the request's response.
- */
- getPrototype: DebuggerClient.requester({
- type: "prototype"
- }),
-
- /**
- * Request the display string of the object.
- *
- * @param onResponse function Called with the request's response.
- */
- getDisplayString: DebuggerClient.requester({
- type: "displayString"
- }),
-
- /**
- * Request the scope of the object.
- *
- * @param onResponse function Called with the request's response.
- */
- getScope: DebuggerClient.requester({
- type: "scope"
- }, {
- before: function (packet) {
- if (this._grip.class !== "Function") {
- throw new Error("scope is only valid for function grips.");
- }
- return packet;
- },
- }),
-
- /**
- * Request the promises directly depending on the current promise.
- */
- getDependentPromises: DebuggerClient.requester({
- type: "dependentPromises"
- }, {
- before: function (packet) {
- if (this._grip.class !== "Promise") {
- throw new Error("getDependentPromises is only valid for promise " +
- "grips.");
- }
- return packet;
- }
- }),
-
- /**
- * Request the stack to the promise's allocation point.
- */
- getPromiseAllocationStack: DebuggerClient.requester({
- type: "allocationStack"
- }, {
- before: function (packet) {
- if (this._grip.class !== "Promise") {
- throw new Error("getAllocationStack is only valid for promise grips.");
- }
- return packet;
- }
- }),
-
- /**
- * Request the stack to the promise's fulfillment point.
- */
- getPromiseFulfillmentStack: DebuggerClient.requester({
- type: "fulfillmentStack"
- }, {
- before: function (packet) {
- if (this._grip.class !== "Promise") {
- throw new Error("getPromiseFulfillmentStack is only valid for " +
- "promise grips.");
- }
- return packet;
- }
- }),
-
- /**
- * Request the stack to the promise's rejection point.
- */
- getPromiseRejectionStack: DebuggerClient.requester({
- type: "rejectionStack"
- }, {
- before: function (packet) {
- if (this._grip.class !== "Promise") {
- throw new Error("getPromiseRejectionStack is only valid for " +
- "promise grips.");
- }
- return packet;
- }
- })
-};
+const {arg, DebuggerClient} = require("./debugger-client");
/**
* A PropertyIteratorClient provides a way to access to property names and
* values of an object efficiently, slice by slice.
* Note that the properties can be sorted in the backend,
* this is controled while creating the PropertyIteratorClient
* from ObjectClient.enumProperties.
*
@@ -2847,580 +73,9 @@ PropertyIteratorClient.prototype = {
* @param callback Function
* The function called when we receive the property values.
*/
all: DebuggerClient.requester({
type: "all"
}, {}),
};
-/**
- * A SymbolIteratorClient provides a way to access to symbols
- * of an object efficiently, slice by slice.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param grip Object
- * A SymbolIteratorActor grip returned by the protocol via
- * TabActor.enumSymbols request.
- */
-function SymbolIteratorClient(client, grip) {
- this._grip = grip;
- this._client = client;
- this.request = this._client.request;
-}
-
-SymbolIteratorClient.prototype = {
- get actor() {
- return this._grip.actor;
- },
-
- /**
- * Get the total number of symbols available in the iterator.
- */
- get count() {
- return this._grip.count;
- },
-
- /**
- * Get a set of following symbols.
- *
- * @param start Number
- * The index of the first symbol to fetch.
- * @param count Number
- * The number of symbols to fetch.
- * @param callback Function
- * The function called when we receive the symbols.
- */
- slice: DebuggerClient.requester({
- type: "slice",
- start: arg(0),
- count: arg(1)
- }, {}),
-
- /**
- * Get all the symbols.
- *
- * @param callback Function
- * The function called when we receive the symbols.
- */
- all: DebuggerClient.requester({
- type: "all"
- }, {}),
-};
-
-/**
- * A ArrayBufferClient provides a way to access ArrayBuffer from the
- * debugger server.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param grip Object
- * A pause-lifetime ArrayBuffer grip returned by the protocol.
- */
-function ArrayBufferClient(client, grip) {
- this._grip = grip;
- this._client = client;
- this.request = this._client.request;
-}
-ArrayBufferClient.prototype = {
- get actor() {
- return this._grip.actor;
- },
- get length() {
- return this._grip.length;
- },
- get _transport() {
- return this._client._transport;
- },
-
- valid: true,
-
- slice: DebuggerClient.requester({
- type: "slice",
- start: arg(0),
- count: arg(1)
- }),
-};
-
-/**
- * A LongStringClient provides a way to access "very long" strings from the
- * debugger server.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param grip Object
- * A pause-lifetime long string grip returned by the protocol.
- */
-function LongStringClient(client, grip) {
- this._grip = grip;
- this._client = client;
- this.request = this._client.request;
-}
-exports.LongStringClient = LongStringClient;
-
-LongStringClient.prototype = {
- get actor() {
- return this._grip.actor;
- },
- get length() {
- return this._grip.length;
- },
- get initial() {
- return this._grip.initial;
- },
- get _transport() {
- return this._client._transport;
- },
-
- valid: true,
-
- /**
- * Get the substring of this LongString from start to end.
- *
- * @param start Number
- * The starting index.
- * @param end Number
- * The ending index.
- * @param callback Function
- * The function called when we receive the substring.
- */
- substring: DebuggerClient.requester({
- type: "substring",
- start: arg(0),
- end: arg(1)
- }),
-};
-
-/**
- * A SourceClient provides a way to access the source text of a script.
- *
- * @param client ThreadClient
- * The thread client parent.
- * @param form Object
- * The form sent across the remote debugging protocol.
- */
-function SourceClient(client, form) {
- this._form = form;
- this._isBlackBoxed = form.isBlackBoxed;
- this._isPrettyPrinted = form.isPrettyPrinted;
- this._activeThread = client;
- this._client = client.client;
-}
-
-SourceClient.prototype = {
- get _transport() {
- return this._client._transport;
- },
- get isBlackBoxed() {
- return this._isBlackBoxed;
- },
- get isPrettyPrinted() {
- return this._isPrettyPrinted;
- },
- get actor() {
- return this._form.actor;
- },
- get request() {
- return this._client.request;
- },
- get url() {
- return this._form.url;
- },
-
- /**
- * Black box this SourceClient's source.
- *
- * @param callback Function
- * The callback function called when we receive the response from the server.
- */
- blackBox: DebuggerClient.requester({
- type: "blackbox"
- }, {
- after: function (response) {
- if (!response.error) {
- this._isBlackBoxed = true;
- if (this._activeThread) {
- this._activeThread.emit("blackboxchange", this);
- }
- }
- return response;
- }
- }),
-
- /**
- * Un-black box this SourceClient's source.
- *
- * @param callback Function
- * The callback function called when we receive the response from the server.
- */
- unblackBox: DebuggerClient.requester({
- type: "unblackbox"
- }, {
- after: function (response) {
- if (!response.error) {
- this._isBlackBoxed = false;
- if (this._activeThread) {
- this._activeThread.emit("blackboxchange", this);
- }
- }
- return response;
- }
- }),
-
- /**
- * Get Executable Lines from a source
- *
- * @param callback Function
- * The callback function called when we receive the response from the server.
- */
- getExecutableLines: function (cb = noop) {
- let packet = {
- to: this._form.actor,
- type: "getExecutableLines"
- };
-
- return this._client.request(packet).then(res => {
- cb(res.lines);
- return res.lines;
- });
- },
-
- /**
- * Get a long string grip for this SourceClient's source.
- */
- source: function (callback = noop) {
- let packet = {
- to: this._form.actor,
- type: "source"
- };
- return this._client.request(packet).then(response => {
- return this._onSourceResponse(response, callback);
- });
- },
-
- /**
- * Pretty print this source's text.
- */
- prettyPrint: function (indent, callback = noop) {
- const packet = {
- to: this._form.actor,
- type: "prettyPrint",
- indent
- };
- return this._client.request(packet).then(response => {
- if (!response.error) {
- this._isPrettyPrinted = true;
- this._activeThread._clearFrames();
- this._activeThread.emit("prettyprintchange", this);
- }
- return this._onSourceResponse(response, callback);
- });
- },
-
- /**
- * Stop pretty printing this source's text.
- */
- disablePrettyPrint: function (callback = noop) {
- const packet = {
- to: this._form.actor,
- type: "disablePrettyPrint"
- };
- return this._client.request(packet).then(response => {
- if (!response.error) {
- this._isPrettyPrinted = false;
- this._activeThread._clearFrames();
- this._activeThread.emit("prettyprintchange", this);
- }
- return this._onSourceResponse(response, callback);
- });
- },
-
- _onSourceResponse: function (response, callback) {
- if (response.error) {
- callback(response);
- return response;
- }
-
- if (typeof response.source === "string") {
- callback(response);
- return response;
- }
-
- let { contentType, source } = response;
- if (source.type === "arrayBuffer") {
- let arrayBuffer = this._activeThread.threadArrayBuffer(source);
- return arrayBuffer.slice(0, arrayBuffer.length).then(function (resp) {
- if (resp.error) {
- callback(resp);
- return resp;
- }
- // Keeping str as a string, ArrayBuffer/Uint8Array will not survive
- // setIn/mergeIn operations.
- const str = atob(resp.encoded);
- let newResponse = {
- source: {
- binary: str,
- toString: () => "[wasm]",
- },
- contentType,
- };
- callback(newResponse);
- return newResponse;
- });
- }
-
- let longString = this._activeThread.threadLongString(source);
- return longString.substring(0, longString.length).then(function (resp) {
- if (resp.error) {
- callback(resp);
- return resp;
- }
-
- let newResponse = {
- source: resp.substring,
- contentType: contentType
- };
- callback(newResponse);
- return newResponse;
- });
- },
-
- /**
- * Request to set a breakpoint in the specified location.
- *
- * @param object location
- * The location and condition of the breakpoint in
- * the form of { line[, column, condition] }.
- * @param function onResponse
- * Called with the thread's response.
- */
- setBreakpoint: function ({ line, column, condition, noSliding }, onResponse = noop) {
- // A helper function that sets the breakpoint.
- let doSetBreakpoint = callback => {
- let root = this._client.mainRoot;
- let location = {
- line,
- column,
- };
-
- let packet = {
- to: this.actor,
- type: "setBreakpoint",
- location,
- condition,
- noSliding,
- };
-
- // Backwards compatibility: send the breakpoint request to the
- // thread if the server doesn't support Debugger.Source actors.
- if (!root.traits.debuggerSourceActors) {
- packet.to = this._activeThread.actor;
- packet.location.url = this.url;
- }
-
- return this._client.request(packet).then(response => {
- // Ignoring errors, since the user may be setting a breakpoint in a
- // dead script that will reappear on a page reload.
- let bpClient;
- if (response.actor) {
- bpClient = new BreakpointClient(
- this._client,
- this,
- response.actor,
- location,
- root.traits.conditionalBreakpoints ? condition : undefined
- );
- }
- onResponse(response, bpClient);
- if (callback) {
- callback();
- }
- return [response, bpClient];
- });
- };
-
- // If the debuggee is paused, just set the breakpoint.
- if (this._activeThread.paused) {
- return doSetBreakpoint();
- }
- // Otherwise, force a pause in order to set the breakpoint.
- return this._activeThread.interrupt().then(response => {
- if (response.error) {
- // Can't set the breakpoint if pausing failed.
- onResponse(response);
- return response;
- }
-
- const { type, why } = response;
- const cleanUp = type == "paused" && why.type == "interrupted"
- ? () => this._activeThread.resume()
- : noop;
-
- return doSetBreakpoint(cleanUp);
- });
- }
-};
-
-/**
- * Breakpoint clients are used to remove breakpoints that are no longer used.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param sourceClient SourceClient
- * The source where this breakpoint exists
- * @param actor string
- * The actor ID for this breakpoint.
- * @param location object
- * The location of the breakpoint. This is an object with two properties:
- * url and line.
- * @param condition string
- * The conditional expression of the breakpoint
- */
-function BreakpointClient(client, sourceClient, actor, location, condition) {
- this._client = client;
- this._actor = actor;
- this.location = location;
- this.location.actor = sourceClient.actor;
- this.location.url = sourceClient.url;
- this.source = sourceClient;
- this.request = this._client.request;
-
- // The condition property should only exist if it's a truthy value
- if (condition) {
- this.condition = condition;
- }
-}
-
-BreakpointClient.prototype = {
-
- _actor: null,
- get actor() {
- return this._actor;
- },
- get _transport() {
- return this._client._transport;
- },
-
- /**
- * Remove the breakpoint from the server.
- */
- remove: DebuggerClient.requester({
- type: "delete"
- }),
-
- /**
- * Determines if this breakpoint has a condition
- */
- hasCondition: function () {
- let root = this._client.mainRoot;
- // XXX bug 990137: We will remove support for client-side handling of
- // conditional breakpoints
- if (root.traits.conditionalBreakpoints) {
- return "condition" in this;
- }
- return "conditionalExpression" in this;
- },
-
- /**
- * Get the condition of this breakpoint. Currently we have to
- * support locally emulated conditional breakpoints until the
- * debugger servers are updated (see bug 990137). We used a
- * different property when moving it server-side to ensure that we
- * are testing the right code.
- */
- getCondition: function () {
- let root = this._client.mainRoot;
- if (root.traits.conditionalBreakpoints) {
- return this.condition;
- }
- return this.conditionalExpression;
- },
-
- /**
- * Set the condition of this breakpoint
- */
- setCondition: function (gThreadClient, condition) {
- let root = this._client.mainRoot;
- let deferred = promise.defer();
-
- if (root.traits.conditionalBreakpoints) {
- let info = {
- line: this.location.line,
- column: this.location.column,
- condition: condition
- };
-
- // Remove the current breakpoint and add a new one with the
- // condition.
- this.remove(response => {
- if (response && response.error) {
- deferred.reject(response);
- return;
- }
-
- this.source.setBreakpoint(info, (resp, newBreakpoint) => {
- if (resp && resp.error) {
- deferred.reject(resp);
- } else {
- deferred.resolve(newBreakpoint);
- }
- });
- });
- } else {
- // The property shouldn't even exist if the condition is blank
- if (condition === "") {
- delete this.conditionalExpression;
- } else {
- this.conditionalExpression = condition;
- }
- deferred.resolve(this);
- }
-
- return deferred.promise;
- }
-};
-
-eventSource(BreakpointClient.prototype);
-
-/**
- * Environment clients are used to manipulate the lexical environment actors.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param form Object
- * The form sent across the remote debugging protocol.
- */
-function EnvironmentClient(client, form) {
- this._client = client;
- this._form = form;
- this.request = this._client.request;
-}
-exports.EnvironmentClient = EnvironmentClient;
-
-EnvironmentClient.prototype = {
-
- get actor() {
- return this._form.actor;
- },
- get _transport() {
- return this._client._transport;
- },
-
- /**
- * Fetches the bindings introduced by this lexical environment.
- */
- getBindings: DebuggerClient.requester({
- type: "bindings"
- }),
-
- /**
- * Changes the value of the identifier whose name is name (a string) to that
- * represented by value (a grip).
- */
- assign: DebuggerClient.requester({
- type: "assign",
- name: arg(0),
- value: arg(1)
- })
-};
-
-eventSource(EnvironmentClient.prototype);
+module.exports = PropertyIteratorClient;
copy from devtools/shared/client/main.js
copy to devtools/shared/client/root-client.js
--- a/devtools/shared/client/main.js
+++ b/devtools/shared/client/root-client.js
@@ -1,1609 +1,16 @@
-/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
-/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
-const { Ci, Cu } = require("chrome");
-const DevToolsUtils = require("devtools/shared/DevToolsUtils");
-const { getStack, callFunctionWithAsyncStack } = require("devtools/shared/platform/stack");
-
-const promise = Cu.import("resource://devtools/shared/deprecated-sync-thenables.js", {}).Promise;
-
-loader.lazyRequireGetter(this, "EventEmitter", "devtools/shared/event-emitter");
-loader.lazyRequireGetter(this, "WebConsoleClient", "devtools/shared/webconsole/client", true);
-loader.lazyRequireGetter(this, "DebuggerSocket", "devtools/shared/security/socket", true);
-loader.lazyRequireGetter(this, "Authentication", "devtools/shared/security/auth");
-
-const noop = () => {};
-
-/**
- * TODO: Get rid of this API in favor of EventTarget (bug 1042642)
- *
- * Add simple event notification to a prototype object. Any object that has
- * some use for event notifications or the observer pattern in general can be
- * augmented with the necessary facilities by passing its prototype to this
- * function.
- *
- * @param proto object
- * The prototype object that will be modified.
- */
-function eventSource(proto) {
- /**
- * Add a listener to the event source for a given event.
- *
- * @param name string
- * The event to listen for.
- * @param listener function
- * Called when the event is fired. If the same listener
- * is added more than once, it will be called once per
- * addListener call.
- */
- proto.addListener = function (name, listener) {
- if (typeof listener != "function") {
- throw TypeError("Listeners must be functions.");
- }
-
- if (!this._listeners) {
- this._listeners = {};
- }
-
- this._getListeners(name).push(listener);
- };
-
- /**
- * Add a listener to the event source for a given event. The
- * listener will be removed after it is called for the first time.
- *
- * @param name string
- * The event to listen for.
- * @param listener function
- * Called when the event is fired.
- */
- proto.addOneTimeListener = function (name, listener) {
- let l = (...args) => {
- this.removeListener(name, l);
- listener.apply(null, args);
- };
- this.addListener(name, l);
- };
-
- /**
- * Remove a listener from the event source previously added with
- * addListener().
- *
- * @param name string
- * The event name used during addListener to add the listener.
- * @param listener function
- * The callback to remove. If addListener was called multiple
- * times, all instances will be removed.
- */
- proto.removeListener = function (name, listener) {
- if (!this._listeners || (listener && !this._listeners[name])) {
- return;
- }
-
- if (!listener) {
- this._listeners[name] = [];
- } else {
- this._listeners[name] =
- this._listeners[name].filter(l => l != listener);
- }
- };
-
- /**
- * Returns the listeners for the specified event name. If none are defined it
- * initializes an empty list and returns that.
- *
- * @param name string
- * The event name.
- */
- proto._getListeners = function (name) {
- if (name in this._listeners) {
- return this._listeners[name];
- }
- this._listeners[name] = [];
- return this._listeners[name];
- };
-
- /**
- * Notify listeners of an event.
- *
- * @param name string
- * The event to fire.
- * @param arguments
- * All arguments will be passed along to the listeners,
- * including the name argument.
- */
- proto.emit = function () {
- if (!this._listeners) {
- return;
- }
-
- let name = arguments[0];
- let listeners = this._getListeners(name).slice(0);
-
- for (let listener of listeners) {
- try {
- listener.apply(null, arguments);
- } catch (e) {
- // Prevent a bad listener from interfering with the others.
- DevToolsUtils.reportException("notify event '" + name + "'", e);
- }
- }
- };
-}
-
-/**
- * Set of protocol messages that affect thread state, and the
- * state the actor is in after each message.
- */
-const ThreadStateTypes = {
- "paused": "paused",
- "resumed": "attached",
- "detached": "detached",
- "running": "attached"
-};
-
-/**
- * Set of protocol messages that are sent by the server without a prior request
- * by the client.
- */
-const UnsolicitedNotifications = {
- "consoleAPICall": "consoleAPICall",
- "eventNotification": "eventNotification",
- "fileActivity": "fileActivity",
- "lastPrivateContextExited": "lastPrivateContextExited",
- "logMessage": "logMessage",
- "networkEvent": "networkEvent",
- "networkEventUpdate": "networkEventUpdate",
- "newGlobal": "newGlobal",
- "newScript": "newScript",
- "tabDetached": "tabDetached",
- "tabListChanged": "tabListChanged",
- "reflowActivity": "reflowActivity",
- "addonListChanged": "addonListChanged",
- "workerListChanged": "workerListChanged",
- "serviceWorkerRegistrationListChanged": "serviceWorkerRegistrationList",
- "tabNavigated": "tabNavigated",
- "frameUpdate": "frameUpdate",
- "pageError": "pageError",
- "documentLoad": "documentLoad",
- "enteredFrame": "enteredFrame",
- "exitedFrame": "exitedFrame",
- "appOpen": "appOpen",
- "appClose": "appClose",
- "appInstall": "appInstall",
- "appUninstall": "appUninstall",
- "evaluationResult": "evaluationResult",
- "newSource": "newSource",
- "updatedSource": "updatedSource",
- "inspectObject": "inspectObject"
-};
-
-/**
- * Set of pause types that are sent by the server and not as an immediate
- * response to a client request.
- */
-const UnsolicitedPauses = {
- "resumeLimit": "resumeLimit",
- "debuggerStatement": "debuggerStatement",
- "breakpoint": "breakpoint",
- "DOMEvent": "DOMEvent",
- "watchpoint": "watchpoint",
- "exception": "exception"
-};
-
-/**
- * Creates a client for the remote debugging protocol server. This client
- * provides the means to communicate with the server and exchange the messages
- * required by the protocol in a traditional JavaScript API.
- */
-const DebuggerClient = exports.DebuggerClient = function (transport) {
- this._transport = transport;
- this._transport.hooks = this;
-
- // Map actor ID to client instance for each actor type.
- this._clients = new Map();
-
- this._pendingRequests = new Map();
- this._activeRequests = new Map();
- this._eventsEnabled = true;
-
- this.traits = {};
-
- this.request = this.request.bind(this);
- this.localTransport = this._transport.onOutputStreamReady === undefined;
-
- /*
- * As the first thing on the connection, expect a greeting packet from
- * the connection's root actor.
- */
- this.mainRoot = null;
- this.expectReply("root", (packet) => {
- this.mainRoot = new RootClient(this, packet);
- this.emit("connected", packet.applicationType, packet.traits);
- });
-};
-
-/**
- * A declarative helper for defining methods that send requests to the server.
- *
- * @param packetSkeleton
- * The form of the packet to send. Can specify fields to be filled from
- * the parameters by using the |arg| function.
- * @param before
- * The function to call before sending the packet. Is passed the packet,
- * and the return value is used as the new packet. The |this| context is
- * the instance of the client object we are defining a method for.
- * @param after
- * The function to call after the response is received. It is passed the
- * response, and the return value is considered the new response that
- * will be passed to the callback. The |this| context is the instance of
- * the client object we are defining a method for.
- * @return Request
- * The `Request` object that is a Promise object and resolves once
- * we receive the response. (See request method for more details)
- */
-DebuggerClient.requester = function (packetSkeleton, config = {}) {
- let { before, after } = config;
- return DevToolsUtils.makeInfallible(function (...args) {
- let outgoingPacket = {
- to: packetSkeleton.to || this.actor
- };
-
- let maxPosition = -1;
- for (let k of Object.keys(packetSkeleton)) {
- if (packetSkeleton[k] instanceof DebuggerClient.Argument) {
- let { position } = packetSkeleton[k];
- outgoingPacket[k] = packetSkeleton[k].getArgument(args);
- maxPosition = Math.max(position, maxPosition);
- } else {
- outgoingPacket[k] = packetSkeleton[k];
- }
- }
-
- if (before) {
- outgoingPacket = before.call(this, outgoingPacket);
- }
-
- return this.request(outgoingPacket, DevToolsUtils.makeInfallible((response) => {
- if (after) {
- let { from } = response;
- response = after.call(this, response);
- if (!response.from) {
- response.from = from;
- }
- }
-
- // The callback is always the last parameter.
- let thisCallback = args[maxPosition + 1];
- if (thisCallback) {
- thisCallback(response);
- }
- return response;
- }, "DebuggerClient.requester request callback"));
- }, "DebuggerClient.requester");
-};
-
-function arg(pos) {
- return new DebuggerClient.Argument(pos);
-}
-
-DebuggerClient.Argument = function (position) {
- this.position = position;
-};
-
-DebuggerClient.Argument.prototype.getArgument = function (params) {
- if (!(this.position in params)) {
- throw new Error("Bad index into params: " + this.position);
- }
- return params[this.position];
-};
-
-// Expose these to save callers the trouble of importing DebuggerSocket
-DebuggerClient.socketConnect = function (options) {
- // Defined here instead of just copying the function to allow lazy-load
- return DebuggerSocket.connect(options);
-};
-DevToolsUtils.defineLazyGetter(DebuggerClient, "Authenticators", () => {
- return Authentication.Authenticators;
-});
-DevToolsUtils.defineLazyGetter(DebuggerClient, "AuthenticationResult", () => {
- return Authentication.AuthenticationResult;
-});
-
-DebuggerClient.prototype = {
- /**
- * Connect to the server and start exchanging protocol messages.
- *
- * @param onConnected function
- * If specified, will be called when the greeting packet is
- * received from the debugging server.
- *
- * @return Promise
- * Resolves once connected with an array whose first element
- * is the application type, by default "browser", and the second
- * element is the traits object (help figure out the features
- * and behaviors of the server we connect to. See RootActor).
- */
- connect: function (onConnected) {
- let deferred = promise.defer();
- this.emit("connect");
-
- // Also emit the event on the |DebuggerClient| object (not on the instance),
- // so it's possible to track all instances.
- EventEmitter.emit(DebuggerClient, "connect", this);
-
- this.addOneTimeListener("connected", (name, applicationType, traits) => {
- this.traits = traits;
- if (onConnected) {
- onConnected(applicationType, traits);
- }
- deferred.resolve([applicationType, traits]);
- });
-
- this._transport.ready();
- return deferred.promise;
- },
-
- /**
- * Shut down communication with the debugging server.
- *
- * @param onClosed function
- * If specified, will be called when the debugging connection
- * has been closed. This parameter is deprecated - please use
- * the returned Promise.
- * @return Promise
- * Resolves after the underlying transport is closed.
- */
- close: function (onClosed) {
- let deferred = promise.defer();
- if (onClosed) {
- deferred.promise.then(onClosed);
- }
-
- // Disable detach event notifications, because event handlers will be in a
- // cleared scope by the time they run.
- this._eventsEnabled = false;
-
- let cleanup = () => {
- this._transport.close();
- this._transport = null;
- };
-
- // If the connection is already closed,
- // there is no need to detach client
- // as we won't be able to send any message.
- if (this._closed) {
- cleanup();
- deferred.resolve();
- return deferred.promise;
- }
-
- this.addOneTimeListener("closed", deferred.resolve);
-
- // Call each client's `detach` method by calling
- // lastly registered ones first to give a chance
- // to detach child clients first.
- let clients = [...this._clients.values()];
- this._clients.clear();
- const detachClients = () => {
- let client = clients.pop();
- if (!client) {
- // All clients detached.
- cleanup();
- return;
- }
- if (client.detach) {
- client.detach(detachClients);
- return;
- }
- detachClients();
- };
- detachClients();
-
- return deferred.promise;
- },
-
- /*
- * This function exists only to preserve DebuggerClient's interface;
- * new code should say 'client.mainRoot.listTabs()'.
- */
- listTabs: function (onResponse) {
- return this.mainRoot.listTabs(onResponse);
- },
-
- /*
- * This function exists only to preserve DebuggerClient's interface;
- * new code should say 'client.mainRoot.listAddons()'.
- */
- listAddons: function (onResponse) {
- return this.mainRoot.listAddons(onResponse);
- },
-
- getTab: function (filter) {
- return this.mainRoot.getTab(filter);
- },
-
- /**
- * Attach to a tab actor.
- *
- * @param string tabActor
- * The actor ID for the tab to attach.
- * @param function onResponse
- * Called with the response packet and a TabClient
- * (which will be undefined on error).
- */
- attachTab: function (tabActor, onResponse = noop) {
- if (this._clients.has(tabActor)) {
- let cachedTab = this._clients.get(tabActor);
- let cachedResponse = {
- cacheDisabled: cachedTab.cacheDisabled,
- javascriptEnabled: cachedTab.javascriptEnabled,
- traits: cachedTab.traits,
- };
- DevToolsUtils.executeSoon(() => onResponse(cachedResponse, cachedTab));
- return promise.resolve([cachedResponse, cachedTab]);
- }
-
- let packet = {
- to: tabActor,
- type: "attach"
- };
- return this.request(packet).then(response => {
- let tabClient;
- if (!response.error) {
- tabClient = new TabClient(this, response);
- this.registerClient(tabClient);
- }
- onResponse(response, tabClient);
- return [response, tabClient];
- });
- },
-
- attachWorker: function (workerActor, onResponse = noop) {
- let workerClient = this._clients.get(workerActor);
- if (workerClient !== undefined) {
- let response = {
- from: workerClient.actor,
- type: "attached",
- url: workerClient.url
- };
- DevToolsUtils.executeSoon(() => onResponse(response, workerClient));
- return promise.resolve([response, workerClient]);
- }
-
- return this.request({ to: workerActor, type: "attach" }).then(response => {
- if (response.error) {
- onResponse(response, null);
- return [response, null];
- }
-
- workerClient = new WorkerClient(this, response);
- this.registerClient(workerClient);
- onResponse(response, workerClient);
- return [response, workerClient];
- });
- },
-
- /**
- * Attach to an addon actor.
- *
- * @param string addonActor
- * The actor ID for the addon to attach.
- * @param function onResponse
- * Called with the response packet and a AddonClient
- * (which will be undefined on error).
- */
- attachAddon: function (addonActor, onResponse = noop) {
- let packet = {
- to: addonActor,
- type: "attach"
- };
- return this.request(packet).then(response => {
- let addonClient;
- if (!response.error) {
- addonClient = new AddonClient(this, addonActor);
- this.registerClient(addonClient);
- this.activeAddon = addonClient;
- }
- onResponse(response, addonClient);
- return [response, addonClient];
- });
- },
-
- /**
- * Attach to a Web Console actor.
- *
- * @param string consoleActor
- * The ID for the console actor to attach to.
- * @param array listeners
- * The console listeners you want to start.
- * @param function onResponse
- * Called with the response packet and a WebConsoleClient
- * instance (which will be undefined on error).
- */
- attachConsole:
- function (consoleActor, listeners, onResponse = noop) {
- let packet = {
- to: consoleActor,
- type: "startListeners",
- listeners: listeners,
- };
-
- return this.request(packet).then(response => {
- let consoleClient;
- if (!response.error) {
- if (this._clients.has(consoleActor)) {
- consoleClient = this._clients.get(consoleActor);
- } else {
- consoleClient = new WebConsoleClient(this, response);
- this.registerClient(consoleClient);
- }
- }
- onResponse(response, consoleClient);
- return [response, consoleClient];
- });
- },
-
- /**
- * Attach to a global-scoped thread actor for chrome debugging.
- *
- * @param string threadActor
- * The actor ID for the thread to attach.
- * @param function onResponse
- * Called with the response packet and a ThreadClient
- * (which will be undefined on error).
- * @param object options
- * Configuration options.
- * - useSourceMaps: whether to use source maps or not.
- */
- attachThread: function (threadActor, onResponse = noop, options = {}) {
- if (this._clients.has(threadActor)) {
- let client = this._clients.get(threadActor);
- DevToolsUtils.executeSoon(() => onResponse({}, client));
- return promise.resolve([{}, client]);
- }
-
- let packet = {
- to: threadActor,
- type: "attach",
- options,
- };
- return this.request(packet).then(response => {
- let threadClient;
- if (!response.error) {
- threadClient = new ThreadClient(this, threadActor);
- this.registerClient(threadClient);
- }
- onResponse(response, threadClient);
- return [response, threadClient];
- });
- },
-
- /**
- * Attach to a trace actor.
- *
- * @param string traceActor
- * The actor ID for the tracer to attach.
- * @param function onResponse
- * Called with the response packet and a TraceClient
- * (which will be undefined on error).
- */
- attachTracer: function (traceActor, onResponse = noop) {
- if (this._clients.has(traceActor)) {
- let client = this._clients.get(traceActor);
- DevToolsUtils.executeSoon(() => onResponse({}, client));
- return promise.resolve([{}, client]);
- }
-
- let packet = {
- to: traceActor,
- type: "attach"
- };
- return this.request(packet).then(response => {
- let traceClient;
- if (!response.error) {
- traceClient = new TraceClient(this, traceActor);
- this.registerClient(traceClient);
- }
- onResponse(response, traceClient);
- return [response, traceClient];
- });
- },
-
- /**
- * Fetch the ChromeActor for the main process or ChildProcessActor for a
- * a given child process ID.
- *
- * @param number id
- * The ID for the process to attach (returned by `listProcesses`).
- * Connected to the main process if omitted, or is 0.
- */
- getProcess: function (id) {
- let packet = {
- to: "root",
- type: "getProcess"
- };
- if (typeof (id) == "number") {
- packet.id = id;
- }
- return this.request(packet);
- },
-
- /**
- * Release an object actor.
- *
- * @param string actor
- * The actor ID to send the request to.
- * @param onResponse function
- * If specified, will be called with the response packet when
- * debugging server responds.
- */
- release: DebuggerClient.requester({
- to: arg(0),
- type: "release"
- }),
-
- /**
- * Send a request to the debugging server.
- *
- * @param packet object
- * A JSON packet to send to the debugging server.
- * @param onResponse function
- * If specified, will be called with the JSON response packet when
- * debugging server responds.
- * @return Request
- * This object emits a number of events to allow you to respond to
- * different parts of the request lifecycle.
- * It is also a Promise object, with a `then` method, that is resolved
- * whenever a JSON or a Bulk response is received; and is rejected
- * if the response is an error.
- * Note: This return value can be ignored if you are using JSON alone,
- * because the callback provided in |onResponse| will be bound to the
- * "json-reply" event automatically.
- *
- * Events emitted:
- * * json-reply: The server replied with a JSON packet, which is
- * passed as event data.
- * * bulk-reply: The server replied with bulk data, which you can read
- * using the event data object containing:
- * * actor: Name of actor that received the packet
- * * type: Name of actor's method that was called on receipt
- * * length: Size of the data to be read
- * * stream: This input stream should only be used directly if you
- * can ensure that you will read exactly |length| bytes
- * and will not close the stream when reading is complete
- * * done: If you use the stream directly (instead of |copyTo|
- * below), you must signal completion by resolving /
- * rejecting this deferred. If it's rejected, the
- * transport will be closed. If an Error is supplied as a
- * rejection value, it will be logged via |dumpn|. If you
- * do use |copyTo|, resolving is taken care of for you
- * when copying completes.
- * * copyTo: A helper function for getting your data out of the
- * stream that meets the stream handling requirements
- * above, and has the following signature:
- * @param output nsIAsyncOutputStream
- * The stream to copy to.
- * @return Promise
- * The promise is resolved when copying completes or
- * rejected if any (unexpected) errors occur.
- * This object also emits "progress" events for each chunk
- * that is copied. See stream-utils.js.
- */
- request: function (packet, onResponse) {
- if (!this.mainRoot) {
- throw Error("Have not yet received a hello packet from the server.");
- }
- let type = packet.type || "";
- if (!packet.to) {
- throw Error("'" + type + "' request packet has no destination.");
- }
-
- // The onResponse callback might modify the response, so we need to call
- // it and resolve the promise with its result if it's truthy.
- const safeOnResponse = response => {
- if (!onResponse) {
- return response;
- }
- return onResponse(response) || response;
- };
-
- if (this._closed) {
- let msg = "'" + type + "' request packet to " +
- "'" + packet.to + "' " +
- "can't be sent as the connection is closed.";
- let resp = { error: "connectionClosed", message: msg };
- return promise.reject(safeOnResponse(resp));
- }
-
- let request = new Request(packet);
- request.format = "json";
- request.stack = getStack();
-
- // Implement a Promise like API on the returned object
- // that resolves/rejects on request response
- let deferred = promise.defer();
- function listenerJson(resp) {
- removeRequestListeners();
- if (resp.error) {
- deferred.reject(safeOnResponse(resp));
- } else {
- deferred.resolve(safeOnResponse(resp));
- }
- }
- function listenerBulk(resp) {
- removeRequestListeners();
- deferred.resolve(safeOnResponse(resp));
- }
-
- const removeRequestListeners = () => {
- request.off("json-reply", listenerJson);
- request.off("bulk-reply", listenerBulk);
- };
-
- request.on("json-reply", listenerJson);
- request.on("bulk-reply", listenerBulk);
-
- this._sendOrQueueRequest(request);
- request.then = deferred.promise.then.bind(deferred.promise);
-
- return request;
- },
-
- /**
- * Transmit streaming data via a bulk request.
- *
- * This method initiates the bulk send process by queuing up the header data.
- * The caller receives eventual access to a stream for writing.
- *
- * Since this opens up more options for how the server might respond (it could
- * send back either JSON or bulk data), and the returned Request object emits
- * events for different stages of the request process that you may want to
- * react to.
- *
- * @param request Object
- * This is modeled after the format of JSON packets above, but does not
- * actually contain the data, but is instead just a routing header:
- * * actor: Name of actor that will receive the packet
- * * type: Name of actor's method that should be called on receipt
- * * length: Size of the data to be sent
- * @return Request
- * This object emits a number of events to allow you to respond to
- * different parts of the request lifecycle.
- *
- * Events emitted:
- * * bulk-send-ready: Ready to send bulk data to the server, using the
- * event data object containing:
- * * stream: This output stream should only be used directly if
- * you can ensure that you will write exactly |length|
- * bytes and will not close the stream when writing is
- * complete
- * * done: If you use the stream directly (instead of |copyFrom|
- * below), you must signal completion by resolving /
- * rejecting this deferred. If it's rejected, the
- * transport will be closed. If an Error is supplied as
- * a rejection value, it will be logged via |dumpn|. If
- * you do use |copyFrom|, resolving is taken care of for
- * you when copying completes.
- * * copyFrom: A helper function for getting your data onto the
- * stream that meets the stream handling requirements
- * above, and has the following signature:
- * @param input nsIAsyncInputStream
- * The stream to copy from.
- * @return Promise
- * The promise is resolved when copying completes or
- * rejected if any (unexpected) errors occur.
- * This object also emits "progress" events for each chunk
- * that is copied. See stream-utils.js.
- * * json-reply: The server replied with a JSON packet, which is
- * passed as event data.
- * * bulk-reply: The server replied with bulk data, which you can read
- * using the event data object containing:
- * * actor: Name of actor that received the packet
- * * type: Name of actor's method that was called on receipt
- * * length: Size of the data to be read
- * * stream: This input stream should only be used directly if you
- * can ensure that you will read exactly |length| bytes
- * and will not close the stream when reading is complete
- * * done: If you use the stream directly (instead of |copyTo|
- * below), you must signal completion by resolving /
- * rejecting this deferred. If it's rejected, the
- * transport will be closed. If an Error is supplied as a
- * rejection value, it will be logged via |dumpn|. If you
- * do use |copyTo|, resolving is taken care of for you
- * when copying completes.
- * * copyTo: A helper function for getting your data out of the
- * stream that meets the stream handling requirements
- * above, and has the following signature:
- * @param output nsIAsyncOutputStream
- * The stream to copy to.
- * @return Promise
- * The promise is resolved when copying completes or
- * rejected if any (unexpected) errors occur.
- * This object also emits "progress" events for each chunk
- * that is copied. See stream-utils.js.
- */
- startBulkRequest: function (request) {
- if (!this.traits.bulk) {
- throw Error("Server doesn't support bulk transfers");
- }
- if (!this.mainRoot) {
- throw Error("Have not yet received a hello packet from the server.");
- }
- if (!request.type) {
- throw Error("Bulk packet is missing the required 'type' field.");
- }
- if (!request.actor) {
- throw Error("'" + request.type + "' bulk packet has no destination.");
- }
- if (!request.length) {
- throw Error("'" + request.type + "' bulk packet has no length.");
- }
-
- request = new Request(request);
- request.format = "bulk";
-
- this._sendOrQueueRequest(request);
-
- return request;
- },
-
- /**
- * If a new request can be sent immediately, do so. Otherwise, queue it.
- */
- _sendOrQueueRequest(request) {
- let actor = request.actor;
- if (!this._activeRequests.has(actor)) {
- this._sendRequest(request);
- } else {
- this._queueRequest(request);
- }
- },
-
- /**
- * Send a request.
- * @throws Error if there is already an active request in flight for the same
- * actor.
- */
- _sendRequest(request) {
- let actor = request.actor;
- this.expectReply(actor, request);
-
- if (request.format === "json") {
- this._transport.send(request.request);
- return;
- }
-
- this._transport.startBulkSend(request.request).then((...args) => {
- request.emit("bulk-send-ready", ...args);
- });
- },
-
- /**
- * Queue a request to be sent later. Queues are only drained when an in
- * flight request to a given actor completes.
- */
- _queueRequest(request) {
- let actor = request.actor;
- let queue = this._pendingRequests.get(actor) || [];
- queue.push(request);
- this._pendingRequests.set(actor, queue);
- },
-
- /**
- * Attempt the next request to a given actor (if any).
- */
- _attemptNextRequest(actor) {
- if (this._activeRequests.has(actor)) {
- return;
- }
- let queue = this._pendingRequests.get(actor);
- if (!queue) {
- return;
- }
- let request = queue.shift();
- if (queue.length === 0) {
- this._pendingRequests.delete(actor);
- }
- this._sendRequest(request);
- },
-
- /**
- * Arrange to hand the next reply from |actor| to the handler bound to
- * |request|.
- *
- * DebuggerClient.prototype.request / startBulkRequest usually takes care of
- * establishing the handler for a given request, but in rare cases (well,
- * greetings from new root actors, is the only case at the moment) we must be
- * prepared for a "reply" that doesn't correspond to any request we sent.
- */
- expectReply: function (actor, request) {
- if (this._activeRequests.has(actor)) {
- throw Error("clashing handlers for next reply from " + actor);
- }
-
- // If a handler is passed directly (as it is with the handler for the root
- // actor greeting), create a dummy request to bind this to.
- if (typeof request === "function") {
- let handler = request;
- request = new Request();
- request.on("json-reply", handler);
- }
-
- this._activeRequests.set(actor, request);
- },
-
- // Transport hooks.
-
- /**
- * Called by DebuggerTransport to dispatch incoming packets as appropriate.
- *
- * @param packet object
- * The incoming packet.
- */
- onPacket: function (packet) {
- if (!packet.from) {
- DevToolsUtils.reportException(
- "onPacket",
- new Error("Server did not specify an actor, dropping packet: " +
- JSON.stringify(packet)));
- return;
- }
-
- // If we have a registered Front for this actor, let it handle the packet
- // and skip all the rest of this unpleasantness.
- let front = this.getActor(packet.from);
- if (front) {
- front.onPacket(packet);
- return;
- }
-
- // Check for "forwardingCancelled" here instead of using a client to handle it.
- // This is necessary because we might receive this event while the client is closing,
- // and the clients have already been removed by that point.
- if (this.mainRoot &&
- packet.from == this.mainRoot.actor &&
- packet.type == "forwardingCancelled") {
- this.purgeRequests(packet.prefix);
- return;
- }
-
- if (this._clients.has(packet.from) && packet.type) {
- let client = this._clients.get(packet.from);
- let type = packet.type;
- if (client.events.indexOf(type) != -1) {
- client.emit(type, packet);
- // we ignore the rest, as the client is expected to handle this packet.
- return;
- }
- }
-
- let activeRequest;
- // See if we have a handler function waiting for a reply from this
- // actor. (Don't count unsolicited notifications or pauses as
- // replies.)
- if (this._activeRequests.has(packet.from) &&
- !(packet.type in UnsolicitedNotifications) &&
- !(packet.type == ThreadStateTypes.paused &&
- packet.why.type in UnsolicitedPauses)) {
- activeRequest = this._activeRequests.get(packet.from);
- this._activeRequests.delete(packet.from);
- }
-
- // If there is a subsequent request for the same actor, hand it off to the
- // transport. Delivery of packets on the other end is always async, even
- // in the local transport case.
- this._attemptNextRequest(packet.from);
-
- // Packets that indicate thread state changes get special treatment.
- if (packet.type in ThreadStateTypes &&
- this._clients.has(packet.from) &&
- typeof this._clients.get(packet.from)._onThreadState == "function") {
- this._clients.get(packet.from)._onThreadState(packet);
- }
-
- // TODO: Bug 1151156 - Remove once Gecko 40 is on b2g-stable.
- if (!this.traits.noNeedToFakeResumptionOnNavigation) {
- // On navigation the server resumes, so the client must resume as well.
- // We achieve that by generating a fake resumption packet that triggers
- // the client's thread state change listeners.
- if (packet.type == UnsolicitedNotifications.tabNavigated &&
- this._clients.has(packet.from) &&
- this._clients.get(packet.from).thread) {
- let thread = this._clients.get(packet.from).thread;
- let resumption = { from: thread._actor, type: "resumed" };
- thread._onThreadState(resumption);
- }
- }
-
- // Only try to notify listeners on events, not responses to requests
- // that lack a packet type.
- if (packet.type) {
- this.emit(packet.type, packet);
- }
-
- if (activeRequest) {
- let emitReply = () => activeRequest.emit("json-reply", packet);
- if (activeRequest.stack) {
- callFunctionWithAsyncStack(emitReply, activeRequest.stack,
- "DevTools RDP");
- } else {
- emitReply();
- }
- }
- },
-
- /**
- * Called by the DebuggerTransport to dispatch incoming bulk packets as
- * appropriate.
- *
- * @param packet object
- * The incoming packet, which contains:
- * * actor: Name of actor that will receive the packet
- * * type: Name of actor's method that should be called on receipt
- * * length: Size of the data to be read
- * * stream: This input stream should only be used directly if you can
- * ensure that you will read exactly |length| bytes and will
- * not close the stream when reading is complete
- * * done: If you use the stream directly (instead of |copyTo|
- * below), you must signal completion by resolving /
- * rejecting this deferred. If it's rejected, the transport
- * will be closed. If an Error is supplied as a rejection
- * value, it will be logged via |dumpn|. If you do use
- * |copyTo|, resolving is taken care of for you when copying
- * completes.
- * * copyTo: A helper function for getting your data out of the stream
- * that meets the stream handling requirements above, and has
- * the following signature:
- * @param output nsIAsyncOutputStream
- * The stream to copy to.
- * @return Promise
- * The promise is resolved when copying completes or rejected
- * if any (unexpected) errors occur.
- * This object also emits "progress" events for each chunk
- * that is copied. See stream-utils.js.
- */
- onBulkPacket: function (packet) {
- let { actor } = packet;
-
- if (!actor) {
- DevToolsUtils.reportException(
- "onBulkPacket",
- new Error("Server did not specify an actor, dropping bulk packet: " +
- JSON.stringify(packet)));
- return;
- }
-
- // See if we have a handler function waiting for a reply from this
- // actor.
- if (!this._activeRequests.has(actor)) {
- return;
- }
-
- let activeRequest = this._activeRequests.get(actor);
- this._activeRequests.delete(actor);
-
- // If there is a subsequent request for the same actor, hand it off to the
- // transport. Delivery of packets on the other end is always async, even
- // in the local transport case.
- this._attemptNextRequest(actor);
-
- activeRequest.emit("bulk-reply", packet);
- },
-
- /**
- * Called by DebuggerTransport when the underlying stream is closed.
- *
- * @param status nsresult
- * The status code that corresponds to the reason for closing
- * the stream.
- */
- onClosed: function () {
- this._closed = true;
- this.emit("closed");
-
- this.purgeRequests();
-
- // The |_pools| array on the client-side currently is used only by
- // protocol.js to store active fronts, mirroring the actor pools found in
- // the server. So, read all usages of "pool" as "protocol.js front".
- //
- // In the normal case where we shutdown cleanly, the toolbox tells each tool
- // to close, and they each call |destroy| on any fronts they were using.
- // When |destroy| or |cleanup| is called on a protocol.js front, it also
- // removes itself from the |_pools| array. Once the toolbox has shutdown,
- // the connection is closed, and we reach here. All fronts (should have
- // been) |destroy|ed, so |_pools| should empty.
- //
- // If the connection instead aborts unexpectedly, we may end up here with
- // all fronts used during the life of the connection. So, we call |cleanup|
- // on them clear their state, reject pending requests, and remove themselves
- // from |_pools|. This saves the toolbox from hanging indefinitely, in case
- // it waits for some server response before shutdown that will now never
- // arrive.
- for (let pool of this._pools) {
- pool.cleanup();
- }
- },
-
- /**
- * Purge pending and active requests in this client.
- *
- * @param prefix string (optional)
- * If a prefix is given, only requests for actor IDs that start with the prefix
- * will be cleaned up. This is useful when forwarding of a portion of requests
- * is cancelled on the server.
- */
- purgeRequests(prefix = "") {
- let reject = function (type, request) {
- // Server can send packets on its own and client only pass a callback
- // to expectReply, so that there is no request object.
- let msg;
- if (request.request) {
- msg = "'" + request.request.type + "' " + type + " request packet" +
- " to '" + request.actor + "' " +
- "can't be sent as the connection just closed.";
- } else {
- msg = "server side packet can't be received as the connection just closed.";
- }
- let packet = { error: "connectionClosed", message: msg };
- request.emit("json-reply", packet);
- };
-
- let pendingRequestsToReject = [];
- this._pendingRequests.forEach((requests, actor) => {
- if (!actor.startsWith(prefix)) {
- return;
- }
- this._pendingRequests.delete(actor);
- pendingRequestsToReject = pendingRequestsToReject.concat(requests);
- });
- pendingRequestsToReject.forEach(request => reject("pending", request));
-
- let activeRequestsToReject = [];
- this._activeRequests.forEach((request, actor) => {
- if (!actor.startsWith(prefix)) {
- return;
- }
- this._activeRequests.delete(actor);
- activeRequestsToReject = activeRequestsToReject.concat(request);
- });
- activeRequestsToReject.forEach(request => reject("active", request));
- },
-
- /**
- * Search for all requests in process for this client, including those made via
- * protocol.js and wait all of them to complete. Since the requests seen when this is
- * first called may in turn trigger more requests, we keep recursing through this
- * function until there is no more activity.
- *
- * This is a fairly heavy weight process, so it's only meant to be used in tests.
- *
- * @return Promise
- * Resolved when all requests have settled.
- */
- waitForRequestsToSettle() {
- let requests = [];
-
- // Gather all pending and active requests in this client
- // The request object supports a Promise API for completion (it has .then())
- this._pendingRequests.forEach(requestsForActor => {
- // Each value is an array of pending requests
- requests = requests.concat(requestsForActor);
- });
- this._activeRequests.forEach(requestForActor => {
- // Each value is a single active request
- requests = requests.concat(requestForActor);
- });
-
- // protocol.js
- // Use a Set because some fronts (like domwalker) seem to have multiple parents.
- let fronts = new Set();
- let poolsToVisit = [...this._pools];
-
- // With protocol.js, each front can potentially have it's own pools containing child
- // fronts, forming a tree. Descend through all the pools to locate all child fronts.
- while (poolsToVisit.length) {
- let pool = poolsToVisit.shift();
- fronts.add(pool);
- for (let child of pool.poolChildren()) {
- poolsToVisit.push(child);
- }
- }
-
- // For each front, wait for its requests to settle
- for (let front of fronts) {
- if (front.hasRequests()) {
- requests.push(front.waitForRequestsToSettle());
- }
- }
-
- // Abort early if there are no requests
- if (!requests.length) {
- return Promise.resolve();
- }
-
- return DevToolsUtils.settleAll(requests).catch(() => {
- // One of the requests might have failed, but ignore that situation here and pipe
- // both success and failure through the same path. The important part is just that
- // we waited.
- }).then(() => {
- // Repeat, more requests may have started in response to those we just waited for
- return this.waitForRequestsToSettle();
- });
- },
-
- registerClient: function (client) {
- let actorID = client.actor;
- if (!actorID) {
- throw new Error("DebuggerServer.registerClient expects " +
- "a client instance with an `actor` attribute.");
- }
- if (!Array.isArray(client.events)) {
- throw new Error("DebuggerServer.registerClient expects " +
- "a client instance with an `events` attribute " +
- "that is an array.");
- }
- if (client.events.length > 0 && typeof (client.emit) != "function") {
- throw new Error("DebuggerServer.registerClient expects " +
- "a client instance with non-empty `events` array to" +
- "have an `emit` function.");
- }
- if (this._clients.has(actorID)) {
- throw new Error("DebuggerServer.registerClient already registered " +
- "a client for this actor.");
- }
- this._clients.set(actorID, client);
- },
-
- unregisterClient: function (client) {
- let actorID = client.actor;
- if (!actorID) {
- throw new Error("DebuggerServer.unregisterClient expects " +
- "a Client instance with a `actor` attribute.");
- }
- this._clients.delete(actorID);
- },
-
- /**
- * Actor lifetime management, echos the server's actor pools.
- */
- __pools: null,
- get _pools() {
- if (this.__pools) {
- return this.__pools;
- }
- this.__pools = new Set();
- return this.__pools;
- },
-
- addActorPool: function (pool) {
- this._pools.add(pool);
- },
- removeActorPool: function (pool) {
- this._pools.delete(pool);
- },
- getActor: function (actorID) {
- let pool = this.poolFor(actorID);
- return pool ? pool.get(actorID) : null;
- },
-
- poolFor: function (actorID) {
- for (let pool of this._pools) {
- if (pool.has(actorID)) {
- return pool;
- }
- }
- return null;
- },
-
- /**
- * Currently attached addon.
- */
- activeAddon: null
-};
-
-eventSource(DebuggerClient.prototype);
-
-class Request extends EventEmitter {
- constructor(request) {
- super();
- this.request = request;
- }
-
- get actor() {
- return this.request.to || this.request.actor;
- }
-}
-
-/**
- * Creates a tab client for the remote debugging protocol server. This client
- * is a front to the tab actor created in the server side, hiding the protocol
- * details in a traditional JavaScript API.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param form object
- * The protocol form for this tab.
- */
-function TabClient(client, form) {
- this.client = client;
- this._actor = form.from;
- this._threadActor = form.threadActor;
- this.javascriptEnabled = form.javascriptEnabled;
- this.cacheDisabled = form.cacheDisabled;
- this.thread = null;
- this.request = this.client.request;
- this.traits = form.traits || {};
- this.events = ["workerListChanged"];
-}
-
-TabClient.prototype = {
- get actor() {
- return this._actor;
- },
- get _transport() {
- return this.client._transport;
- },
-
- /**
- * Attach to a thread actor.
- *
- * @param object options
- * Configuration options.
- * - useSourceMaps: whether to use source maps or not.
- * @param function onResponse
- * Called with the response packet and a ThreadClient
- * (which will be undefined on error).
- */
- attachThread: function (options = {}, onResponse = noop) {
- if (this.thread) {
- DevToolsUtils.executeSoon(() => onResponse({}, this.thread));
- return promise.resolve([{}, this.thread]);
- }
-
- let packet = {
- to: this._threadActor,
- type: "attach",
- options,
- };
- return this.request(packet).then(response => {
- if (!response.error) {
- this.thread = new ThreadClient(this, this._threadActor);
- this.client.registerClient(this.thread);
- }
- onResponse(response, this.thread);
- return [response, this.thread];
- });
- },
-
- /**
- * Detach the client from the tab actor.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- detach: DebuggerClient.requester({
- type: "detach"
- }, {
- before: function (packet) {
- if (this.thread) {
- this.thread.detach();
- }
- return packet;
- },
- after: function (response) {
- this.client.unregisterClient(this);
- return response;
- },
- }),
-
- /**
- * Bring the window to the front.
- */
- focus: DebuggerClient.requester({
- type: "focus"
- }, {}),
-
- /**
- * Reload the page in this tab.
- *
- * @param [optional] object options
- * An object with a `force` property indicating whether or not
- * this reload should skip the cache
- */
- reload: function (options = { force: false }) {
- return this._reload(options);
- },
- _reload: DebuggerClient.requester({
- type: "reload",
- options: arg(0)
- }),
-
- /**
- * Navigate to another URL.
- *
- * @param string url
- * The URL to navigate to.
- */
- navigateTo: DebuggerClient.requester({
- type: "navigateTo",
- url: arg(0)
- }),
-
- /**
- * Reconfigure the tab actor.
- *
- * @param object options
- * A dictionary object of the new options to use in the tab actor.
- * @param function onResponse
- * Called with the response packet.
- */
- reconfigure: DebuggerClient.requester({
- type: "reconfigure",
- options: arg(0)
- }),
-
- listWorkers: DebuggerClient.requester({
- type: "listWorkers"
- }),
-
- attachWorker: function (workerActor, onResponse) {
- return this.client.attachWorker(workerActor, onResponse);
- },
-};
-
-eventSource(TabClient.prototype);
-
-function WorkerClient(client, form) {
- this.client = client;
- this._actor = form.from;
- this._isClosed = false;
- this._url = form.url;
-
- this._onClose = this._onClose.bind(this);
-
- this.addListener("close", this._onClose);
-
- this.traits = {};
-}
-
-WorkerClient.prototype = {
- get _transport() {
- return this.client._transport;
- },
-
- get request() {
- return this.client.request;
- },
-
- get actor() {
- return this._actor;
- },
-
- get url() {
- return this._url;
- },
-
- get isClosed() {
- return this._isClosed;
- },
-
- detach: DebuggerClient.requester({ type: "detach" }, {
- after: function (response) {
- if (this.thread) {
- this.client.unregisterClient(this.thread);
- }
- this.client.unregisterClient(this);
- return response;
- },
- }),
-
- attachThread: function (options = {}, onResponse = noop) {
- if (this.thread) {
- let response = [{
- type: "connected",
- threadActor: this.thread._actor,
- consoleActor: this.consoleActor,
- }, this.thread];
- DevToolsUtils.executeSoon(() => onResponse(response));
- return response;
- }
-
- // The connect call on server doesn't attach the thread as of version 44.
- return this.request({
- to: this._actor,
- type: "connect",
- options,
- }).then(connectResponse => {
- if (connectResponse.error) {
- onResponse(connectResponse, null);
- return [connectResponse, null];
- }
-
- return this.request({
- to: connectResponse.threadActor,
- type: "attach",
- options,
- }).then(attachResponse => {
- if (attachResponse.error) {
- onResponse(attachResponse, null);
- }
-
- this.thread = new ThreadClient(this, connectResponse.threadActor);
- this.consoleActor = connectResponse.consoleActor;
- this.client.registerClient(this.thread);
-
- onResponse(connectResponse, this.thread);
- return [connectResponse, this.thread];
- });
- }, error => {
- onResponse(error, null);
- });
- },
-
- _onClose: function () {
- this.removeListener("close", this._onClose);
-
- if (this.thread) {
- this.client.unregisterClient(this.thread);
- }
- this.client.unregisterClient(this);
- this._isClosed = true;
- },
-
- reconfigure: function () {
- return Promise.resolve();
- },
-
- events: ["close"]
-};
-
-eventSource(WorkerClient.prototype);
-
-function AddonClient(client, actor) {
- this._client = client;
- this._actor = actor;
- this.request = this._client.request;
- this.events = [];
-}
-
-AddonClient.prototype = {
- get actor() {
- return this._actor;
- },
- get _transport() {
- return this._client._transport;
- },
-
- /**
- * Detach the client from the addon actor.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- detach: DebuggerClient.requester({
- type: "detach"
- }, {
- after: function (response) {
- if (this._client.activeAddon === this) {
- this._client.activeAddon = null;
- }
- this._client.unregisterClient(this);
- return response;
- },
- })
-};
+const { Ci } = require("chrome");
+const {DebuggerClient} = require("./debugger-client");
/**
* A RootClient object represents a root actor on the server. Each
* DebuggerClient keeps a RootClient instance representing the root actor
* for the initial connection; DebuggerClient's 'listTabs' and
* 'listChildProcesses' methods forward to that root actor.
*
* @param client object
@@ -1764,1663 +171,9 @@ RootClient.prototype = {
get _transport() {
return this._client._transport;
},
get request() {
return this._client.request;
}
};
-/**
- * Creates a thread client for the remote debugging protocol server. This client
- * is a front to the thread actor created in the server side, hiding the
- * protocol details in a traditional JavaScript API.
- *
- * @param client DebuggerClient|TabClient
- * The parent of the thread (tab for tab-scoped debuggers, DebuggerClient
- * for chrome debuggers).
- * @param actor string
- * The actor ID for this thread.
- */
-function ThreadClient(client, actor) {
- this._parent = client;
- this.client = client instanceof DebuggerClient ? client : client.client;
- this._actor = actor;
- this._frameCache = [];
- this._scriptCache = {};
- this._pauseGrips = {};
- this._threadGrips = {};
- this.request = this.client.request;
-}
-
-ThreadClient.prototype = {
- _state: "paused",
- get state() {
- return this._state;
- },
- get paused() {
- return this._state === "paused";
- },
-
- _pauseOnExceptions: false,
- _ignoreCaughtExceptions: false,
- _pauseOnDOMEvents: null,
-
- _actor: null,
- get actor() {
- return this._actor;
- },
-
- get _transport() {
- return this.client._transport;
- },
-
- _assertPaused: function (command) {
- if (!this.paused) {
- throw Error(command + " command sent while not paused. Currently " + this._state);
- }
- },
-
- /**
- * Resume a paused thread. If the optional limit parameter is present, then
- * the thread will also pause when that limit is reached.
- *
- * @param [optional] object limit
- * An object with a type property set to the appropriate limit (next,
- * step, or finish) per the remote debugging protocol specification.
- * Use null to specify no limit.
- * @param function onResponse
- * Called with the response packet.
- */
- _doResume: DebuggerClient.requester({
- type: "resume",
- resumeLimit: arg(0)
- }, {
- before: function (packet) {
- this._assertPaused("resume");
-
- // Put the client in a tentative "resuming" state so we can prevent
- // further requests that should only be sent in the paused state.
- this._previousState = this._state;
- this._state = "resuming";
-
- if (this._pauseOnExceptions) {
- packet.pauseOnExceptions = this._pauseOnExceptions;
- }
- if (this._ignoreCaughtExceptions) {
- packet.ignoreCaughtExceptions = this._ignoreCaughtExceptions;
- }
- if (this._pauseOnDOMEvents) {
- packet.pauseOnDOMEvents = this._pauseOnDOMEvents;
- }
- return packet;
- },
- after: function (response) {
- if (response.error && this._state == "resuming") {
- // There was an error resuming, update the state to the new one
- // reported by the server, if given (only on wrongState), otherwise
- // reset back to the previous state.
- if (response.state) {
- this._state = ThreadStateTypes[response.state];
- } else {
- this._state = this._previousState;
- }
- }
- delete this._previousState;
- return response;
- },
- }),
-
- /**
- * Reconfigure the thread actor.
- *
- * @param object options
- * A dictionary object of the new options to use in the thread actor.
- * @param function onResponse
- * Called with the response packet.
- */
- reconfigure: DebuggerClient.requester({
- type: "reconfigure",
- options: arg(0)
- }),
-
- /**
- * Resume a paused thread.
- */
- resume: function (onResponse) {
- return this._doResume(null, onResponse);
- },
-
- /**
- * Resume then pause without stepping.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- resumeThenPause: function (onResponse) {
- return this._doResume({ type: "break" }, onResponse);
- },
-
- /**
- * Step over a function call.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- stepOver: function (onResponse) {
- return this._doResume({ type: "next" }, onResponse);
- },
-
- /**
- * Step into a function call.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- stepIn: function (onResponse) {
- return this._doResume({ type: "step" }, onResponse);
- },
-
- /**
- * Step out of a function call.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- stepOut: function (onResponse) {
- return this._doResume({ type: "finish" }, onResponse);
- },
-
- /**
- * Immediately interrupt a running thread.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- interrupt: function (onResponse) {
- return this._doInterrupt(null, onResponse);
- },
-
- /**
- * Pause execution right before the next JavaScript bytecode is executed.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- breakOnNext: function (onResponse) {
- return this._doInterrupt("onNext", onResponse);
- },
-
- /**
- * Interrupt a running thread.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- _doInterrupt: DebuggerClient.requester({
- type: "interrupt",
- when: arg(0)
- }),
-
- /**
- * Enable or disable pausing when an exception is thrown.
- *
- * @param boolean pauseOnExceptions
- * Enables pausing if true, disables otherwise.
- * @param boolean ignoreCaughtExceptions
- * Whether to ignore caught exceptions
- * @param function onResponse
- * Called with the response packet.
- */
- pauseOnExceptions: function (pauseOnExceptions,
- ignoreCaughtExceptions,
- onResponse = noop) {
- this._pauseOnExceptions = pauseOnExceptions;
- this._ignoreCaughtExceptions = ignoreCaughtExceptions;
-
- // Otherwise send the flag using a standard resume request.
- if (!this.paused) {
- return this.interrupt(response => {
- if (response.error) {
- // Can't continue if pausing failed.
- onResponse(response);
- return response;
- }
- return this.resume(onResponse);
- });
- }
-
- onResponse();
- return promise.resolve();
- },
-
- /**
- * Enable pausing when the specified DOM events are triggered. Disabling
- * pausing on an event can be realized by calling this method with the updated
- * array of events that doesn't contain it.
- *
- * @param array|string events
- * An array of strings, representing the DOM event types to pause on,
- * or "*" to pause on all DOM events. Pass an empty array to
- * completely disable pausing on DOM events.
- * @param function onResponse
- * Called with the response packet in a future turn of the event loop.
- */
- pauseOnDOMEvents: function (events, onResponse = noop) {
- this._pauseOnDOMEvents = events;
- // If the debuggee is paused, the value of the array will be communicated in
- // the next resumption. Otherwise we have to force a pause in order to send
- // the array.
- if (this.paused) {
- DevToolsUtils.executeSoon(() => onResponse({}));
- return {};
- }
- return this.interrupt(response => {
- // Can't continue if pausing failed.
- if (response.error) {
- onResponse(response);
- return response;
- }
- return this.resume(onResponse);
- });
- },
-
- /**
- * Send a clientEvaluate packet to the debuggee. Response
- * will be a resume packet.
- *
- * @param string frame
- * The actor ID of the frame where the evaluation should take place.
- * @param string expression
- * The expression that will be evaluated in the scope of the frame
- * above.
- * @param function onResponse
- * Called with the response packet.
- */
- eval: DebuggerClient.requester({
- type: "clientEvaluate",
- frame: arg(0),
- expression: arg(1)
- }, {
- before: function (packet) {
- this._assertPaused("eval");
- // Put the client in a tentative "resuming" state so we can prevent
- // further requests that should only be sent in the paused state.
- this._state = "resuming";
- return packet;
- },
- after: function (response) {
- if (response.error) {
- // There was an error resuming, back to paused state.
- this._state = "paused";
- }
- return response;
- },
- }),
-
- /**
- * Detach from the thread actor.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- detach: DebuggerClient.requester({
- type: "detach"
- }, {
- after: function (response) {
- this.client.unregisterClient(this);
- this._parent.thread = null;
- return response;
- },
- }),
-
- /**
- * Release multiple thread-lifetime object actors. If any pause-lifetime
- * actors are included in the request, a |notReleasable| error will return,
- * but all the thread-lifetime ones will have been released.
- *
- * @param array actors
- * An array with actor IDs to release.
- */
- releaseMany: DebuggerClient.requester({
- type: "releaseMany",
- actors: arg(0),
- }),
-
- /**
- * Promote multiple pause-lifetime object actors to thread-lifetime ones.
- *
- * @param array actors
- * An array with actor IDs to promote.
- */
- threadGrips: DebuggerClient.requester({
- type: "threadGrips",
- actors: arg(0)
- }),
-
- /**
- * Return the event listeners defined on the page.
- *
- * @param onResponse Function
- * Called with the thread's response.
- */
- eventListeners: DebuggerClient.requester({
- type: "eventListeners"
- }),
-
- /**
- * Request the loaded sources for the current thread.
- *
- * @param onResponse Function
- * Called with the thread's response.
- */
- getSources: DebuggerClient.requester({
- type: "sources"
- }),
-
- /**
- * Clear the thread's source script cache. A scriptscleared event
- * will be sent.
- */
- _clearScripts: function () {
- if (Object.keys(this._scriptCache).length > 0) {
- this._scriptCache = {};
- this.emit("scriptscleared");
- }
- },
-
- /**
- * Request frames from the callstack for the current thread.
- *
- * @param start integer
- * The number of the youngest stack frame to return (the youngest
- * frame is 0).
- * @param count integer
- * The maximum number of frames to return, or null to return all
- * frames.
- * @param onResponse function
- * Called with the thread's response.
- */
- getFrames: DebuggerClient.requester({
- type: "frames",
- start: arg(0),
- count: arg(1)
- }),
-
- /**
- * An array of cached frames. Clients can observe the framesadded and
- * framescleared event to keep up to date on changes to this cache,
- * and can fill it using the fillFrames method.
- */
- get cachedFrames() {
- return this._frameCache;
- },
-
- /**
- * true if there are more stack frames available on the server.
- */
- get moreFrames() {
- return this.paused && (!this._frameCache || this._frameCache.length == 0
- || !this._frameCache[this._frameCache.length - 1].oldest);
- },
-
- /**
- * Request the frame environment.
- *
- * @param frameId string
- */
- getEnvironment: function (frameId) {
- return this.request({ to: frameId, type: "getEnvironment" });
- },
-
- /**
- * Ensure that at least total stack frames have been loaded in the
- * ThreadClient's stack frame cache. A framesadded event will be
- * sent when the stack frame cache is updated.
- *
- * @param total number
- * The minimum number of stack frames to be included.
- * @param callback function
- * Optional callback function called when frames have been loaded
- * @returns true if a framesadded notification should be expected.
- */
- fillFrames: function (total, callback = noop) {
- this._assertPaused("fillFrames");
- if (this._frameCache.length >= total) {
- return false;
- }
-
- let numFrames = this._frameCache.length;
-
- this.getFrames(numFrames, total - numFrames, (response) => {
- if (response.error) {
- callback(response);
- return;
- }
-
- let threadGrips = DevToolsUtils.values(this._threadGrips);
-
- for (let i in response.frames) {
- let frame = response.frames[i];
- if (!frame.where.source) {
- // Older servers use urls instead, so we need to resolve
- // them to source actors
- for (let grip of threadGrips) {
- if (grip instanceof SourceClient && grip.url === frame.url) {
- frame.where.source = grip._form;
- }
- }
- }
-
- this._frameCache[frame.depth] = frame;
- }
-
- // If we got as many frames as we asked for, there might be more
- // frames available.
- this.emit("framesadded");
-
- callback(response);
- });
-
- return true;
- },
-
- /**
- * Clear the thread's stack frame cache. A framescleared event
- * will be sent.
- */
- _clearFrames: function () {
- if (this._frameCache.length > 0) {
- this._frameCache = [];
- this.emit("framescleared");
- }
- },
-
- /**
- * Return a ObjectClient object for the given object grip.
- *
- * @param grip object
- * A pause-lifetime object grip returned by the protocol.
- */
- pauseGrip: function (grip) {
- if (grip.actor in this._pauseGrips) {
- return this._pauseGrips[grip.actor];
- }
-
- let client = new ObjectClient(this.client, grip);
- this._pauseGrips[grip.actor] = client;
- return client;
- },
-
- /**
- * Get or create a long string client, checking the grip client cache if it
- * already exists.
- *
- * @param grip Object
- * The long string grip returned by the protocol.
- * @param gripCacheName String
- * The property name of the grip client cache to check for existing
- * clients in.
- */
- _longString: function (grip, gripCacheName) {
- if (grip.actor in this[gripCacheName]) {
- return this[gripCacheName][grip.actor];
- }
-
- let client = new LongStringClient(this.client, grip);
- this[gripCacheName][grip.actor] = client;
- return client;
- },
-
- /**
- * Return an instance of LongStringClient for the given long string grip that
- * is scoped to the current pause.
- *
- * @param grip Object
- * The long string grip returned by the protocol.
- */
- pauseLongString: function (grip) {
- return this._longString(grip, "_pauseGrips");
- },
-
- /**
- * Return an instance of LongStringClient for the given long string grip that
- * is scoped to the thread lifetime.
- *
- * @param grip Object
- * The long string grip returned by the protocol.
- */
- threadLongString: function (grip) {
- return this._longString(grip, "_threadGrips");
- },
-
- /**
- * Get or create an ArrayBuffer client, checking the grip client cache if it
- * already exists.
- *
- * @param grip Object
- * The ArrayBuffer grip returned by the protocol.
- * @param gripCacheName String
- * The property name of the grip client cache to check for existing
- * clients in.
- */
- _arrayBuffer: function (grip, gripCacheName) {
- if (grip.actor in this[gripCacheName]) {
- return this[gripCacheName][grip.actor];
- }
-
- let client = new ArrayBufferClient(this.client, grip);
- this[gripCacheName][grip.actor] = client;
- return client;
- },
-
- /**
- * Return an instance of ArrayBufferClient for the given ArrayBuffer grip that
- * is scoped to the thread lifetime.
- *
- * @param grip Object
- * The ArrayBuffer grip returned by the protocol.
- */
- threadArrayBuffer: function (grip) {
- return this._arrayBuffer(grip, "_threadGrips");
- },
-
- /**
- * Clear and invalidate all the grip clients from the given cache.
- *
- * @param gripCacheName
- * The property name of the grip cache we want to clear.
- */
- _clearObjectClients: function (gripCacheName) {
- for (let id in this[gripCacheName]) {
- this[gripCacheName][id].valid = false;
- }
- this[gripCacheName] = {};
- },
-
- /**
- * Invalidate pause-lifetime grip clients and clear the list of current grip
- * clients.
- */
- _clearPauseGrips: function () {
- this._clearObjectClients("_pauseGrips");
- },
-
- /**
- * Invalidate thread-lifetime grip clients and clear the list of current grip
- * clients.
- */
- _clearThreadGrips: function () {
- this._clearObjectClients("_threadGrips");
- },
-
- /**
- * Handle thread state change by doing necessary cleanup and notifying all
- * registered listeners.
- */
- _onThreadState: function (packet) {
- this._state = ThreadStateTypes[packet.type];
- // The debugger UI may not be initialized yet so we want to keep
- // the packet around so it knows what to pause state to display
- // when it's initialized
- this._lastPausePacket = packet.type === "resumed" ? null : packet;
- this._clearFrames();
- this._clearPauseGrips();
- packet.type === ThreadStateTypes.detached && this._clearThreadGrips();
- this.client._eventsEnabled && this.emit(packet.type, packet);
- },
-
- getLastPausePacket: function () {
- return this._lastPausePacket;
- },
-
- /**
- * Return an EnvironmentClient instance for the given environment actor form.
- */
- environment: function (form) {
- return new EnvironmentClient(this.client, form);
- },
-
- /**
- * Return an instance of SourceClient for the given source actor form.
- */
- source: function (form) {
- if (form.actor in this._threadGrips) {
- return this._threadGrips[form.actor];
- }
-
- this._threadGrips[form.actor] = new SourceClient(this, form);
- return this._threadGrips[form.actor];
- },
-
- /**
- * Request the prototype and own properties of mutlipleObjects.
- *
- * @param onResponse function
- * Called with the request's response.
- * @param actors [string]
- * List of actor ID of the queried objects.
- */
- getPrototypesAndProperties: DebuggerClient.requester({
- type: "prototypesAndProperties",
- actors: arg(0)
- }),
-
- events: ["newSource"]
-};
-
-eventSource(ThreadClient.prototype);
-
-/**
- * Creates a tracing profiler client for the remote debugging protocol
- * server. This client is a front to the trace actor created on the
- * server side, hiding the protocol details in a traditional
- * JavaScript API.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param actor string
- * The actor ID for this thread.
- */
-function TraceClient(client, actor) {
- this._client = client;
- this._actor = actor;
- this._activeTraces = new Set();
- this._waitingPackets = new Map();
- this._expectedPacket = 0;
- this.request = this._client.request;
- this.events = [];
-}
-
-TraceClient.prototype = {
- get actor() {
- return this._actor;
- },
- get tracing() {
- return this._activeTraces.size > 0;
- },
-
- get _transport() {
- return this._client._transport;
- },
-
- /**
- * Detach from the trace actor.
- */
- detach: DebuggerClient.requester({
- type: "detach"
- }, {
- after: function (response) {
- this._client.unregisterClient(this);
- return response;
- },
- }),
-
- /**
- * Start a new trace.
- *
- * @param trace [string]
- * An array of trace types to be recorded by the new trace.
- *
- * @param name string
- * The name of the new trace.
- *
- * @param onResponse function
- * Called with the request's response.
- */
- startTrace: DebuggerClient.requester({
- type: "startTrace",
- name: arg(1),
- trace: arg(0)
- }, {
- after: function (response) {
- if (response.error) {
- return response;
- }
-
- if (!this.tracing) {
- this._waitingPackets.clear();
- this._expectedPacket = 0;
- }
- this._activeTraces.add(response.name);
-
- return response;
- },
- }),
-
- /**
- * End a trace. If a name is provided, stop the named
- * trace. Otherwise, stop the most recently started trace.
- *
- * @param name string
- * The name of the trace to stop.
- *
- * @param onResponse function
- * Called with the request's response.
- */
- stopTrace: DebuggerClient.requester({
- type: "stopTrace",
- name: arg(0)
- }, {
- after: function (response) {
- if (response.error) {
- return response;
- }
-
- this._activeTraces.delete(response.name);
-
- return response;
- },
- })
-};
-
-/**
- * Grip clients are used to retrieve information about the relevant object.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param grip object
- * A pause-lifetime object grip returned by the protocol.
- */
-function ObjectClient(client, grip) {
- this._grip = grip;
- this._client = client;
- this.request = this._client.request;
-}
-exports.ObjectClient = ObjectClient;
-
-ObjectClient.prototype = {
- get actor() {
- return this._grip.actor;
- },
- get _transport() {
- return this._client._transport;
- },
-
- valid: true,
-
- get isFrozen() {
- return this._grip.frozen;
- },
- get isSealed() {
- return this._grip.sealed;
- },
- get isExtensible() {
- return this._grip.extensible;
- },
-
- getDefinitionSite: DebuggerClient.requester({
- type: "definitionSite"
- }, {
- before: function (packet) {
- if (this._grip.class != "Function") {
- throw new Error("getDefinitionSite is only valid for function grips.");
- }
- return packet;
- }
- }),
-
- /**
- * Request the names of a function's formal parameters.
- *
- * @param onResponse function
- * Called with an object of the form:
- * { parameterNames:[<parameterName>, ...] }
- * where each <parameterName> is the name of a parameter.
- */
- getParameterNames: DebuggerClient.requester({
- type: "parameterNames"
- }, {
- before: function (packet) {
- if (this._grip.class !== "Function") {
- throw new Error("getParameterNames is only valid for function grips.");
- }
- return packet;
- },
- }),
-
- /**
- * Request the names of the properties defined on the object and not its
- * prototype.
- *
- * @param onResponse function Called with the request's response.
- */
- getOwnPropertyNames: DebuggerClient.requester({
- type: "ownPropertyNames"
- }),
-
- /**
- * Request the prototype and own properties of the object.
- *
- * @param onResponse function Called with the request's response.
- */
- getPrototypeAndProperties: DebuggerClient.requester({
- type: "prototypeAndProperties"
- }),
-
- /**
- * Request a PropertyIteratorClient instance to ease listing
- * properties for this object.
- *
- * @param options Object
- * A dictionary object with various boolean attributes:
- * - ignoreIndexedProperties Boolean
- * If true, filters out Array items.
- * e.g. properties names between `0` and `object.length`.
- * - ignoreNonIndexedProperties Boolean
- * If true, filters out items that aren't array items
- * e.g. properties names that are not a number between `0`
- * and `object.length`.
- * - sort Boolean
- * If true, the iterator will sort the properties by name
- * before dispatching them.
- * @param onResponse function Called with the client instance.
- */
- enumProperties: DebuggerClient.requester({
- type: "enumProperties",
- options: arg(0)
- }, {
- after: function (response) {
- if (response.iterator) {
- return { iterator: new PropertyIteratorClient(this._client, response.iterator) };
- }
- return response;
- },
- }),
-
- /**
- * Request a PropertyIteratorClient instance to enumerate entries in a
- * Map/Set-like object.
- *
- * @param onResponse function Called with the request's response.
- */
- enumEntries: DebuggerClient.requester({
- type: "enumEntries"
- }, {
- before: function (packet) {
- if (!["Map", "WeakMap", "Set", "WeakSet"].includes(this._grip.class)) {
- throw new Error("enumEntries is only valid for Map/Set-like grips.");
- }
- return packet;
- },
- after: function (response) {
- if (response.iterator) {
- return {
- iterator: new PropertyIteratorClient(this._client, response.iterator)
- };
- }
- return response;
- }
- }),
-
- /**
- * Request a SymbolIteratorClient instance to enumerate symbols in an object.
- *
- * @param onResponse function Called with the request's response.
- */
- enumSymbols: DebuggerClient.requester({
- type: "enumSymbols"
- }, {
- before: function (packet) {
- if (this._grip.type !== "object") {
- throw new Error("enumSymbols is only valid for objects grips.");
- }
- return packet;
- },
- after: function (response) {
- if (response.iterator) {
- return {
- iterator: new SymbolIteratorClient(this._client, response.iterator)
- };
- }
- return response;
- }
- }),
-
- /**
- * Request the property descriptor of the object's specified property.
- *
- * @param name string The name of the requested property.
- * @param onResponse function Called with the request's response.
- */
- getProperty: DebuggerClient.requester({
- type: "property",
- name: arg(0)
- }),
-
- /**
- * Request the prototype of the object.
- *
- * @param onResponse function Called with the request's response.
- */
- getPrototype: DebuggerClient.requester({
- type: "prototype"
- }),
-
- /**
- * Request the display string of the object.
- *
- * @param onResponse function Called with the request's response.
- */
- getDisplayString: DebuggerClient.requester({
- type: "displayString"
- }),
-
- /**
- * Request the scope of the object.
- *
- * @param onResponse function Called with the request's response.
- */
- getScope: DebuggerClient.requester({
- type: "scope"
- }, {
- before: function (packet) {
- if (this._grip.class !== "Function") {
- throw new Error("scope is only valid for function grips.");
- }
- return packet;
- },
- }),
-
- /**
- * Request the promises directly depending on the current promise.
- */
- getDependentPromises: DebuggerClient.requester({
- type: "dependentPromises"
- }, {
- before: function (packet) {
- if (this._grip.class !== "Promise") {
- throw new Error("getDependentPromises is only valid for promise " +
- "grips.");
- }
- return packet;
- }
- }),
-
- /**
- * Request the stack to the promise's allocation point.
- */
- getPromiseAllocationStack: DebuggerClient.requester({
- type: "allocationStack"
- }, {
- before: function (packet) {
- if (this._grip.class !== "Promise") {
- throw new Error("getAllocationStack is only valid for promise grips.");
- }
- return packet;
- }
- }),
-
- /**
- * Request the stack to the promise's fulfillment point.
- */
- getPromiseFulfillmentStack: DebuggerClient.requester({
- type: "fulfillmentStack"
- }, {
- before: function (packet) {
- if (this._grip.class !== "Promise") {
- throw new Error("getPromiseFulfillmentStack is only valid for " +
- "promise grips.");
- }
- return packet;
- }
- }),
-
- /**
- * Request the stack to the promise's rejection point.
- */
- getPromiseRejectionStack: DebuggerClient.requester({
- type: "rejectionStack"
- }, {
- before: function (packet) {
- if (this._grip.class !== "Promise") {
- throw new Error("getPromiseRejectionStack is only valid for " +
- "promise grips.");
- }
- return packet;
- }
- })
-};
-
-/**
- * A PropertyIteratorClient provides a way to access to property names and
- * values of an object efficiently, slice by slice.
- * Note that the properties can be sorted in the backend,
- * this is controled while creating the PropertyIteratorClient
- * from ObjectClient.enumProperties.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param grip Object
- * A PropertyIteratorActor grip returned by the protocol via
- * TabActor.enumProperties request.
- */
-function PropertyIteratorClient(client, grip) {
- this._grip = grip;
- this._client = client;
- this.request = this._client.request;
-}
-
-PropertyIteratorClient.prototype = {
- get actor() {
- return this._grip.actor;
- },
-
- /**
- * Get the total number of properties available in the iterator.
- */
- get count() {
- return this._grip.count;
- },
-
- /**
- * Get one or more property names that correspond to the positions in the
- * indexes parameter.
- *
- * @param indexes Array
- * An array of property indexes.
- * @param callback Function
- * The function called when we receive the property names.
- */
- names: DebuggerClient.requester({
- type: "names",
- indexes: arg(0)
- }, {}),
-
- /**
- * Get a set of following property value(s).
- *
- * @param start Number
- * The index of the first property to fetch.
- * @param count Number
- * The number of properties to fetch.
- * @param callback Function
- * The function called when we receive the property values.
- */
- slice: DebuggerClient.requester({
- type: "slice",
- start: arg(0),
- count: arg(1)
- }, {}),
-
- /**
- * Get all the property values.
- *
- * @param callback Function
- * The function called when we receive the property values.
- */
- all: DebuggerClient.requester({
- type: "all"
- }, {}),
-};
-
-/**
- * A SymbolIteratorClient provides a way to access to symbols
- * of an object efficiently, slice by slice.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param grip Object
- * A SymbolIteratorActor grip returned by the protocol via
- * TabActor.enumSymbols request.
- */
-function SymbolIteratorClient(client, grip) {
- this._grip = grip;
- this._client = client;
- this.request = this._client.request;
-}
-
-SymbolIteratorClient.prototype = {
- get actor() {
- return this._grip.actor;
- },
-
- /**
- * Get the total number of symbols available in the iterator.
- */
- get count() {
- return this._grip.count;
- },
-
- /**
- * Get a set of following symbols.
- *
- * @param start Number
- * The index of the first symbol to fetch.
- * @param count Number
- * The number of symbols to fetch.
- * @param callback Function
- * The function called when we receive the symbols.
- */
- slice: DebuggerClient.requester({
- type: "slice",
- start: arg(0),
- count: arg(1)
- }, {}),
-
- /**
- * Get all the symbols.
- *
- * @param callback Function
- * The function called when we receive the symbols.
- */
- all: DebuggerClient.requester({
- type: "all"
- }, {}),
-};
-
-/**
- * A ArrayBufferClient provides a way to access ArrayBuffer from the
- * debugger server.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param grip Object
- * A pause-lifetime ArrayBuffer grip returned by the protocol.
- */
-function ArrayBufferClient(client, grip) {
- this._grip = grip;
- this._client = client;
- this.request = this._client.request;
-}
-ArrayBufferClient.prototype = {
- get actor() {
- return this._grip.actor;
- },
- get length() {
- return this._grip.length;
- },
- get _transport() {
- return this._client._transport;
- },
-
- valid: true,
-
- slice: DebuggerClient.requester({
- type: "slice",
- start: arg(0),
- count: arg(1)
- }),
-};
-
-/**
- * A LongStringClient provides a way to access "very long" strings from the
- * debugger server.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param grip Object
- * A pause-lifetime long string grip returned by the protocol.
- */
-function LongStringClient(client, grip) {
- this._grip = grip;
- this._client = client;
- this.request = this._client.request;
-}
-exports.LongStringClient = LongStringClient;
-
-LongStringClient.prototype = {
- get actor() {
- return this._grip.actor;
- },
- get length() {
- return this._grip.length;
- },
- get initial() {
- return this._grip.initial;
- },
- get _transport() {
- return this._client._transport;
- },
-
- valid: true,
-
- /**
- * Get the substring of this LongString from start to end.
- *
- * @param start Number
- * The starting index.
- * @param end Number
- * The ending index.
- * @param callback Function
- * The function called when we receive the substring.
- */
- substring: DebuggerClient.requester({
- type: "substring",
- start: arg(0),
- end: arg(1)
- }),
-};
-
-/**
- * A SourceClient provides a way to access the source text of a script.
- *
- * @param client ThreadClient
- * The thread client parent.
- * @param form Object
- * The form sent across the remote debugging protocol.
- */
-function SourceClient(client, form) {
- this._form = form;
- this._isBlackBoxed = form.isBlackBoxed;
- this._isPrettyPrinted = form.isPrettyPrinted;
- this._activeThread = client;
- this._client = client.client;
-}
-
-SourceClient.prototype = {
- get _transport() {
- return this._client._transport;
- },
- get isBlackBoxed() {
- return this._isBlackBoxed;
- },
- get isPrettyPrinted() {
- return this._isPrettyPrinted;
- },
- get actor() {
- return this._form.actor;
- },
- get request() {
- return this._client.request;
- },
- get url() {
- return this._form.url;
- },
-
- /**
- * Black box this SourceClient's source.
- *
- * @param callback Function
- * The callback function called when we receive the response from the server.
- */
- blackBox: DebuggerClient.requester({
- type: "blackbox"
- }, {
- after: function (response) {
- if (!response.error) {
- this._isBlackBoxed = true;
- if (this._activeThread) {
- this._activeThread.emit("blackboxchange", this);
- }
- }
- return response;
- }
- }),
-
- /**
- * Un-black box this SourceClient's source.
- *
- * @param callback Function
- * The callback function called when we receive the response from the server.
- */
- unblackBox: DebuggerClient.requester({
- type: "unblackbox"
- }, {
- after: function (response) {
- if (!response.error) {
- this._isBlackBoxed = false;
- if (this._activeThread) {
- this._activeThread.emit("blackboxchange", this);
- }
- }
- return response;
- }
- }),
-
- /**
- * Get Executable Lines from a source
- *
- * @param callback Function
- * The callback function called when we receive the response from the server.
- */
- getExecutableLines: function (cb = noop) {
- let packet = {
- to: this._form.actor,
- type: "getExecutableLines"
- };
-
- return this._client.request(packet).then(res => {
- cb(res.lines);
- return res.lines;
- });
- },
-
- /**
- * Get a long string grip for this SourceClient's source.
- */
- source: function (callback = noop) {
- let packet = {
- to: this._form.actor,
- type: "source"
- };
- return this._client.request(packet).then(response => {
- return this._onSourceResponse(response, callback);
- });
- },
-
- /**
- * Pretty print this source's text.
- */
- prettyPrint: function (indent, callback = noop) {
- const packet = {
- to: this._form.actor,
- type: "prettyPrint",
- indent
- };
- return this._client.request(packet).then(response => {
- if (!response.error) {
- this._isPrettyPrinted = true;
- this._activeThread._clearFrames();
- this._activeThread.emit("prettyprintchange", this);
- }
- return this._onSourceResponse(response, callback);
- });
- },
-
- /**
- * Stop pretty printing this source's text.
- */
- disablePrettyPrint: function (callback = noop) {
- const packet = {
- to: this._form.actor,
- type: "disablePrettyPrint"
- };
- return this._client.request(packet).then(response => {
- if (!response.error) {
- this._isPrettyPrinted = false;
- this._activeThread._clearFrames();
- this._activeThread.emit("prettyprintchange", this);
- }
- return this._onSourceResponse(response, callback);
- });
- },
-
- _onSourceResponse: function (response, callback) {
- if (response.error) {
- callback(response);
- return response;
- }
-
- if (typeof response.source === "string") {
- callback(response);
- return response;
- }
-
- let { contentType, source } = response;
- if (source.type === "arrayBuffer") {
- let arrayBuffer = this._activeThread.threadArrayBuffer(source);
- return arrayBuffer.slice(0, arrayBuffer.length).then(function (resp) {
- if (resp.error) {
- callback(resp);
- return resp;
- }
- // Keeping str as a string, ArrayBuffer/Uint8Array will not survive
- // setIn/mergeIn operations.
- const str = atob(resp.encoded);
- let newResponse = {
- source: {
- binary: str,
- toString: () => "[wasm]",
- },
- contentType,
- };
- callback(newResponse);
- return newResponse;
- });
- }
-
- let longString = this._activeThread.threadLongString(source);
- return longString.substring(0, longString.length).then(function (resp) {
- if (resp.error) {
- callback(resp);
- return resp;
- }
-
- let newResponse = {
- source: resp.substring,
- contentType: contentType
- };
- callback(newResponse);
- return newResponse;
- });
- },
-
- /**
- * Request to set a breakpoint in the specified location.
- *
- * @param object location
- * The location and condition of the breakpoint in
- * the form of { line[, column, condition] }.
- * @param function onResponse
- * Called with the thread's response.
- */
- setBreakpoint: function ({ line, column, condition, noSliding }, onResponse = noop) {
- // A helper function that sets the breakpoint.
- let doSetBreakpoint = callback => {
- let root = this._client.mainRoot;
- let location = {
- line,
- column,
- };
-
- let packet = {
- to: this.actor,
- type: "setBreakpoint",
- location,
- condition,
- noSliding,
- };
-
- // Backwards compatibility: send the breakpoint request to the
- // thread if the server doesn't support Debugger.Source actors.
- if (!root.traits.debuggerSourceActors) {
- packet.to = this._activeThread.actor;
- packet.location.url = this.url;
- }
-
- return this._client.request(packet).then(response => {
- // Ignoring errors, since the user may be setting a breakpoint in a
- // dead script that will reappear on a page reload.
- let bpClient;
- if (response.actor) {
- bpClient = new BreakpointClient(
- this._client,
- this,
- response.actor,
- location,
- root.traits.conditionalBreakpoints ? condition : undefined
- );
- }
- onResponse(response, bpClient);
- if (callback) {
- callback();
- }
- return [response, bpClient];
- });
- };
-
- // If the debuggee is paused, just set the breakpoint.
- if (this._activeThread.paused) {
- return doSetBreakpoint();
- }
- // Otherwise, force a pause in order to set the breakpoint.
- return this._activeThread.interrupt().then(response => {
- if (response.error) {
- // Can't set the breakpoint if pausing failed.
- onResponse(response);
- return response;
- }
-
- const { type, why } = response;
- const cleanUp = type == "paused" && why.type == "interrupted"
- ? () => this._activeThread.resume()
- : noop;
-
- return doSetBreakpoint(cleanUp);
- });
- }
-};
-
-/**
- * Breakpoint clients are used to remove breakpoints that are no longer used.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param sourceClient SourceClient
- * The source where this breakpoint exists
- * @param actor string
- * The actor ID for this breakpoint.
- * @param location object
- * The location of the breakpoint. This is an object with two properties:
- * url and line.
- * @param condition string
- * The conditional expression of the breakpoint
- */
-function BreakpointClient(client, sourceClient, actor, location, condition) {
- this._client = client;
- this._actor = actor;
- this.location = location;
- this.location.actor = sourceClient.actor;
- this.location.url = sourceClient.url;
- this.source = sourceClient;
- this.request = this._client.request;
-
- // The condition property should only exist if it's a truthy value
- if (condition) {
- this.condition = condition;
- }
-}
-
-BreakpointClient.prototype = {
-
- _actor: null,
- get actor() {
- return this._actor;
- },
- get _transport() {
- return this._client._transport;
- },
-
- /**
- * Remove the breakpoint from the server.
- */
- remove: DebuggerClient.requester({
- type: "delete"
- }),
-
- /**
- * Determines if this breakpoint has a condition
- */
- hasCondition: function () {
- let root = this._client.mainRoot;
- // XXX bug 990137: We will remove support for client-side handling of
- // conditional breakpoints
- if (root.traits.conditionalBreakpoints) {
- return "condition" in this;
- }
- return "conditionalExpression" in this;
- },
-
- /**
- * Get the condition of this breakpoint. Currently we have to
- * support locally emulated conditional breakpoints until the
- * debugger servers are updated (see bug 990137). We used a
- * different property when moving it server-side to ensure that we
- * are testing the right code.
- */
- getCondition: function () {
- let root = this._client.mainRoot;
- if (root.traits.conditionalBreakpoints) {
- return this.condition;
- }
- return this.conditionalExpression;
- },
-
- /**
- * Set the condition of this breakpoint
- */
- setCondition: function (gThreadClient, condition) {
- let root = this._client.mainRoot;
- let deferred = promise.defer();
-
- if (root.traits.conditionalBreakpoints) {
- let info = {
- line: this.location.line,
- column: this.location.column,
- condition: condition
- };
-
- // Remove the current breakpoint and add a new one with the
- // condition.
- this.remove(response => {
- if (response && response.error) {
- deferred.reject(response);
- return;
- }
-
- this.source.setBreakpoint(info, (resp, newBreakpoint) => {
- if (resp && resp.error) {
- deferred.reject(resp);
- } else {
- deferred.resolve(newBreakpoint);
- }
- });
- });
- } else {
- // The property shouldn't even exist if the condition is blank
- if (condition === "") {
- delete this.conditionalExpression;
- } else {
- this.conditionalExpression = condition;
- }
- deferred.resolve(this);
- }
-
- return deferred.promise;
- }
-};
-
-eventSource(BreakpointClient.prototype);
-
-/**
- * Environment clients are used to manipulate the lexical environment actors.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param form Object
- * The form sent across the remote debugging protocol.
- */
-function EnvironmentClient(client, form) {
- this._client = client;
- this._form = form;
- this.request = this._client.request;
-}
-exports.EnvironmentClient = EnvironmentClient;
-
-EnvironmentClient.prototype = {
-
- get actor() {
- return this._form.actor;
- },
- get _transport() {
- return this._client._transport;
- },
-
- /**
- * Fetches the bindings introduced by this lexical environment.
- */
- getBindings: DebuggerClient.requester({
- type: "bindings"
- }),
-
- /**
- * Changes the value of the identifier whose name is name (a string) to that
- * represented by value (a grip).
- */
- assign: DebuggerClient.requester({
- type: "assign",
- name: arg(0),
- value: arg(1)
- })
-};
-
-eventSource(EnvironmentClient.prototype);
+module.exports = RootClient;
copy from devtools/shared/client/main.js
copy to devtools/shared/client/source-client.js
--- a/devtools/shared/client/main.js
+++ b/devtools/shared/client/source-client.js
@@ -1,3001 +1,20 @@
-/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
-/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
-const { Ci, Cu } = require("chrome");
-const DevToolsUtils = require("devtools/shared/DevToolsUtils");
-const { getStack, callFunctionWithAsyncStack } = require("devtools/shared/platform/stack");
-
-const promise = Cu.import("resource://devtools/shared/deprecated-sync-thenables.js", {}).Promise;
-
-loader.lazyRequireGetter(this, "EventEmitter", "devtools/shared/event-emitter");
-loader.lazyRequireGetter(this, "WebConsoleClient", "devtools/shared/webconsole/client", true);
-loader.lazyRequireGetter(this, "DebuggerSocket", "devtools/shared/security/socket", true);
-loader.lazyRequireGetter(this, "Authentication", "devtools/shared/security/auth");
+const {DebuggerClient} = require("./debugger-client");
+loader.lazyRequireGetter(this, "BreakpointClient", "devtools/shared/client/breakpoint-client");
const noop = () => {};
/**
- * TODO: Get rid of this API in favor of EventTarget (bug 1042642)
- *
- * Add simple event notification to a prototype object. Any object that has
- * some use for event notifications or the observer pattern in general can be
- * augmented with the necessary facilities by passing its prototype to this
- * function.
- *
- * @param proto object
- * The prototype object that will be modified.
- */
-function eventSource(proto) {
- /**
- * Add a listener to the event source for a given event.
- *
- * @param name string
- * The event to listen for.
- * @param listener function
- * Called when the event is fired. If the same listener
- * is added more than once, it will be called once per
- * addListener call.
- */
- proto.addListener = function (name, listener) {
- if (typeof listener != "function") {
- throw TypeError("Listeners must be functions.");
- }
-
- if (!this._listeners) {
- this._listeners = {};
- }
-
- this._getListeners(name).push(listener);
- };
-
- /**
- * Add a listener to the event source for a given event. The
- * listener will be removed after it is called for the first time.
- *
- * @param name string
- * The event to listen for.
- * @param listener function
- * Called when the event is fired.
- */
- proto.addOneTimeListener = function (name, listener) {
- let l = (...args) => {
- this.removeListener(name, l);
- listener.apply(null, args);
- };
- this.addListener(name, l);
- };
-
- /**
- * Remove a listener from the event source previously added with
- * addListener().
- *
- * @param name string
- * The event name used during addListener to add the listener.
- * @param listener function
- * The callback to remove. If addListener was called multiple
- * times, all instances will be removed.
- */
- proto.removeListener = function (name, listener) {
- if (!this._listeners || (listener && !this._listeners[name])) {
- return;
- }
-
- if (!listener) {
- this._listeners[name] = [];
- } else {
- this._listeners[name] =
- this._listeners[name].filter(l => l != listener);
- }
- };
-
- /**
- * Returns the listeners for the specified event name. If none are defined it
- * initializes an empty list and returns that.
- *
- * @param name string
- * The event name.
- */
- proto._getListeners = function (name) {
- if (name in this._listeners) {
- return this._listeners[name];
- }
- this._listeners[name] = [];
- return this._listeners[name];
- };
-
- /**
- * Notify listeners of an event.
- *
- * @param name string
- * The event to fire.
- * @param arguments
- * All arguments will be passed along to the listeners,
- * including the name argument.
- */
- proto.emit = function () {
- if (!this._listeners) {
- return;
- }
-
- let name = arguments[0];
- let listeners = this._getListeners(name).slice(0);
-
- for (let listener of listeners) {
- try {
- listener.apply(null, arguments);
- } catch (e) {
- // Prevent a bad listener from interfering with the others.
- DevToolsUtils.reportException("notify event '" + name + "'", e);
- }
- }
- };
-}
-
-/**
- * Set of protocol messages that affect thread state, and the
- * state the actor is in after each message.
- */
-const ThreadStateTypes = {
- "paused": "paused",
- "resumed": "attached",
- "detached": "detached",
- "running": "attached"
-};
-
-/**
- * Set of protocol messages that are sent by the server without a prior request
- * by the client.
- */
-const UnsolicitedNotifications = {
- "consoleAPICall": "consoleAPICall",
- "eventNotification": "eventNotification",
- "fileActivity": "fileActivity",
- "lastPrivateContextExited": "lastPrivateContextExited",
- "logMessage": "logMessage",
- "networkEvent": "networkEvent",
- "networkEventUpdate": "networkEventUpdate",
- "newGlobal": "newGlobal",
- "newScript": "newScript",
- "tabDetached": "tabDetached",
- "tabListChanged": "tabListChanged",
- "reflowActivity": "reflowActivity",
- "addonListChanged": "addonListChanged",
- "workerListChanged": "workerListChanged",
- "serviceWorkerRegistrationListChanged": "serviceWorkerRegistrationList",
- "tabNavigated": "tabNavigated",
- "frameUpdate": "frameUpdate",
- "pageError": "pageError",
- "documentLoad": "documentLoad",
- "enteredFrame": "enteredFrame",
- "exitedFrame": "exitedFrame",
- "appOpen": "appOpen",
- "appClose": "appClose",
- "appInstall": "appInstall",
- "appUninstall": "appUninstall",
- "evaluationResult": "evaluationResult",
- "newSource": "newSource",
- "updatedSource": "updatedSource",
- "inspectObject": "inspectObject"
-};
-
-/**
- * Set of pause types that are sent by the server and not as an immediate
- * response to a client request.
- */
-const UnsolicitedPauses = {
- "resumeLimit": "resumeLimit",
- "debuggerStatement": "debuggerStatement",
- "breakpoint": "breakpoint",
- "DOMEvent": "DOMEvent",
- "watchpoint": "watchpoint",
- "exception": "exception"
-};
-
-/**
- * Creates a client for the remote debugging protocol server. This client
- * provides the means to communicate with the server and exchange the messages
- * required by the protocol in a traditional JavaScript API.
- */
-const DebuggerClient = exports.DebuggerClient = function (transport) {
- this._transport = transport;
- this._transport.hooks = this;
-
- // Map actor ID to client instance for each actor type.
- this._clients = new Map();
-
- this._pendingRequests = new Map();
- this._activeRequests = new Map();
- this._eventsEnabled = true;
-
- this.traits = {};
-
- this.request = this.request.bind(this);
- this.localTransport = this._transport.onOutputStreamReady === undefined;
-
- /*
- * As the first thing on the connection, expect a greeting packet from
- * the connection's root actor.
- */
- this.mainRoot = null;
- this.expectReply("root", (packet) => {
- this.mainRoot = new RootClient(this, packet);
- this.emit("connected", packet.applicationType, packet.traits);
- });
-};
-
-/**
- * A declarative helper for defining methods that send requests to the server.
- *
- * @param packetSkeleton
- * The form of the packet to send. Can specify fields to be filled from
- * the parameters by using the |arg| function.
- * @param before
- * The function to call before sending the packet. Is passed the packet,
- * and the return value is used as the new packet. The |this| context is
- * the instance of the client object we are defining a method for.
- * @param after
- * The function to call after the response is received. It is passed the
- * response, and the return value is considered the new response that
- * will be passed to the callback. The |this| context is the instance of
- * the client object we are defining a method for.
- * @return Request
- * The `Request` object that is a Promise object and resolves once
- * we receive the response. (See request method for more details)
- */
-DebuggerClient.requester = function (packetSkeleton, config = {}) {
- let { before, after } = config;
- return DevToolsUtils.makeInfallible(function (...args) {
- let outgoingPacket = {
- to: packetSkeleton.to || this.actor
- };
-
- let maxPosition = -1;
- for (let k of Object.keys(packetSkeleton)) {
- if (packetSkeleton[k] instanceof DebuggerClient.Argument) {
- let { position } = packetSkeleton[k];
- outgoingPacket[k] = packetSkeleton[k].getArgument(args);
- maxPosition = Math.max(position, maxPosition);
- } else {
- outgoingPacket[k] = packetSkeleton[k];
- }
- }
-
- if (before) {
- outgoingPacket = before.call(this, outgoingPacket);
- }
-
- return this.request(outgoingPacket, DevToolsUtils.makeInfallible((response) => {
- if (after) {
- let { from } = response;
- response = after.call(this, response);
- if (!response.from) {
- response.from = from;
- }
- }
-
- // The callback is always the last parameter.
- let thisCallback = args[maxPosition + 1];
- if (thisCallback) {
- thisCallback(response);
- }
- return response;
- }, "DebuggerClient.requester request callback"));
- }, "DebuggerClient.requester");
-};
-
-function arg(pos) {
- return new DebuggerClient.Argument(pos);
-}
-
-DebuggerClient.Argument = function (position) {
- this.position = position;
-};
-
-DebuggerClient.Argument.prototype.getArgument = function (params) {
- if (!(this.position in params)) {
- throw new Error("Bad index into params: " + this.position);
- }
- return params[this.position];
-};
-
-// Expose these to save callers the trouble of importing DebuggerSocket
-DebuggerClient.socketConnect = function (options) {
- // Defined here instead of just copying the function to allow lazy-load
- return DebuggerSocket.connect(options);
-};
-DevToolsUtils.defineLazyGetter(DebuggerClient, "Authenticators", () => {
- return Authentication.Authenticators;
-});
-DevToolsUtils.defineLazyGetter(DebuggerClient, "AuthenticationResult", () => {
- return Authentication.AuthenticationResult;
-});
-
-DebuggerClient.prototype = {
- /**
- * Connect to the server and start exchanging protocol messages.
- *
- * @param onConnected function
- * If specified, will be called when the greeting packet is
- * received from the debugging server.
- *
- * @return Promise
- * Resolves once connected with an array whose first element
- * is the application type, by default "browser", and the second
- * element is the traits object (help figure out the features
- * and behaviors of the server we connect to. See RootActor).
- */
- connect: function (onConnected) {
- let deferred = promise.defer();
- this.emit("connect");
-
- // Also emit the event on the |DebuggerClient| object (not on the instance),
- // so it's possible to track all instances.
- EventEmitter.emit(DebuggerClient, "connect", this);
-
- this.addOneTimeListener("connected", (name, applicationType, traits) => {
- this.traits = traits;
- if (onConnected) {
- onConnected(applicationType, traits);
- }
- deferred.resolve([applicationType, traits]);
- });
-
- this._transport.ready();
- return deferred.promise;
- },
-
- /**
- * Shut down communication with the debugging server.
- *
- * @param onClosed function
- * If specified, will be called when the debugging connection
- * has been closed. This parameter is deprecated - please use
- * the returned Promise.
- * @return Promise
- * Resolves after the underlying transport is closed.
- */
- close: function (onClosed) {
- let deferred = promise.defer();
- if (onClosed) {
- deferred.promise.then(onClosed);
- }
-
- // Disable detach event notifications, because event handlers will be in a
- // cleared scope by the time they run.
- this._eventsEnabled = false;
-
- let cleanup = () => {
- this._transport.close();
- this._transport = null;
- };
-
- // If the connection is already closed,
- // there is no need to detach client
- // as we won't be able to send any message.
- if (this._closed) {
- cleanup();
- deferred.resolve();
- return deferred.promise;
- }
-
- this.addOneTimeListener("closed", deferred.resolve);
-
- // Call each client's `detach` method by calling
- // lastly registered ones first to give a chance
- // to detach child clients first.
- let clients = [...this._clients.values()];
- this._clients.clear();
- const detachClients = () => {
- let client = clients.pop();
- if (!client) {
- // All clients detached.
- cleanup();
- return;
- }
- if (client.detach) {
- client.detach(detachClients);
- return;
- }
- detachClients();
- };
- detachClients();
-
- return deferred.promise;
- },
-
- /*
- * This function exists only to preserve DebuggerClient's interface;
- * new code should say 'client.mainRoot.listTabs()'.
- */
- listTabs: function (onResponse) {
- return this.mainRoot.listTabs(onResponse);
- },
-
- /*
- * This function exists only to preserve DebuggerClient's interface;
- * new code should say 'client.mainRoot.listAddons()'.
- */
- listAddons: function (onResponse) {
- return this.mainRoot.listAddons(onResponse);
- },
-
- getTab: function (filter) {
- return this.mainRoot.getTab(filter);
- },
-
- /**
- * Attach to a tab actor.
- *
- * @param string tabActor
- * The actor ID for the tab to attach.
- * @param function onResponse
- * Called with the response packet and a TabClient
- * (which will be undefined on error).
- */
- attachTab: function (tabActor, onResponse = noop) {
- if (this._clients.has(tabActor)) {
- let cachedTab = this._clients.get(tabActor);
- let cachedResponse = {
- cacheDisabled: cachedTab.cacheDisabled,
- javascriptEnabled: cachedTab.javascriptEnabled,
- traits: cachedTab.traits,
- };
- DevToolsUtils.executeSoon(() => onResponse(cachedResponse, cachedTab));
- return promise.resolve([cachedResponse, cachedTab]);
- }
-
- let packet = {
- to: tabActor,
- type: "attach"
- };
- return this.request(packet).then(response => {
- let tabClient;
- if (!response.error) {
- tabClient = new TabClient(this, response);
- this.registerClient(tabClient);
- }
- onResponse(response, tabClient);
- return [response, tabClient];
- });
- },
-
- attachWorker: function (workerActor, onResponse = noop) {
- let workerClient = this._clients.get(workerActor);
- if (workerClient !== undefined) {
- let response = {
- from: workerClient.actor,
- type: "attached",
- url: workerClient.url
- };
- DevToolsUtils.executeSoon(() => onResponse(response, workerClient));
- return promise.resolve([response, workerClient]);
- }
-
- return this.request({ to: workerActor, type: "attach" }).then(response => {
- if (response.error) {
- onResponse(response, null);
- return [response, null];
- }
-
- workerClient = new WorkerClient(this, response);
- this.registerClient(workerClient);
- onResponse(response, workerClient);
- return [response, workerClient];
- });
- },
-
- /**
- * Attach to an addon actor.
- *
- * @param string addonActor
- * The actor ID for the addon to attach.
- * @param function onResponse
- * Called with the response packet and a AddonClient
- * (which will be undefined on error).
- */
- attachAddon: function (addonActor, onResponse = noop) {
- let packet = {
- to: addonActor,
- type: "attach"
- };
- return this.request(packet).then(response => {
- let addonClient;
- if (!response.error) {
- addonClient = new AddonClient(this, addonActor);
- this.registerClient(addonClient);
- this.activeAddon = addonClient;
- }
- onResponse(response, addonClient);
- return [response, addonClient];
- });
- },
-
- /**
- * Attach to a Web Console actor.
- *
- * @param string consoleActor
- * The ID for the console actor to attach to.
- * @param array listeners
- * The console listeners you want to start.
- * @param function onResponse
- * Called with the response packet and a WebConsoleClient
- * instance (which will be undefined on error).
- */
- attachConsole:
- function (consoleActor, listeners, onResponse = noop) {
- let packet = {
- to: consoleActor,
- type: "startListeners",
- listeners: listeners,
- };
-
- return this.request(packet).then(response => {
- let consoleClient;
- if (!response.error) {
- if (this._clients.has(consoleActor)) {
- consoleClient = this._clients.get(consoleActor);
- } else {
- consoleClient = new WebConsoleClient(this, response);
- this.registerClient(consoleClient);
- }
- }
- onResponse(response, consoleClient);
- return [response, consoleClient];
- });
- },
-
- /**
- * Attach to a global-scoped thread actor for chrome debugging.
- *
- * @param string threadActor
- * The actor ID for the thread to attach.
- * @param function onResponse
- * Called with the response packet and a ThreadClient
- * (which will be undefined on error).
- * @param object options
- * Configuration options.
- * - useSourceMaps: whether to use source maps or not.
- */
- attachThread: function (threadActor, onResponse = noop, options = {}) {
- if (this._clients.has(threadActor)) {
- let client = this._clients.get(threadActor);
- DevToolsUtils.executeSoon(() => onResponse({}, client));
- return promise.resolve([{}, client]);
- }
-
- let packet = {
- to: threadActor,
- type: "attach",
- options,
- };
- return this.request(packet).then(response => {
- let threadClient;
- if (!response.error) {
- threadClient = new ThreadClient(this, threadActor);
- this.registerClient(threadClient);
- }
- onResponse(response, threadClient);
- return [response, threadClient];
- });
- },
-
- /**
- * Attach to a trace actor.
- *
- * @param string traceActor
- * The actor ID for the tracer to attach.
- * @param function onResponse
- * Called with the response packet and a TraceClient
- * (which will be undefined on error).
- */
- attachTracer: function (traceActor, onResponse = noop) {
- if (this._clients.has(traceActor)) {
- let client = this._clients.get(traceActor);
- DevToolsUtils.executeSoon(() => onResponse({}, client));
- return promise.resolve([{}, client]);
- }
-
- let packet = {
- to: traceActor,
- type: "attach"
- };
- return this.request(packet).then(response => {
- let traceClient;
- if (!response.error) {
- traceClient = new TraceClient(this, traceActor);
- this.registerClient(traceClient);
- }
- onResponse(response, traceClient);
- return [response, traceClient];
- });
- },
-
- /**
- * Fetch the ChromeActor for the main process or ChildProcessActor for a
- * a given child process ID.
- *
- * @param number id
- * The ID for the process to attach (returned by `listProcesses`).
- * Connected to the main process if omitted, or is 0.
- */
- getProcess: function (id) {
- let packet = {
- to: "root",
- type: "getProcess"
- };
- if (typeof (id) == "number") {
- packet.id = id;
- }
- return this.request(packet);
- },
-
- /**
- * Release an object actor.
- *
- * @param string actor
- * The actor ID to send the request to.
- * @param onResponse function
- * If specified, will be called with the response packet when
- * debugging server responds.
- */
- release: DebuggerClient.requester({
- to: arg(0),
- type: "release"
- }),
-
- /**
- * Send a request to the debugging server.
- *
- * @param packet object
- * A JSON packet to send to the debugging server.
- * @param onResponse function
- * If specified, will be called with the JSON response packet when
- * debugging server responds.
- * @return Request
- * This object emits a number of events to allow you to respond to
- * different parts of the request lifecycle.
- * It is also a Promise object, with a `then` method, that is resolved
- * whenever a JSON or a Bulk response is received; and is rejected
- * if the response is an error.
- * Note: This return value can be ignored if you are using JSON alone,
- * because the callback provided in |onResponse| will be bound to the
- * "json-reply" event automatically.
- *
- * Events emitted:
- * * json-reply: The server replied with a JSON packet, which is
- * passed as event data.
- * * bulk-reply: The server replied with bulk data, which you can read
- * using the event data object containing:
- * * actor: Name of actor that received the packet
- * * type: Name of actor's method that was called on receipt
- * * length: Size of the data to be read
- * * stream: This input stream should only be used directly if you
- * can ensure that you will read exactly |length| bytes
- * and will not close the stream when reading is complete
- * * done: If you use the stream directly (instead of |copyTo|
- * below), you must signal completion by resolving /
- * rejecting this deferred. If it's rejected, the
- * transport will be closed. If an Error is supplied as a
- * rejection value, it will be logged via |dumpn|. If you
- * do use |copyTo|, resolving is taken care of for you
- * when copying completes.
- * * copyTo: A helper function for getting your data out of the
- * stream that meets the stream handling requirements
- * above, and has the following signature:
- * @param output nsIAsyncOutputStream
- * The stream to copy to.
- * @return Promise
- * The promise is resolved when copying completes or
- * rejected if any (unexpected) errors occur.
- * This object also emits "progress" events for each chunk
- * that is copied. See stream-utils.js.
- */
- request: function (packet, onResponse) {
- if (!this.mainRoot) {
- throw Error("Have not yet received a hello packet from the server.");
- }
- let type = packet.type || "";
- if (!packet.to) {
- throw Error("'" + type + "' request packet has no destination.");
- }
-
- // The onResponse callback might modify the response, so we need to call
- // it and resolve the promise with its result if it's truthy.
- const safeOnResponse = response => {
- if (!onResponse) {
- return response;
- }
- return onResponse(response) || response;
- };
-
- if (this._closed) {
- let msg = "'" + type + "' request packet to " +
- "'" + packet.to + "' " +
- "can't be sent as the connection is closed.";
- let resp = { error: "connectionClosed", message: msg };
- return promise.reject(safeOnResponse(resp));
- }
-
- let request = new Request(packet);
- request.format = "json";
- request.stack = getStack();
-
- // Implement a Promise like API on the returned object
- // that resolves/rejects on request response
- let deferred = promise.defer();
- function listenerJson(resp) {
- removeRequestListeners();
- if (resp.error) {
- deferred.reject(safeOnResponse(resp));
- } else {
- deferred.resolve(safeOnResponse(resp));
- }
- }
- function listenerBulk(resp) {
- removeRequestListeners();
- deferred.resolve(safeOnResponse(resp));
- }
-
- const removeRequestListeners = () => {
- request.off("json-reply", listenerJson);
- request.off("bulk-reply", listenerBulk);
- };
-
- request.on("json-reply", listenerJson);
- request.on("bulk-reply", listenerBulk);
-
- this._sendOrQueueRequest(request);
- request.then = deferred.promise.then.bind(deferred.promise);
-
- return request;
- },
-
- /**
- * Transmit streaming data via a bulk request.
- *
- * This method initiates the bulk send process by queuing up the header data.
- * The caller receives eventual access to a stream for writing.
- *
- * Since this opens up more options for how the server might respond (it could
- * send back either JSON or bulk data), and the returned Request object emits
- * events for different stages of the request process that you may want to
- * react to.
- *
- * @param request Object
- * This is modeled after the format of JSON packets above, but does not
- * actually contain the data, but is instead just a routing header:
- * * actor: Name of actor that will receive the packet
- * * type: Name of actor's method that should be called on receipt
- * * length: Size of the data to be sent
- * @return Request
- * This object emits a number of events to allow you to respond to
- * different parts of the request lifecycle.
- *
- * Events emitted:
- * * bulk-send-ready: Ready to send bulk data to the server, using the
- * event data object containing:
- * * stream: This output stream should only be used directly if
- * you can ensure that you will write exactly |length|
- * bytes and will not close the stream when writing is
- * complete
- * * done: If you use the stream directly (instead of |copyFrom|
- * below), you must signal completion by resolving /
- * rejecting this deferred. If it's rejected, the
- * transport will be closed. If an Error is supplied as
- * a rejection value, it will be logged via |dumpn|. If
- * you do use |copyFrom|, resolving is taken care of for
- * you when copying completes.
- * * copyFrom: A helper function for getting your data onto the
- * stream that meets the stream handling requirements
- * above, and has the following signature:
- * @param input nsIAsyncInputStream
- * The stream to copy from.
- * @return Promise
- * The promise is resolved when copying completes or
- * rejected if any (unexpected) errors occur.
- * This object also emits "progress" events for each chunk
- * that is copied. See stream-utils.js.
- * * json-reply: The server replied with a JSON packet, which is
- * passed as event data.
- * * bulk-reply: The server replied with bulk data, which you can read
- * using the event data object containing:
- * * actor: Name of actor that received the packet
- * * type: Name of actor's method that was called on receipt
- * * length: Size of the data to be read
- * * stream: This input stream should only be used directly if you
- * can ensure that you will read exactly |length| bytes
- * and will not close the stream when reading is complete
- * * done: If you use the stream directly (instead of |copyTo|
- * below), you must signal completion by resolving /
- * rejecting this deferred. If it's rejected, the
- * transport will be closed. If an Error is supplied as a
- * rejection value, it will be logged via |dumpn|. If you
- * do use |copyTo|, resolving is taken care of for you
- * when copying completes.
- * * copyTo: A helper function for getting your data out of the
- * stream that meets the stream handling requirements
- * above, and has the following signature:
- * @param output nsIAsyncOutputStream
- * The stream to copy to.
- * @return Promise
- * The promise is resolved when copying completes or
- * rejected if any (unexpected) errors occur.
- * This object also emits "progress" events for each chunk
- * that is copied. See stream-utils.js.
- */
- startBulkRequest: function (request) {
- if (!this.traits.bulk) {
- throw Error("Server doesn't support bulk transfers");
- }
- if (!this.mainRoot) {
- throw Error("Have not yet received a hello packet from the server.");
- }
- if (!request.type) {
- throw Error("Bulk packet is missing the required 'type' field.");
- }
- if (!request.actor) {
- throw Error("'" + request.type + "' bulk packet has no destination.");
- }
- if (!request.length) {
- throw Error("'" + request.type + "' bulk packet has no length.");
- }
-
- request = new Request(request);
- request.format = "bulk";
-
- this._sendOrQueueRequest(request);
-
- return request;
- },
-
- /**
- * If a new request can be sent immediately, do so. Otherwise, queue it.
- */
- _sendOrQueueRequest(request) {
- let actor = request.actor;
- if (!this._activeRequests.has(actor)) {
- this._sendRequest(request);
- } else {
- this._queueRequest(request);
- }
- },
-
- /**
- * Send a request.
- * @throws Error if there is already an active request in flight for the same
- * actor.
- */
- _sendRequest(request) {
- let actor = request.actor;
- this.expectReply(actor, request);
-
- if (request.format === "json") {
- this._transport.send(request.request);
- return;
- }
-
- this._transport.startBulkSend(request.request).then((...args) => {
- request.emit("bulk-send-ready", ...args);
- });
- },
-
- /**
- * Queue a request to be sent later. Queues are only drained when an in
- * flight request to a given actor completes.
- */
- _queueRequest(request) {
- let actor = request.actor;
- let queue = this._pendingRequests.get(actor) || [];
- queue.push(request);
- this._pendingRequests.set(actor, queue);
- },
-
- /**
- * Attempt the next request to a given actor (if any).
- */
- _attemptNextRequest(actor) {
- if (this._activeRequests.has(actor)) {
- return;
- }
- let queue = this._pendingRequests.get(actor);
- if (!queue) {
- return;
- }
- let request = queue.shift();
- if (queue.length === 0) {
- this._pendingRequests.delete(actor);
- }
- this._sendRequest(request);
- },
-
- /**
- * Arrange to hand the next reply from |actor| to the handler bound to
- * |request|.
- *
- * DebuggerClient.prototype.request / startBulkRequest usually takes care of
- * establishing the handler for a given request, but in rare cases (well,
- * greetings from new root actors, is the only case at the moment) we must be
- * prepared for a "reply" that doesn't correspond to any request we sent.
- */
- expectReply: function (actor, request) {
- if (this._activeRequests.has(actor)) {
- throw Error("clashing handlers for next reply from " + actor);
- }
-
- // If a handler is passed directly (as it is with the handler for the root
- // actor greeting), create a dummy request to bind this to.
- if (typeof request === "function") {
- let handler = request;
- request = new Request();
- request.on("json-reply", handler);
- }
-
- this._activeRequests.set(actor, request);
- },
-
- // Transport hooks.
-
- /**
- * Called by DebuggerTransport to dispatch incoming packets as appropriate.
- *
- * @param packet object
- * The incoming packet.
- */
- onPacket: function (packet) {
- if (!packet.from) {
- DevToolsUtils.reportException(
- "onPacket",
- new Error("Server did not specify an actor, dropping packet: " +
- JSON.stringify(packet)));
- return;
- }
-
- // If we have a registered Front for this actor, let it handle the packet
- // and skip all the rest of this unpleasantness.
- let front = this.getActor(packet.from);
- if (front) {
- front.onPacket(packet);
- return;
- }
-
- // Check for "forwardingCancelled" here instead of using a client to handle it.
- // This is necessary because we might receive this event while the client is closing,
- // and the clients have already been removed by that point.
- if (this.mainRoot &&
- packet.from == this.mainRoot.actor &&
- packet.type == "forwardingCancelled") {
- this.purgeRequests(packet.prefix);
- return;
- }
-
- if (this._clients.has(packet.from) && packet.type) {
- let client = this._clients.get(packet.from);
- let type = packet.type;
- if (client.events.indexOf(type) != -1) {
- client.emit(type, packet);
- // we ignore the rest, as the client is expected to handle this packet.
- return;
- }
- }
-
- let activeRequest;
- // See if we have a handler function waiting for a reply from this
- // actor. (Don't count unsolicited notifications or pauses as
- // replies.)
- if (this._activeRequests.has(packet.from) &&
- !(packet.type in UnsolicitedNotifications) &&
- !(packet.type == ThreadStateTypes.paused &&
- packet.why.type in UnsolicitedPauses)) {
- activeRequest = this._activeRequests.get(packet.from);
- this._activeRequests.delete(packet.from);
- }
-
- // If there is a subsequent request for the same actor, hand it off to the
- // transport. Delivery of packets on the other end is always async, even
- // in the local transport case.
- this._attemptNextRequest(packet.from);
-
- // Packets that indicate thread state changes get special treatment.
- if (packet.type in ThreadStateTypes &&
- this._clients.has(packet.from) &&
- typeof this._clients.get(packet.from)._onThreadState == "function") {
- this._clients.get(packet.from)._onThreadState(packet);
- }
-
- // TODO: Bug 1151156 - Remove once Gecko 40 is on b2g-stable.
- if (!this.traits.noNeedToFakeResumptionOnNavigation) {
- // On navigation the server resumes, so the client must resume as well.
- // We achieve that by generating a fake resumption packet that triggers
- // the client's thread state change listeners.
- if (packet.type == UnsolicitedNotifications.tabNavigated &&
- this._clients.has(packet.from) &&
- this._clients.get(packet.from).thread) {
- let thread = this._clients.get(packet.from).thread;
- let resumption = { from: thread._actor, type: "resumed" };
- thread._onThreadState(resumption);
- }
- }
-
- // Only try to notify listeners on events, not responses to requests
- // that lack a packet type.
- if (packet.type) {
- this.emit(packet.type, packet);
- }
-
- if (activeRequest) {
- let emitReply = () => activeRequest.emit("json-reply", packet);
- if (activeRequest.stack) {
- callFunctionWithAsyncStack(emitReply, activeRequest.stack,
- "DevTools RDP");
- } else {
- emitReply();
- }
- }
- },
-
- /**
- * Called by the DebuggerTransport to dispatch incoming bulk packets as
- * appropriate.
- *
- * @param packet object
- * The incoming packet, which contains:
- * * actor: Name of actor that will receive the packet
- * * type: Name of actor's method that should be called on receipt
- * * length: Size of the data to be read
- * * stream: This input stream should only be used directly if you can
- * ensure that you will read exactly |length| bytes and will
- * not close the stream when reading is complete
- * * done: If you use the stream directly (instead of |copyTo|
- * below), you must signal completion by resolving /
- * rejecting this deferred. If it's rejected, the transport
- * will be closed. If an Error is supplied as a rejection
- * value, it will be logged via |dumpn|. If you do use
- * |copyTo|, resolving is taken care of for you when copying
- * completes.
- * * copyTo: A helper function for getting your data out of the stream
- * that meets the stream handling requirements above, and has
- * the following signature:
- * @param output nsIAsyncOutputStream
- * The stream to copy to.
- * @return Promise
- * The promise is resolved when copying completes or rejected
- * if any (unexpected) errors occur.
- * This object also emits "progress" events for each chunk
- * that is copied. See stream-utils.js.
- */
- onBulkPacket: function (packet) {
- let { actor } = packet;
-
- if (!actor) {
- DevToolsUtils.reportException(
- "onBulkPacket",
- new Error("Server did not specify an actor, dropping bulk packet: " +
- JSON.stringify(packet)));
- return;
- }
-
- // See if we have a handler function waiting for a reply from this
- // actor.
- if (!this._activeRequests.has(actor)) {
- return;
- }
-
- let activeRequest = this._activeRequests.get(actor);
- this._activeRequests.delete(actor);
-
- // If there is a subsequent request for the same actor, hand it off to the
- // transport. Delivery of packets on the other end is always async, even
- // in the local transport case.
- this._attemptNextRequest(actor);
-
- activeRequest.emit("bulk-reply", packet);
- },
-
- /**
- * Called by DebuggerTransport when the underlying stream is closed.
- *
- * @param status nsresult
- * The status code that corresponds to the reason for closing
- * the stream.
- */
- onClosed: function () {
- this._closed = true;
- this.emit("closed");
-
- this.purgeRequests();
-
- // The |_pools| array on the client-side currently is used only by
- // protocol.js to store active fronts, mirroring the actor pools found in
- // the server. So, read all usages of "pool" as "protocol.js front".
- //
- // In the normal case where we shutdown cleanly, the toolbox tells each tool
- // to close, and they each call |destroy| on any fronts they were using.
- // When |destroy| or |cleanup| is called on a protocol.js front, it also
- // removes itself from the |_pools| array. Once the toolbox has shutdown,
- // the connection is closed, and we reach here. All fronts (should have
- // been) |destroy|ed, so |_pools| should empty.
- //
- // If the connection instead aborts unexpectedly, we may end up here with
- // all fronts used during the life of the connection. So, we call |cleanup|
- // on them clear their state, reject pending requests, and remove themselves
- // from |_pools|. This saves the toolbox from hanging indefinitely, in case
- // it waits for some server response before shutdown that will now never
- // arrive.
- for (let pool of this._pools) {
- pool.cleanup();
- }
- },
-
- /**
- * Purge pending and active requests in this client.
- *
- * @param prefix string (optional)
- * If a prefix is given, only requests for actor IDs that start with the prefix
- * will be cleaned up. This is useful when forwarding of a portion of requests
- * is cancelled on the server.
- */
- purgeRequests(prefix = "") {
- let reject = function (type, request) {
- // Server can send packets on its own and client only pass a callback
- // to expectReply, so that there is no request object.
- let msg;
- if (request.request) {
- msg = "'" + request.request.type + "' " + type + " request packet" +
- " to '" + request.actor + "' " +
- "can't be sent as the connection just closed.";
- } else {
- msg = "server side packet can't be received as the connection just closed.";
- }
- let packet = { error: "connectionClosed", message: msg };
- request.emit("json-reply", packet);
- };
-
- let pendingRequestsToReject = [];
- this._pendingRequests.forEach((requests, actor) => {
- if (!actor.startsWith(prefix)) {
- return;
- }
- this._pendingRequests.delete(actor);
- pendingRequestsToReject = pendingRequestsToReject.concat(requests);
- });
- pendingRequestsToReject.forEach(request => reject("pending", request));
-
- let activeRequestsToReject = [];
- this._activeRequests.forEach((request, actor) => {
- if (!actor.startsWith(prefix)) {
- return;
- }
- this._activeRequests.delete(actor);
- activeRequestsToReject = activeRequestsToReject.concat(request);
- });
- activeRequestsToReject.forEach(request => reject("active", request));
- },
-
- /**
- * Search for all requests in process for this client, including those made via
- * protocol.js and wait all of them to complete. Since the requests seen when this is
- * first called may in turn trigger more requests, we keep recursing through this
- * function until there is no more activity.
- *
- * This is a fairly heavy weight process, so it's only meant to be used in tests.
- *
- * @return Promise
- * Resolved when all requests have settled.
- */
- waitForRequestsToSettle() {
- let requests = [];
-
- // Gather all pending and active requests in this client
- // The request object supports a Promise API for completion (it has .then())
- this._pendingRequests.forEach(requestsForActor => {
- // Each value is an array of pending requests
- requests = requests.concat(requestsForActor);
- });
- this._activeRequests.forEach(requestForActor => {
- // Each value is a single active request
- requests = requests.concat(requestForActor);
- });
-
- // protocol.js
- // Use a Set because some fronts (like domwalker) seem to have multiple parents.
- let fronts = new Set();
- let poolsToVisit = [...this._pools];
-
- // With protocol.js, each front can potentially have it's own pools containing child
- // fronts, forming a tree. Descend through all the pools to locate all child fronts.
- while (poolsToVisit.length) {
- let pool = poolsToVisit.shift();
- fronts.add(pool);
- for (let child of pool.poolChildren()) {
- poolsToVisit.push(child);
- }
- }
-
- // For each front, wait for its requests to settle
- for (let front of fronts) {
- if (front.hasRequests()) {
- requests.push(front.waitForRequestsToSettle());
- }
- }
-
- // Abort early if there are no requests
- if (!requests.length) {
- return Promise.resolve();
- }
-
- return DevToolsUtils.settleAll(requests).catch(() => {
- // One of the requests might have failed, but ignore that situation here and pipe
- // both success and failure through the same path. The important part is just that
- // we waited.
- }).then(() => {
- // Repeat, more requests may have started in response to those we just waited for
- return this.waitForRequestsToSettle();
- });
- },
-
- registerClient: function (client) {
- let actorID = client.actor;
- if (!actorID) {
- throw new Error("DebuggerServer.registerClient expects " +
- "a client instance with an `actor` attribute.");
- }
- if (!Array.isArray(client.events)) {
- throw new Error("DebuggerServer.registerClient expects " +
- "a client instance with an `events` attribute " +
- "that is an array.");
- }
- if (client.events.length > 0 && typeof (client.emit) != "function") {
- throw new Error("DebuggerServer.registerClient expects " +
- "a client instance with non-empty `events` array to" +
- "have an `emit` function.");
- }
- if (this._clients.has(actorID)) {
- throw new Error("DebuggerServer.registerClient already registered " +
- "a client for this actor.");
- }
- this._clients.set(actorID, client);
- },
-
- unregisterClient: function (client) {
- let actorID = client.actor;
- if (!actorID) {
- throw new Error("DebuggerServer.unregisterClient expects " +
- "a Client instance with a `actor` attribute.");
- }
- this._clients.delete(actorID);
- },
-
- /**
- * Actor lifetime management, echos the server's actor pools.
- */
- __pools: null,
- get _pools() {
- if (this.__pools) {
- return this.__pools;
- }
- this.__pools = new Set();
- return this.__pools;
- },
-
- addActorPool: function (pool) {
- this._pools.add(pool);
- },
- removeActorPool: function (pool) {
- this._pools.delete(pool);
- },
- getActor: function (actorID) {
- let pool = this.poolFor(actorID);
- return pool ? pool.get(actorID) : null;
- },
-
- poolFor: function (actorID) {
- for (let pool of this._pools) {
- if (pool.has(actorID)) {
- return pool;
- }
- }
- return null;
- },
-
- /**
- * Currently attached addon.
- */
- activeAddon: null
-};
-
-eventSource(DebuggerClient.prototype);
-
-class Request extends EventEmitter {
- constructor(request) {
- super();
- this.request = request;
- }
-
- get actor() {
- return this.request.to || this.request.actor;
- }
-}
-
-/**
- * Creates a tab client for the remote debugging protocol server. This client
- * is a front to the tab actor created in the server side, hiding the protocol
- * details in a traditional JavaScript API.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param form object
- * The protocol form for this tab.
- */
-function TabClient(client, form) {
- this.client = client;
- this._actor = form.from;
- this._threadActor = form.threadActor;
- this.javascriptEnabled = form.javascriptEnabled;
- this.cacheDisabled = form.cacheDisabled;
- this.thread = null;
- this.request = this.client.request;
- this.traits = form.traits || {};
- this.events = ["workerListChanged"];
-}
-
-TabClient.prototype = {
- get actor() {
- return this._actor;
- },
- get _transport() {
- return this.client._transport;
- },
-
- /**
- * Attach to a thread actor.
- *
- * @param object options
- * Configuration options.
- * - useSourceMaps: whether to use source maps or not.
- * @param function onResponse
- * Called with the response packet and a ThreadClient
- * (which will be undefined on error).
- */
- attachThread: function (options = {}, onResponse = noop) {
- if (this.thread) {
- DevToolsUtils.executeSoon(() => onResponse({}, this.thread));
- return promise.resolve([{}, this.thread]);
- }
-
- let packet = {
- to: this._threadActor,
- type: "attach",
- options,
- };
- return this.request(packet).then(response => {
- if (!response.error) {
- this.thread = new ThreadClient(this, this._threadActor);
- this.client.registerClient(this.thread);
- }
- onResponse(response, this.thread);
- return [response, this.thread];
- });
- },
-
- /**
- * Detach the client from the tab actor.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- detach: DebuggerClient.requester({
- type: "detach"
- }, {
- before: function (packet) {
- if (this.thread) {
- this.thread.detach();
- }
- return packet;
- },
- after: function (response) {
- this.client.unregisterClient(this);
- return response;
- },
- }),
-
- /**
- * Bring the window to the front.
- */
- focus: DebuggerClient.requester({
- type: "focus"
- }, {}),
-
- /**
- * Reload the page in this tab.
- *
- * @param [optional] object options
- * An object with a `force` property indicating whether or not
- * this reload should skip the cache
- */
- reload: function (options = { force: false }) {
- return this._reload(options);
- },
- _reload: DebuggerClient.requester({
- type: "reload",
- options: arg(0)
- }),
-
- /**
- * Navigate to another URL.
- *
- * @param string url
- * The URL to navigate to.
- */
- navigateTo: DebuggerClient.requester({
- type: "navigateTo",
- url: arg(0)
- }),
-
- /**
- * Reconfigure the tab actor.
- *
- * @param object options
- * A dictionary object of the new options to use in the tab actor.
- * @param function onResponse
- * Called with the response packet.
- */
- reconfigure: DebuggerClient.requester({
- type: "reconfigure",
- options: arg(0)
- }),
-
- listWorkers: DebuggerClient.requester({
- type: "listWorkers"
- }),
-
- attachWorker: function (workerActor, onResponse) {
- return this.client.attachWorker(workerActor, onResponse);
- },
-};
-
-eventSource(TabClient.prototype);
-
-function WorkerClient(client, form) {
- this.client = client;
- this._actor = form.from;
- this._isClosed = false;
- this._url = form.url;
-
- this._onClose = this._onClose.bind(this);
-
- this.addListener("close", this._onClose);
-
- this.traits = {};
-}
-
-WorkerClient.prototype = {
- get _transport() {
- return this.client._transport;
- },
-
- get request() {
- return this.client.request;
- },
-
- get actor() {
- return this._actor;
- },
-
- get url() {
- return this._url;
- },
-
- get isClosed() {
- return this._isClosed;
- },
-
- detach: DebuggerClient.requester({ type: "detach" }, {
- after: function (response) {
- if (this.thread) {
- this.client.unregisterClient(this.thread);
- }
- this.client.unregisterClient(this);
- return response;
- },
- }),
-
- attachThread: function (options = {}, onResponse = noop) {
- if (this.thread) {
- let response = [{
- type: "connected",
- threadActor: this.thread._actor,
- consoleActor: this.consoleActor,
- }, this.thread];
- DevToolsUtils.executeSoon(() => onResponse(response));
- return response;
- }
-
- // The connect call on server doesn't attach the thread as of version 44.
- return this.request({
- to: this._actor,
- type: "connect",
- options,
- }).then(connectResponse => {
- if (connectResponse.error) {
- onResponse(connectResponse, null);
- return [connectResponse, null];
- }
-
- return this.request({
- to: connectResponse.threadActor,
- type: "attach",
- options,
- }).then(attachResponse => {
- if (attachResponse.error) {
- onResponse(attachResponse, null);
- }
-
- this.thread = new ThreadClient(this, connectResponse.threadActor);
- this.consoleActor = connectResponse.consoleActor;
- this.client.registerClient(this.thread);
-
- onResponse(connectResponse, this.thread);
- return [connectResponse, this.thread];
- });
- }, error => {
- onResponse(error, null);
- });
- },
-
- _onClose: function () {
- this.removeListener("close", this._onClose);
-
- if (this.thread) {
- this.client.unregisterClient(this.thread);
- }
- this.client.unregisterClient(this);
- this._isClosed = true;
- },
-
- reconfigure: function () {
- return Promise.resolve();
- },
-
- events: ["close"]
-};
-
-eventSource(WorkerClient.prototype);
-
-function AddonClient(client, actor) {
- this._client = client;
- this._actor = actor;
- this.request = this._client.request;
- this.events = [];
-}
-
-AddonClient.prototype = {
- get actor() {
- return this._actor;
- },
- get _transport() {
- return this._client._transport;
- },
-
- /**
- * Detach the client from the addon actor.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- detach: DebuggerClient.requester({
- type: "detach"
- }, {
- after: function (response) {
- if (this._client.activeAddon === this) {
- this._client.activeAddon = null;
- }
- this._client.unregisterClient(this);
- return response;
- },
- })
-};
-
-/**
- * A RootClient object represents a root actor on the server. Each
- * DebuggerClient keeps a RootClient instance representing the root actor
- * for the initial connection; DebuggerClient's 'listTabs' and
- * 'listChildProcesses' methods forward to that root actor.
- *
- * @param client object
- * The client connection to which this actor belongs.
- * @param greeting string
- * The greeting packet from the root actor we're to represent.
- *
- * Properties of a RootClient instance:
- *
- * @property actor string
- * The name of this child's root actor.
- * @property applicationType string
- * The application type, as given in the root actor's greeting packet.
- * @property traits object
- * The traits object, as given in the root actor's greeting packet.
- */
-function RootClient(client, greeting) {
- this._client = client;
- this.actor = greeting.from;
- this.applicationType = greeting.applicationType;
- this.traits = greeting.traits;
-}
-exports.RootClient = RootClient;
-
-RootClient.prototype = {
- constructor: RootClient,
-
- /**
- * Gets the "root" form, which lists all the global actors that affect the entire
- * browser. This can replace usages of `listTabs` that only wanted the global actors
- * and didn't actually care about tabs.
- */
- getRoot: DebuggerClient.requester({ type: "getRoot" }),
-
- /**
- * List the open tabs.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- listTabs: DebuggerClient.requester({ type: "listTabs" }),
-
- /**
- * List the installed addons.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- listAddons: DebuggerClient.requester({ type: "listAddons" }),
-
- /**
- * List the registered workers.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- listWorkers: DebuggerClient.requester({ type: "listWorkers" }),
-
- /**
- * List the registered service workers.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- listServiceWorkerRegistrations: DebuggerClient.requester({
- type: "listServiceWorkerRegistrations"
- }),
-
- /**
- * List the running processes.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- listProcesses: DebuggerClient.requester({ type: "listProcesses" }),
-
- /**
- * Fetch the TabActor for the currently selected tab, or for a specific
- * tab given as first parameter.
- *
- * @param [optional] object filter
- * A dictionary object with following optional attributes:
- * - outerWindowID: used to match tabs in parent process
- * - tabId: used to match tabs in child processes
- * - tab: a reference to xul:tab element
- * If nothing is specified, returns the actor for the currently
- * selected tab.
- */
- getTab: function (filter) {
- let packet = {
- to: this.actor,
- type: "getTab"
- };
-
- if (filter) {
- if (typeof (filter.outerWindowID) == "number") {
- packet.outerWindowID = filter.outerWindowID;
- } else if (typeof (filter.tabId) == "number") {
- packet.tabId = filter.tabId;
- } else if ("tab" in filter) {
- let browser = filter.tab.linkedBrowser;
- if (browser.frameLoader.tabParent) {
- // Tabs in child process
- packet.tabId = browser.frameLoader.tabParent.tabId;
- } else if (browser.outerWindowID) {
- // <xul:browser> tabs in parent process
- packet.outerWindowID = browser.outerWindowID;
- } else {
- // <iframe mozbrowser> tabs in parent process
- let windowUtils = browser.contentWindow
- .QueryInterface(Ci.nsIInterfaceRequestor)
- .getInterface(Ci.nsIDOMWindowUtils);
- packet.outerWindowID = windowUtils.outerWindowID;
- }
- } else {
- // Throw if a filter object have been passed but without
- // any clearly idenfified filter.
- throw new Error("Unsupported argument given to getTab request");
- }
- }
-
- return this.request(packet);
- },
-
- /**
- * Fetch the WindowActor for a specific window, like a browser window in
- * Firefox, but it can be used to reach any window in the process.
- *
- * @param number outerWindowID
- * The outerWindowID of the top level window you are looking for.
- */
- getWindow: function ({ outerWindowID }) {
- if (!outerWindowID) {
- throw new Error("Must specify outerWindowID");
- }
-
- let packet = {
- to: this.actor,
- type: "getWindow",
- outerWindowID,
- };
-
- return this.request(packet);
- },
-
- /**
- * Description of protocol's actors and methods.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- protocolDescription: DebuggerClient.requester({ type: "protocolDescription" }),
-
- /*
- * Methods constructed by DebuggerClient.requester require these forwards
- * on their 'this'.
- */
- get _transport() {
- return this._client._transport;
- },
- get request() {
- return this._client.request;
- }
-};
-
-/**
- * Creates a thread client for the remote debugging protocol server. This client
- * is a front to the thread actor created in the server side, hiding the
- * protocol details in a traditional JavaScript API.
- *
- * @param client DebuggerClient|TabClient
- * The parent of the thread (tab for tab-scoped debuggers, DebuggerClient
- * for chrome debuggers).
- * @param actor string
- * The actor ID for this thread.
- */
-function ThreadClient(client, actor) {
- this._parent = client;
- this.client = client instanceof DebuggerClient ? client : client.client;
- this._actor = actor;
- this._frameCache = [];
- this._scriptCache = {};
- this._pauseGrips = {};
- this._threadGrips = {};
- this.request = this.client.request;
-}
-
-ThreadClient.prototype = {
- _state: "paused",
- get state() {
- return this._state;
- },
- get paused() {
- return this._state === "paused";
- },
-
- _pauseOnExceptions: false,
- _ignoreCaughtExceptions: false,
- _pauseOnDOMEvents: null,
-
- _actor: null,
- get actor() {
- return this._actor;
- },
-
- get _transport() {
- return this.client._transport;
- },
-
- _assertPaused: function (command) {
- if (!this.paused) {
- throw Error(command + " command sent while not paused. Currently " + this._state);
- }
- },
-
- /**
- * Resume a paused thread. If the optional limit parameter is present, then
- * the thread will also pause when that limit is reached.
- *
- * @param [optional] object limit
- * An object with a type property set to the appropriate limit (next,
- * step, or finish) per the remote debugging protocol specification.
- * Use null to specify no limit.
- * @param function onResponse
- * Called with the response packet.
- */
- _doResume: DebuggerClient.requester({
- type: "resume",
- resumeLimit: arg(0)
- }, {
- before: function (packet) {
- this._assertPaused("resume");
-
- // Put the client in a tentative "resuming" state so we can prevent
- // further requests that should only be sent in the paused state.
- this._previousState = this._state;
- this._state = "resuming";
-
- if (this._pauseOnExceptions) {
- packet.pauseOnExceptions = this._pauseOnExceptions;
- }
- if (this._ignoreCaughtExceptions) {
- packet.ignoreCaughtExceptions = this._ignoreCaughtExceptions;
- }
- if (this._pauseOnDOMEvents) {
- packet.pauseOnDOMEvents = this._pauseOnDOMEvents;
- }
- return packet;
- },
- after: function (response) {
- if (response.error && this._state == "resuming") {
- // There was an error resuming, update the state to the new one
- // reported by the server, if given (only on wrongState), otherwise
- // reset back to the previous state.
- if (response.state) {
- this._state = ThreadStateTypes[response.state];
- } else {
- this._state = this._previousState;
- }
- }
- delete this._previousState;
- return response;
- },
- }),
-
- /**
- * Reconfigure the thread actor.
- *
- * @param object options
- * A dictionary object of the new options to use in the thread actor.
- * @param function onResponse
- * Called with the response packet.
- */
- reconfigure: DebuggerClient.requester({
- type: "reconfigure",
- options: arg(0)
- }),
-
- /**
- * Resume a paused thread.
- */
- resume: function (onResponse) {
- return this._doResume(null, onResponse);
- },
-
- /**
- * Resume then pause without stepping.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- resumeThenPause: function (onResponse) {
- return this._doResume({ type: "break" }, onResponse);
- },
-
- /**
- * Step over a function call.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- stepOver: function (onResponse) {
- return this._doResume({ type: "next" }, onResponse);
- },
-
- /**
- * Step into a function call.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- stepIn: function (onResponse) {
- return this._doResume({ type: "step" }, onResponse);
- },
-
- /**
- * Step out of a function call.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- stepOut: function (onResponse) {
- return this._doResume({ type: "finish" }, onResponse);
- },
-
- /**
- * Immediately interrupt a running thread.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- interrupt: function (onResponse) {
- return this._doInterrupt(null, onResponse);
- },
-
- /**
- * Pause execution right before the next JavaScript bytecode is executed.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- breakOnNext: function (onResponse) {
- return this._doInterrupt("onNext", onResponse);
- },
-
- /**
- * Interrupt a running thread.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- _doInterrupt: DebuggerClient.requester({
- type: "interrupt",
- when: arg(0)
- }),
-
- /**
- * Enable or disable pausing when an exception is thrown.
- *
- * @param boolean pauseOnExceptions
- * Enables pausing if true, disables otherwise.
- * @param boolean ignoreCaughtExceptions
- * Whether to ignore caught exceptions
- * @param function onResponse
- * Called with the response packet.
- */
- pauseOnExceptions: function (pauseOnExceptions,
- ignoreCaughtExceptions,
- onResponse = noop) {
- this._pauseOnExceptions = pauseOnExceptions;
- this._ignoreCaughtExceptions = ignoreCaughtExceptions;
-
- // Otherwise send the flag using a standard resume request.
- if (!this.paused) {
- return this.interrupt(response => {
- if (response.error) {
- // Can't continue if pausing failed.
- onResponse(response);
- return response;
- }
- return this.resume(onResponse);
- });
- }
-
- onResponse();
- return promise.resolve();
- },
-
- /**
- * Enable pausing when the specified DOM events are triggered. Disabling
- * pausing on an event can be realized by calling this method with the updated
- * array of events that doesn't contain it.
- *
- * @param array|string events
- * An array of strings, representing the DOM event types to pause on,
- * or "*" to pause on all DOM events. Pass an empty array to
- * completely disable pausing on DOM events.
- * @param function onResponse
- * Called with the response packet in a future turn of the event loop.
- */
- pauseOnDOMEvents: function (events, onResponse = noop) {
- this._pauseOnDOMEvents = events;
- // If the debuggee is paused, the value of the array will be communicated in
- // the next resumption. Otherwise we have to force a pause in order to send
- // the array.
- if (this.paused) {
- DevToolsUtils.executeSoon(() => onResponse({}));
- return {};
- }
- return this.interrupt(response => {
- // Can't continue if pausing failed.
- if (response.error) {
- onResponse(response);
- return response;
- }
- return this.resume(onResponse);
- });
- },
-
- /**
- * Send a clientEvaluate packet to the debuggee. Response
- * will be a resume packet.
- *
- * @param string frame
- * The actor ID of the frame where the evaluation should take place.
- * @param string expression
- * The expression that will be evaluated in the scope of the frame
- * above.
- * @param function onResponse
- * Called with the response packet.
- */
- eval: DebuggerClient.requester({
- type: "clientEvaluate",
- frame: arg(0),
- expression: arg(1)
- }, {
- before: function (packet) {
- this._assertPaused("eval");
- // Put the client in a tentative "resuming" state so we can prevent
- // further requests that should only be sent in the paused state.
- this._state = "resuming";
- return packet;
- },
- after: function (response) {
- if (response.error) {
- // There was an error resuming, back to paused state.
- this._state = "paused";
- }
- return response;
- },
- }),
-
- /**
- * Detach from the thread actor.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- detach: DebuggerClient.requester({
- type: "detach"
- }, {
- after: function (response) {
- this.client.unregisterClient(this);
- this._parent.thread = null;
- return response;
- },
- }),
-
- /**
- * Release multiple thread-lifetime object actors. If any pause-lifetime
- * actors are included in the request, a |notReleasable| error will return,
- * but all the thread-lifetime ones will have been released.
- *
- * @param array actors
- * An array with actor IDs to release.
- */
- releaseMany: DebuggerClient.requester({
- type: "releaseMany",
- actors: arg(0),
- }),
-
- /**
- * Promote multiple pause-lifetime object actors to thread-lifetime ones.
- *
- * @param array actors
- * An array with actor IDs to promote.
- */
- threadGrips: DebuggerClient.requester({
- type: "threadGrips",
- actors: arg(0)
- }),
-
- /**
- * Return the event listeners defined on the page.
- *
- * @param onResponse Function
- * Called with the thread's response.
- */
- eventListeners: DebuggerClient.requester({
- type: "eventListeners"
- }),
-
- /**
- * Request the loaded sources for the current thread.
- *
- * @param onResponse Function
- * Called with the thread's response.
- */
- getSources: DebuggerClient.requester({
- type: "sources"
- }),
-
- /**
- * Clear the thread's source script cache. A scriptscleared event
- * will be sent.
- */
- _clearScripts: function () {
- if (Object.keys(this._scriptCache).length > 0) {
- this._scriptCache = {};
- this.emit("scriptscleared");
- }
- },
-
- /**
- * Request frames from the callstack for the current thread.
- *
- * @param start integer
- * The number of the youngest stack frame to return (the youngest
- * frame is 0).
- * @param count integer
- * The maximum number of frames to return, or null to return all
- * frames.
- * @param onResponse function
- * Called with the thread's response.
- */
- getFrames: DebuggerClient.requester({
- type: "frames",
- start: arg(0),
- count: arg(1)
- }),
-
- /**
- * An array of cached frames. Clients can observe the framesadded and
- * framescleared event to keep up to date on changes to this cache,
- * and can fill it using the fillFrames method.
- */
- get cachedFrames() {
- return this._frameCache;
- },
-
- /**
- * true if there are more stack frames available on the server.
- */
- get moreFrames() {
- return this.paused && (!this._frameCache || this._frameCache.length == 0
- || !this._frameCache[this._frameCache.length - 1].oldest);
- },
-
- /**
- * Request the frame environment.
- *
- * @param frameId string
- */
- getEnvironment: function (frameId) {
- return this.request({ to: frameId, type: "getEnvironment" });
- },
-
- /**
- * Ensure that at least total stack frames have been loaded in the
- * ThreadClient's stack frame cache. A framesadded event will be
- * sent when the stack frame cache is updated.
- *
- * @param total number
- * The minimum number of stack frames to be included.
- * @param callback function
- * Optional callback function called when frames have been loaded
- * @returns true if a framesadded notification should be expected.
- */
- fillFrames: function (total, callback = noop) {
- this._assertPaused("fillFrames");
- if (this._frameCache.length >= total) {
- return false;
- }
-
- let numFrames = this._frameCache.length;
-
- this.getFrames(numFrames, total - numFrames, (response) => {
- if (response.error) {
- callback(response);
- return;
- }
-
- let threadGrips = DevToolsUtils.values(this._threadGrips);
-
- for (let i in response.frames) {
- let frame = response.frames[i];
- if (!frame.where.source) {
- // Older servers use urls instead, so we need to resolve
- // them to source actors
- for (let grip of threadGrips) {
- if (grip instanceof SourceClient && grip.url === frame.url) {
- frame.where.source = grip._form;
- }
- }
- }
-
- this._frameCache[frame.depth] = frame;
- }
-
- // If we got as many frames as we asked for, there might be more
- // frames available.
- this.emit("framesadded");
-
- callback(response);
- });
-
- return true;
- },
-
- /**
- * Clear the thread's stack frame cache. A framescleared event
- * will be sent.
- */
- _clearFrames: function () {
- if (this._frameCache.length > 0) {
- this._frameCache = [];
- this.emit("framescleared");
- }
- },
-
- /**
- * Return a ObjectClient object for the given object grip.
- *
- * @param grip object
- * A pause-lifetime object grip returned by the protocol.
- */
- pauseGrip: function (grip) {
- if (grip.actor in this._pauseGrips) {
- return this._pauseGrips[grip.actor];
- }
-
- let client = new ObjectClient(this.client, grip);
- this._pauseGrips[grip.actor] = client;
- return client;
- },
-
- /**
- * Get or create a long string client, checking the grip client cache if it
- * already exists.
- *
- * @param grip Object
- * The long string grip returned by the protocol.
- * @param gripCacheName String
- * The property name of the grip client cache to check for existing
- * clients in.
- */
- _longString: function (grip, gripCacheName) {
- if (grip.actor in this[gripCacheName]) {
- return this[gripCacheName][grip.actor];
- }
-
- let client = new LongStringClient(this.client, grip);
- this[gripCacheName][grip.actor] = client;
- return client;
- },
-
- /**
- * Return an instance of LongStringClient for the given long string grip that
- * is scoped to the current pause.
- *
- * @param grip Object
- * The long string grip returned by the protocol.
- */
- pauseLongString: function (grip) {
- return this._longString(grip, "_pauseGrips");
- },
-
- /**
- * Return an instance of LongStringClient for the given long string grip that
- * is scoped to the thread lifetime.
- *
- * @param grip Object
- * The long string grip returned by the protocol.
- */
- threadLongString: function (grip) {
- return this._longString(grip, "_threadGrips");
- },
-
- /**
- * Get or create an ArrayBuffer client, checking the grip client cache if it
- * already exists.
- *
- * @param grip Object
- * The ArrayBuffer grip returned by the protocol.
- * @param gripCacheName String
- * The property name of the grip client cache to check for existing
- * clients in.
- */
- _arrayBuffer: function (grip, gripCacheName) {
- if (grip.actor in this[gripCacheName]) {
- return this[gripCacheName][grip.actor];
- }
-
- let client = new ArrayBufferClient(this.client, grip);
- this[gripCacheName][grip.actor] = client;
- return client;
- },
-
- /**
- * Return an instance of ArrayBufferClient for the given ArrayBuffer grip that
- * is scoped to the thread lifetime.
- *
- * @param grip Object
- * The ArrayBuffer grip returned by the protocol.
- */
- threadArrayBuffer: function (grip) {
- return this._arrayBuffer(grip, "_threadGrips");
- },
-
- /**
- * Clear and invalidate all the grip clients from the given cache.
- *
- * @param gripCacheName
- * The property name of the grip cache we want to clear.
- */
- _clearObjectClients: function (gripCacheName) {
- for (let id in this[gripCacheName]) {
- this[gripCacheName][id].valid = false;
- }
- this[gripCacheName] = {};
- },
-
- /**
- * Invalidate pause-lifetime grip clients and clear the list of current grip
- * clients.
- */
- _clearPauseGrips: function () {
- this._clearObjectClients("_pauseGrips");
- },
-
- /**
- * Invalidate thread-lifetime grip clients and clear the list of current grip
- * clients.
- */
- _clearThreadGrips: function () {
- this._clearObjectClients("_threadGrips");
- },
-
- /**
- * Handle thread state change by doing necessary cleanup and notifying all
- * registered listeners.
- */
- _onThreadState: function (packet) {
- this._state = ThreadStateTypes[packet.type];
- // The debugger UI may not be initialized yet so we want to keep
- // the packet around so it knows what to pause state to display
- // when it's initialized
- this._lastPausePacket = packet.type === "resumed" ? null : packet;
- this._clearFrames();
- this._clearPauseGrips();
- packet.type === ThreadStateTypes.detached && this._clearThreadGrips();
- this.client._eventsEnabled && this.emit(packet.type, packet);
- },
-
- getLastPausePacket: function () {
- return this._lastPausePacket;
- },
-
- /**
- * Return an EnvironmentClient instance for the given environment actor form.
- */
- environment: function (form) {
- return new EnvironmentClient(this.client, form);
- },
-
- /**
- * Return an instance of SourceClient for the given source actor form.
- */
- source: function (form) {
- if (form.actor in this._threadGrips) {
- return this._threadGrips[form.actor];
- }
-
- this._threadGrips[form.actor] = new SourceClient(this, form);
- return this._threadGrips[form.actor];
- },
-
- /**
- * Request the prototype and own properties of mutlipleObjects.
- *
- * @param onResponse function
- * Called with the request's response.
- * @param actors [string]
- * List of actor ID of the queried objects.
- */
- getPrototypesAndProperties: DebuggerClient.requester({
- type: "prototypesAndProperties",
- actors: arg(0)
- }),
-
- events: ["newSource"]
-};
-
-eventSource(ThreadClient.prototype);
-
-/**
- * Creates a tracing profiler client for the remote debugging protocol
- * server. This client is a front to the trace actor created on the
- * server side, hiding the protocol details in a traditional
- * JavaScript API.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param actor string
- * The actor ID for this thread.
- */
-function TraceClient(client, actor) {
- this._client = client;
- this._actor = actor;
- this._activeTraces = new Set();
- this._waitingPackets = new Map();
- this._expectedPacket = 0;
- this.request = this._client.request;
- this.events = [];
-}
-
-TraceClient.prototype = {
- get actor() {
- return this._actor;
- },
- get tracing() {
- return this._activeTraces.size > 0;
- },
-
- get _transport() {
- return this._client._transport;
- },
-
- /**
- * Detach from the trace actor.
- */
- detach: DebuggerClient.requester({
- type: "detach"
- }, {
- after: function (response) {
- this._client.unregisterClient(this);
- return response;
- },
- }),
-
- /**
- * Start a new trace.
- *
- * @param trace [string]
- * An array of trace types to be recorded by the new trace.
- *
- * @param name string
- * The name of the new trace.
- *
- * @param onResponse function
- * Called with the request's response.
- */
- startTrace: DebuggerClient.requester({
- type: "startTrace",
- name: arg(1),
- trace: arg(0)
- }, {
- after: function (response) {
- if (response.error) {
- return response;
- }
-
- if (!this.tracing) {
- this._waitingPackets.clear();
- this._expectedPacket = 0;
- }
- this._activeTraces.add(response.name);
-
- return response;
- },
- }),
-
- /**
- * End a trace. If a name is provided, stop the named
- * trace. Otherwise, stop the most recently started trace.
- *
- * @param name string
- * The name of the trace to stop.
- *
- * @param onResponse function
- * Called with the request's response.
- */
- stopTrace: DebuggerClient.requester({
- type: "stopTrace",
- name: arg(0)
- }, {
- after: function (response) {
- if (response.error) {
- return response;
- }
-
- this._activeTraces.delete(response.name);
-
- return response;
- },
- })
-};
-
-/**
- * Grip clients are used to retrieve information about the relevant object.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param grip object
- * A pause-lifetime object grip returned by the protocol.
- */
-function ObjectClient(client, grip) {
- this._grip = grip;
- this._client = client;
- this.request = this._client.request;
-}
-exports.ObjectClient = ObjectClient;
-
-ObjectClient.prototype = {
- get actor() {
- return this._grip.actor;
- },
- get _transport() {
- return this._client._transport;
- },
-
- valid: true,
-
- get isFrozen() {
- return this._grip.frozen;
- },
- get isSealed() {
- return this._grip.sealed;
- },
- get isExtensible() {
- return this._grip.extensible;
- },
-
- getDefinitionSite: DebuggerClient.requester({
- type: "definitionSite"
- }, {
- before: function (packet) {
- if (this._grip.class != "Function") {
- throw new Error("getDefinitionSite is only valid for function grips.");
- }
- return packet;
- }
- }),
-
- /**
- * Request the names of a function's formal parameters.
- *
- * @param onResponse function
- * Called with an object of the form:
- * { parameterNames:[<parameterName>, ...] }
- * where each <parameterName> is the name of a parameter.
- */
- getParameterNames: DebuggerClient.requester({
- type: "parameterNames"
- }, {
- before: function (packet) {
- if (this._grip.class !== "Function") {
- throw new Error("getParameterNames is only valid for function grips.");
- }
- return packet;
- },
- }),
-
- /**
- * Request the names of the properties defined on the object and not its
- * prototype.
- *
- * @param onResponse function Called with the request's response.
- */
- getOwnPropertyNames: DebuggerClient.requester({
- type: "ownPropertyNames"
- }),
-
- /**
- * Request the prototype and own properties of the object.
- *
- * @param onResponse function Called with the request's response.
- */
- getPrototypeAndProperties: DebuggerClient.requester({
- type: "prototypeAndProperties"
- }),
-
- /**
- * Request a PropertyIteratorClient instance to ease listing
- * properties for this object.
- *
- * @param options Object
- * A dictionary object with various boolean attributes:
- * - ignoreIndexedProperties Boolean
- * If true, filters out Array items.
- * e.g. properties names between `0` and `object.length`.
- * - ignoreNonIndexedProperties Boolean
- * If true, filters out items that aren't array items
- * e.g. properties names that are not a number between `0`
- * and `object.length`.
- * - sort Boolean
- * If true, the iterator will sort the properties by name
- * before dispatching them.
- * @param onResponse function Called with the client instance.
- */
- enumProperties: DebuggerClient.requester({
- type: "enumProperties",
- options: arg(0)
- }, {
- after: function (response) {
- if (response.iterator) {
- return { iterator: new PropertyIteratorClient(this._client, response.iterator) };
- }
- return response;
- },
- }),
-
- /**
- * Request a PropertyIteratorClient instance to enumerate entries in a
- * Map/Set-like object.
- *
- * @param onResponse function Called with the request's response.
- */
- enumEntries: DebuggerClient.requester({
- type: "enumEntries"
- }, {
- before: function (packet) {
- if (!["Map", "WeakMap", "Set", "WeakSet"].includes(this._grip.class)) {
- throw new Error("enumEntries is only valid for Map/Set-like grips.");
- }
- return packet;
- },
- after: function (response) {
- if (response.iterator) {
- return {
- iterator: new PropertyIteratorClient(this._client, response.iterator)
- };
- }
- return response;
- }
- }),
-
- /**
- * Request a SymbolIteratorClient instance to enumerate symbols in an object.
- *
- * @param onResponse function Called with the request's response.
- */
- enumSymbols: DebuggerClient.requester({
- type: "enumSymbols"
- }, {
- before: function (packet) {
- if (this._grip.type !== "object") {
- throw new Error("enumSymbols is only valid for objects grips.");
- }
- return packet;
- },
- after: function (response) {
- if (response.iterator) {
- return {
- iterator: new SymbolIteratorClient(this._client, response.iterator)
- };
- }
- return response;
- }
- }),
-
- /**
- * Request the property descriptor of the object's specified property.
- *
- * @param name string The name of the requested property.
- * @param onResponse function Called with the request's response.
- */
- getProperty: DebuggerClient.requester({
- type: "property",
- name: arg(0)
- }),
-
- /**
- * Request the prototype of the object.
- *
- * @param onResponse function Called with the request's response.
- */
- getPrototype: DebuggerClient.requester({
- type: "prototype"
- }),
-
- /**
- * Request the display string of the object.
- *
- * @param onResponse function Called with the request's response.
- */
- getDisplayString: DebuggerClient.requester({
- type: "displayString"
- }),
-
- /**
- * Request the scope of the object.
- *
- * @param onResponse function Called with the request's response.
- */
- getScope: DebuggerClient.requester({
- type: "scope"
- }, {
- before: function (packet) {
- if (this._grip.class !== "Function") {
- throw new Error("scope is only valid for function grips.");
- }
- return packet;
- },
- }),
-
- /**
- * Request the promises directly depending on the current promise.
- */
- getDependentPromises: DebuggerClient.requester({
- type: "dependentPromises"
- }, {
- before: function (packet) {
- if (this._grip.class !== "Promise") {
- throw new Error("getDependentPromises is only valid for promise " +
- "grips.");
- }
- return packet;
- }
- }),
-
- /**
- * Request the stack to the promise's allocation point.
- */
- getPromiseAllocationStack: DebuggerClient.requester({
- type: "allocationStack"
- }, {
- before: function (packet) {
- if (this._grip.class !== "Promise") {
- throw new Error("getAllocationStack is only valid for promise grips.");
- }
- return packet;
- }
- }),
-
- /**
- * Request the stack to the promise's fulfillment point.
- */
- getPromiseFulfillmentStack: DebuggerClient.requester({
- type: "fulfillmentStack"
- }, {
- before: function (packet) {
- if (this._grip.class !== "Promise") {
- throw new Error("getPromiseFulfillmentStack is only valid for " +
- "promise grips.");
- }
- return packet;
- }
- }),
-
- /**
- * Request the stack to the promise's rejection point.
- */
- getPromiseRejectionStack: DebuggerClient.requester({
- type: "rejectionStack"
- }, {
- before: function (packet) {
- if (this._grip.class !== "Promise") {
- throw new Error("getPromiseRejectionStack is only valid for " +
- "promise grips.");
- }
- return packet;
- }
- })
-};
-
-/**
- * A PropertyIteratorClient provides a way to access to property names and
- * values of an object efficiently, slice by slice.
- * Note that the properties can be sorted in the backend,
- * this is controled while creating the PropertyIteratorClient
- * from ObjectClient.enumProperties.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param grip Object
- * A PropertyIteratorActor grip returned by the protocol via
- * TabActor.enumProperties request.
- */
-function PropertyIteratorClient(client, grip) {
- this._grip = grip;
- this._client = client;
- this.request = this._client.request;
-}
-
-PropertyIteratorClient.prototype = {
- get actor() {
- return this._grip.actor;
- },
-
- /**
- * Get the total number of properties available in the iterator.
- */
- get count() {
- return this._grip.count;
- },
-
- /**
- * Get one or more property names that correspond to the positions in the
- * indexes parameter.
- *
- * @param indexes Array
- * An array of property indexes.
- * @param callback Function
- * The function called when we receive the property names.
- */
- names: DebuggerClient.requester({
- type: "names",
- indexes: arg(0)
- }, {}),
-
- /**
- * Get a set of following property value(s).
- *
- * @param start Number
- * The index of the first property to fetch.
- * @param count Number
- * The number of properties to fetch.
- * @param callback Function
- * The function called when we receive the property values.
- */
- slice: DebuggerClient.requester({
- type: "slice",
- start: arg(0),
- count: arg(1)
- }, {}),
-
- /**
- * Get all the property values.
- *
- * @param callback Function
- * The function called when we receive the property values.
- */
- all: DebuggerClient.requester({
- type: "all"
- }, {}),
-};
-
-/**
- * A SymbolIteratorClient provides a way to access to symbols
- * of an object efficiently, slice by slice.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param grip Object
- * A SymbolIteratorActor grip returned by the protocol via
- * TabActor.enumSymbols request.
- */
-function SymbolIteratorClient(client, grip) {
- this._grip = grip;
- this._client = client;
- this.request = this._client.request;
-}
-
-SymbolIteratorClient.prototype = {
- get actor() {
- return this._grip.actor;
- },
-
- /**
- * Get the total number of symbols available in the iterator.
- */
- get count() {
- return this._grip.count;
- },
-
- /**
- * Get a set of following symbols.
- *
- * @param start Number
- * The index of the first symbol to fetch.
- * @param count Number
- * The number of symbols to fetch.
- * @param callback Function
- * The function called when we receive the symbols.
- */
- slice: DebuggerClient.requester({
- type: "slice",
- start: arg(0),
- count: arg(1)
- }, {}),
-
- /**
- * Get all the symbols.
- *
- * @param callback Function
- * The function called when we receive the symbols.
- */
- all: DebuggerClient.requester({
- type: "all"
- }, {}),
-};
-
-/**
- * A ArrayBufferClient provides a way to access ArrayBuffer from the
- * debugger server.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param grip Object
- * A pause-lifetime ArrayBuffer grip returned by the protocol.
- */
-function ArrayBufferClient(client, grip) {
- this._grip = grip;
- this._client = client;
- this.request = this._client.request;
-}
-ArrayBufferClient.prototype = {
- get actor() {
- return this._grip.actor;
- },
- get length() {
- return this._grip.length;
- },
- get _transport() {
- return this._client._transport;
- },
-
- valid: true,
-
- slice: DebuggerClient.requester({
- type: "slice",
- start: arg(0),
- count: arg(1)
- }),
-};
-
-/**
- * A LongStringClient provides a way to access "very long" strings from the
- * debugger server.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param grip Object
- * A pause-lifetime long string grip returned by the protocol.
- */
-function LongStringClient(client, grip) {
- this._grip = grip;
- this._client = client;
- this.request = this._client.request;
-}
-exports.LongStringClient = LongStringClient;
-
-LongStringClient.prototype = {
- get actor() {
- return this._grip.actor;
- },
- get length() {
- return this._grip.length;
- },
- get initial() {
- return this._grip.initial;
- },
- get _transport() {
- return this._client._transport;
- },
-
- valid: true,
-
- /**
- * Get the substring of this LongString from start to end.
- *
- * @param start Number
- * The starting index.
- * @param end Number
- * The ending index.
- * @param callback Function
- * The function called when we receive the substring.
- */
- substring: DebuggerClient.requester({
- type: "substring",
- start: arg(0),
- end: arg(1)
- }),
-};
-
-/**
* A SourceClient provides a way to access the source text of a script.
*
* @param client ThreadClient
* The thread client parent.
* @param form Object
* The form sent across the remote debugging protocol.
*/
function SourceClient(client, form) {
@@ -3255,172 +274,9 @@ SourceClient.prototype = {
? () => this._activeThread.resume()
: noop;
return doSetBreakpoint(cleanUp);
});
}
};
-/**
- * Breakpoint clients are used to remove breakpoints that are no longer used.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param sourceClient SourceClient
- * The source where this breakpoint exists
- * @param actor string
- * The actor ID for this breakpoint.
- * @param location object
- * The location of the breakpoint. This is an object with two properties:
- * url and line.
- * @param condition string
- * The conditional expression of the breakpoint
- */
-function BreakpointClient(client, sourceClient, actor, location, condition) {
- this._client = client;
- this._actor = actor;
- this.location = location;
- this.location.actor = sourceClient.actor;
- this.location.url = sourceClient.url;
- this.source = sourceClient;
- this.request = this._client.request;
-
- // The condition property should only exist if it's a truthy value
- if (condition) {
- this.condition = condition;
- }
-}
-
-BreakpointClient.prototype = {
-
- _actor: null,
- get actor() {
- return this._actor;
- },
- get _transport() {
- return this._client._transport;
- },
-
- /**
- * Remove the breakpoint from the server.
- */
- remove: DebuggerClient.requester({
- type: "delete"
- }),
-
- /**
- * Determines if this breakpoint has a condition
- */
- hasCondition: function () {
- let root = this._client.mainRoot;
- // XXX bug 990137: We will remove support for client-side handling of
- // conditional breakpoints
- if (root.traits.conditionalBreakpoints) {
- return "condition" in this;
- }
- return "conditionalExpression" in this;
- },
-
- /**
- * Get the condition of this breakpoint. Currently we have to
- * support locally emulated conditional breakpoints until the
- * debugger servers are updated (see bug 990137). We used a
- * different property when moving it server-side to ensure that we
- * are testing the right code.
- */
- getCondition: function () {
- let root = this._client.mainRoot;
- if (root.traits.conditionalBreakpoints) {
- return this.condition;
- }
- return this.conditionalExpression;
- },
-
- /**
- * Set the condition of this breakpoint
- */
- setCondition: function (gThreadClient, condition) {
- let root = this._client.mainRoot;
- let deferred = promise.defer();
-
- if (root.traits.conditionalBreakpoints) {
- let info = {
- line: this.location.line,
- column: this.location.column,
- condition: condition
- };
-
- // Remove the current breakpoint and add a new one with the
- // condition.
- this.remove(response => {
- if (response && response.error) {
- deferred.reject(response);
- return;
- }
-
- this.source.setBreakpoint(info, (resp, newBreakpoint) => {
- if (resp && resp.error) {
- deferred.reject(resp);
- } else {
- deferred.resolve(newBreakpoint);
- }
- });
- });
- } else {
- // The property shouldn't even exist if the condition is blank
- if (condition === "") {
- delete this.conditionalExpression;
- } else {
- this.conditionalExpression = condition;
- }
- deferred.resolve(this);
- }
-
- return deferred.promise;
- }
-};
-
-eventSource(BreakpointClient.prototype);
-
-/**
- * Environment clients are used to manipulate the lexical environment actors.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param form Object
- * The form sent across the remote debugging protocol.
- */
-function EnvironmentClient(client, form) {
- this._client = client;
- this._form = form;
- this.request = this._client.request;
-}
-exports.EnvironmentClient = EnvironmentClient;
-
-EnvironmentClient.prototype = {
-
- get actor() {
- return this._form.actor;
- },
- get _transport() {
- return this._client._transport;
- },
-
- /**
- * Fetches the bindings introduced by this lexical environment.
- */
- getBindings: DebuggerClient.requester({
- type: "bindings"
- }),
-
- /**
- * Changes the value of the identifier whose name is name (a string) to that
- * represented by value (a grip).
- */
- assign: DebuggerClient.requester({
- type: "assign",
- name: arg(0),
- value: arg(1)
- })
-};
-
-eventSource(EnvironmentClient.prototype);
+module.exports = SourceClient;
copy from devtools/shared/client/main.js
copy to devtools/shared/client/symbol-iterator-client.js
--- a/devtools/shared/client/main.js
+++ b/devtools/shared/client/symbol-iterator-client.js
@@ -1,2861 +1,15 @@
-/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
-/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
-const { Ci, Cu } = require("chrome");
-const DevToolsUtils = require("devtools/shared/DevToolsUtils");
-const { getStack, callFunctionWithAsyncStack } = require("devtools/shared/platform/stack");
-
-const promise = Cu.import("resource://devtools/shared/deprecated-sync-thenables.js", {}).Promise;
-
-loader.lazyRequireGetter(this, "EventEmitter", "devtools/shared/event-emitter");
-loader.lazyRequireGetter(this, "WebConsoleClient", "devtools/shared/webconsole/client", true);
-loader.lazyRequireGetter(this, "DebuggerSocket", "devtools/shared/security/socket", true);
-loader.lazyRequireGetter(this, "Authentication", "devtools/shared/security/auth");
-
-const noop = () => {};
-
-/**
- * TODO: Get rid of this API in favor of EventTarget (bug 1042642)
- *
- * Add simple event notification to a prototype object. Any object that has
- * some use for event notifications or the observer pattern in general can be
- * augmented with the necessary facilities by passing its prototype to this
- * function.
- *
- * @param proto object
- * The prototype object that will be modified.
- */
-function eventSource(proto) {
- /**
- * Add a listener to the event source for a given event.
- *
- * @param name string
- * The event to listen for.
- * @param listener function
- * Called when the event is fired. If the same listener
- * is added more than once, it will be called once per
- * addListener call.
- */
- proto.addListener = function (name, listener) {
- if (typeof listener != "function") {
- throw TypeError("Listeners must be functions.");
- }
-
- if (!this._listeners) {
- this._listeners = {};
- }
-
- this._getListeners(name).push(listener);
- };
-
- /**
- * Add a listener to the event source for a given event. The
- * listener will be removed after it is called for the first time.
- *
- * @param name string
- * The event to listen for.
- * @param listener function
- * Called when the event is fired.
- */
- proto.addOneTimeListener = function (name, listener) {
- let l = (...args) => {
- this.removeListener(name, l);
- listener.apply(null, args);
- };
- this.addListener(name, l);
- };
-
- /**
- * Remove a listener from the event source previously added with
- * addListener().
- *
- * @param name string
- * The event name used during addListener to add the listener.
- * @param listener function
- * The callback to remove. If addListener was called multiple
- * times, all instances will be removed.
- */
- proto.removeListener = function (name, listener) {
- if (!this._listeners || (listener && !this._listeners[name])) {
- return;
- }
-
- if (!listener) {
- this._listeners[name] = [];
- } else {
- this._listeners[name] =
- this._listeners[name].filter(l => l != listener);
- }
- };
-
- /**
- * Returns the listeners for the specified event name. If none are defined it
- * initializes an empty list and returns that.
- *
- * @param name string
- * The event name.
- */
- proto._getListeners = function (name) {
- if (name in this._listeners) {
- return this._listeners[name];
- }
- this._listeners[name] = [];
- return this._listeners[name];
- };
-
- /**
- * Notify listeners of an event.
- *
- * @param name string
- * The event to fire.
- * @param arguments
- * All arguments will be passed along to the listeners,
- * including the name argument.
- */
- proto.emit = function () {
- if (!this._listeners) {
- return;
- }
-
- let name = arguments[0];
- let listeners = this._getListeners(name).slice(0);
-
- for (let listener of listeners) {
- try {
- listener.apply(null, arguments);
- } catch (e) {
- // Prevent a bad listener from interfering with the others.
- DevToolsUtils.reportException("notify event '" + name + "'", e);
- }
- }
- };
-}
-
-/**
- * Set of protocol messages that affect thread state, and the
- * state the actor is in after each message.
- */
-const ThreadStateTypes = {
- "paused": "paused",
- "resumed": "attached",
- "detached": "detached",
- "running": "attached"
-};
-
-/**
- * Set of protocol messages that are sent by the server without a prior request
- * by the client.
- */
-const UnsolicitedNotifications = {
- "consoleAPICall": "consoleAPICall",
- "eventNotification": "eventNotification",
- "fileActivity": "fileActivity",
- "lastPrivateContextExited": "lastPrivateContextExited",
- "logMessage": "logMessage",
- "networkEvent": "networkEvent",
- "networkEventUpdate": "networkEventUpdate",
- "newGlobal": "newGlobal",
- "newScript": "newScript",
- "tabDetached": "tabDetached",
- "tabListChanged": "tabListChanged",
- "reflowActivity": "reflowActivity",
- "addonListChanged": "addonListChanged",
- "workerListChanged": "workerListChanged",
- "serviceWorkerRegistrationListChanged": "serviceWorkerRegistrationList",
- "tabNavigated": "tabNavigated",
- "frameUpdate": "frameUpdate",
- "pageError": "pageError",
- "documentLoad": "documentLoad",
- "enteredFrame": "enteredFrame",
- "exitedFrame": "exitedFrame",
- "appOpen": "appOpen",
- "appClose": "appClose",
- "appInstall": "appInstall",
- "appUninstall": "appUninstall",
- "evaluationResult": "evaluationResult",
- "newSource": "newSource",
- "updatedSource": "updatedSource",
- "inspectObject": "inspectObject"
-};
-
-/**
- * Set of pause types that are sent by the server and not as an immediate
- * response to a client request.
- */
-const UnsolicitedPauses = {
- "resumeLimit": "resumeLimit",
- "debuggerStatement": "debuggerStatement",
- "breakpoint": "breakpoint",
- "DOMEvent": "DOMEvent",
- "watchpoint": "watchpoint",
- "exception": "exception"
-};
-
-/**
- * Creates a client for the remote debugging protocol server. This client
- * provides the means to communicate with the server and exchange the messages
- * required by the protocol in a traditional JavaScript API.
- */
-const DebuggerClient = exports.DebuggerClient = function (transport) {
- this._transport = transport;
- this._transport.hooks = this;
-
- // Map actor ID to client instance for each actor type.
- this._clients = new Map();
-
- this._pendingRequests = new Map();
- this._activeRequests = new Map();
- this._eventsEnabled = true;
-
- this.traits = {};
-
- this.request = this.request.bind(this);
- this.localTransport = this._transport.onOutputStreamReady === undefined;
-
- /*
- * As the first thing on the connection, expect a greeting packet from
- * the connection's root actor.
- */
- this.mainRoot = null;
- this.expectReply("root", (packet) => {
- this.mainRoot = new RootClient(this, packet);
- this.emit("connected", packet.applicationType, packet.traits);
- });
-};
-
-/**
- * A declarative helper for defining methods that send requests to the server.
- *
- * @param packetSkeleton
- * The form of the packet to send. Can specify fields to be filled from
- * the parameters by using the |arg| function.
- * @param before
- * The function to call before sending the packet. Is passed the packet,
- * and the return value is used as the new packet. The |this| context is
- * the instance of the client object we are defining a method for.
- * @param after
- * The function to call after the response is received. It is passed the
- * response, and the return value is considered the new response that
- * will be passed to the callback. The |this| context is the instance of
- * the client object we are defining a method for.
- * @return Request
- * The `Request` object that is a Promise object and resolves once
- * we receive the response. (See request method for more details)
- */
-DebuggerClient.requester = function (packetSkeleton, config = {}) {
- let { before, after } = config;
- return DevToolsUtils.makeInfallible(function (...args) {
- let outgoingPacket = {
- to: packetSkeleton.to || this.actor
- };
-
- let maxPosition = -1;
- for (let k of Object.keys(packetSkeleton)) {
- if (packetSkeleton[k] instanceof DebuggerClient.Argument) {
- let { position } = packetSkeleton[k];
- outgoingPacket[k] = packetSkeleton[k].getArgument(args);
- maxPosition = Math.max(position, maxPosition);
- } else {
- outgoingPacket[k] = packetSkeleton[k];
- }
- }
-
- if (before) {
- outgoingPacket = before.call(this, outgoingPacket);
- }
-
- return this.request(outgoingPacket, DevToolsUtils.makeInfallible((response) => {
- if (after) {
- let { from } = response;
- response = after.call(this, response);
- if (!response.from) {
- response.from = from;
- }
- }
-
- // The callback is always the last parameter.
- let thisCallback = args[maxPosition + 1];
- if (thisCallback) {
- thisCallback(response);
- }
- return response;
- }, "DebuggerClient.requester request callback"));
- }, "DebuggerClient.requester");
-};
-
-function arg(pos) {
- return new DebuggerClient.Argument(pos);
-}
-
-DebuggerClient.Argument = function (position) {
- this.position = position;
-};
-
-DebuggerClient.Argument.prototype.getArgument = function (params) {
- if (!(this.position in params)) {
- throw new Error("Bad index into params: " + this.position);
- }
- return params[this.position];
-};
-
-// Expose these to save callers the trouble of importing DebuggerSocket
-DebuggerClient.socketConnect = function (options) {
- // Defined here instead of just copying the function to allow lazy-load
- return DebuggerSocket.connect(options);
-};
-DevToolsUtils.defineLazyGetter(DebuggerClient, "Authenticators", () => {
- return Authentication.Authenticators;
-});
-DevToolsUtils.defineLazyGetter(DebuggerClient, "AuthenticationResult", () => {
- return Authentication.AuthenticationResult;
-});
-
-DebuggerClient.prototype = {
- /**
- * Connect to the server and start exchanging protocol messages.
- *
- * @param onConnected function
- * If specified, will be called when the greeting packet is
- * received from the debugging server.
- *
- * @return Promise
- * Resolves once connected with an array whose first element
- * is the application type, by default "browser", and the second
- * element is the traits object (help figure out the features
- * and behaviors of the server we connect to. See RootActor).
- */
- connect: function (onConnected) {
- let deferred = promise.defer();
- this.emit("connect");
-
- // Also emit the event on the |DebuggerClient| object (not on the instance),
- // so it's possible to track all instances.
- EventEmitter.emit(DebuggerClient, "connect", this);
-
- this.addOneTimeListener("connected", (name, applicationType, traits) => {
- this.traits = traits;
- if (onConnected) {
- onConnected(applicationType, traits);
- }
- deferred.resolve([applicationType, traits]);
- });
-
- this._transport.ready();
- return deferred.promise;
- },
-
- /**
- * Shut down communication with the debugging server.
- *
- * @param onClosed function
- * If specified, will be called when the debugging connection
- * has been closed. This parameter is deprecated - please use
- * the returned Promise.
- * @return Promise
- * Resolves after the underlying transport is closed.
- */
- close: function (onClosed) {
- let deferred = promise.defer();
- if (onClosed) {
- deferred.promise.then(onClosed);
- }
-
- // Disable detach event notifications, because event handlers will be in a
- // cleared scope by the time they run.
- this._eventsEnabled = false;
-
- let cleanup = () => {
- this._transport.close();
- this._transport = null;
- };
-
- // If the connection is already closed,
- // there is no need to detach client
- // as we won't be able to send any message.
- if (this._closed) {
- cleanup();
- deferred.resolve();
- return deferred.promise;
- }
-
- this.addOneTimeListener("closed", deferred.resolve);
-
- // Call each client's `detach` method by calling
- // lastly registered ones first to give a chance
- // to detach child clients first.
- let clients = [...this._clients.values()];
- this._clients.clear();
- const detachClients = () => {
- let client = clients.pop();
- if (!client) {
- // All clients detached.
- cleanup();
- return;
- }
- if (client.detach) {
- client.detach(detachClients);
- return;
- }
- detachClients();
- };
- detachClients();
-
- return deferred.promise;
- },
-
- /*
- * This function exists only to preserve DebuggerClient's interface;
- * new code should say 'client.mainRoot.listTabs()'.
- */
- listTabs: function (onResponse) {
- return this.mainRoot.listTabs(onResponse);
- },
-
- /*
- * This function exists only to preserve DebuggerClient's interface;
- * new code should say 'client.mainRoot.listAddons()'.
- */
- listAddons: function (onResponse) {
- return this.mainRoot.listAddons(onResponse);
- },
-
- getTab: function (filter) {
- return this.mainRoot.getTab(filter);
- },
-
- /**
- * Attach to a tab actor.
- *
- * @param string tabActor
- * The actor ID for the tab to attach.
- * @param function onResponse
- * Called with the response packet and a TabClient
- * (which will be undefined on error).
- */
- attachTab: function (tabActor, onResponse = noop) {
- if (this._clients.has(tabActor)) {
- let cachedTab = this._clients.get(tabActor);
- let cachedResponse = {
- cacheDisabled: cachedTab.cacheDisabled,
- javascriptEnabled: cachedTab.javascriptEnabled,
- traits: cachedTab.traits,
- };
- DevToolsUtils.executeSoon(() => onResponse(cachedResponse, cachedTab));
- return promise.resolve([cachedResponse, cachedTab]);
- }
-
- let packet = {
- to: tabActor,
- type: "attach"
- };
- return this.request(packet).then(response => {
- let tabClient;
- if (!response.error) {
- tabClient = new TabClient(this, response);
- this.registerClient(tabClient);
- }
- onResponse(response, tabClient);
- return [response, tabClient];
- });
- },
-
- attachWorker: function (workerActor, onResponse = noop) {
- let workerClient = this._clients.get(workerActor);
- if (workerClient !== undefined) {
- let response = {
- from: workerClient.actor,
- type: "attached",
- url: workerClient.url
- };
- DevToolsUtils.executeSoon(() => onResponse(response, workerClient));
- return promise.resolve([response, workerClient]);
- }
-
- return this.request({ to: workerActor, type: "attach" }).then(response => {
- if (response.error) {
- onResponse(response, null);
- return [response, null];
- }
-
- workerClient = new WorkerClient(this, response);
- this.registerClient(workerClient);
- onResponse(response, workerClient);
- return [response, workerClient];
- });
- },
-
- /**
- * Attach to an addon actor.
- *
- * @param string addonActor
- * The actor ID for the addon to attach.
- * @param function onResponse
- * Called with the response packet and a AddonClient
- * (which will be undefined on error).
- */
- attachAddon: function (addonActor, onResponse = noop) {
- let packet = {
- to: addonActor,
- type: "attach"
- };
- return this.request(packet).then(response => {
- let addonClient;
- if (!response.error) {
- addonClient = new AddonClient(this, addonActor);
- this.registerClient(addonClient);
- this.activeAddon = addonClient;
- }
- onResponse(response, addonClient);
- return [response, addonClient];
- });
- },
-
- /**
- * Attach to a Web Console actor.
- *
- * @param string consoleActor
- * The ID for the console actor to attach to.
- * @param array listeners
- * The console listeners you want to start.
- * @param function onResponse
- * Called with the response packet and a WebConsoleClient
- * instance (which will be undefined on error).
- */
- attachConsole:
- function (consoleActor, listeners, onResponse = noop) {
- let packet = {
- to: consoleActor,
- type: "startListeners",
- listeners: listeners,
- };
-
- return this.request(packet).then(response => {
- let consoleClient;
- if (!response.error) {
- if (this._clients.has(consoleActor)) {
- consoleClient = this._clients.get(consoleActor);
- } else {
- consoleClient = new WebConsoleClient(this, response);
- this.registerClient(consoleClient);
- }
- }
- onResponse(response, consoleClient);
- return [response, consoleClient];
- });
- },
-
- /**
- * Attach to a global-scoped thread actor for chrome debugging.
- *
- * @param string threadActor
- * The actor ID for the thread to attach.
- * @param function onResponse
- * Called with the response packet and a ThreadClient
- * (which will be undefined on error).
- * @param object options
- * Configuration options.
- * - useSourceMaps: whether to use source maps or not.
- */
- attachThread: function (threadActor, onResponse = noop, options = {}) {
- if (this._clients.has(threadActor)) {
- let client = this._clients.get(threadActor);
- DevToolsUtils.executeSoon(() => onResponse({}, client));
- return promise.resolve([{}, client]);
- }
-
- let packet = {
- to: threadActor,
- type: "attach",
- options,
- };
- return this.request(packet).then(response => {
- let threadClient;
- if (!response.error) {
- threadClient = new ThreadClient(this, threadActor);
- this.registerClient(threadClient);
- }
- onResponse(response, threadClient);
- return [response, threadClient];
- });
- },
-
- /**
- * Attach to a trace actor.
- *
- * @param string traceActor
- * The actor ID for the tracer to attach.
- * @param function onResponse
- * Called with the response packet and a TraceClient
- * (which will be undefined on error).
- */
- attachTracer: function (traceActor, onResponse = noop) {
- if (this._clients.has(traceActor)) {
- let client = this._clients.get(traceActor);
- DevToolsUtils.executeSoon(() => onResponse({}, client));
- return promise.resolve([{}, client]);
- }
-
- let packet = {
- to: traceActor,
- type: "attach"
- };
- return this.request(packet).then(response => {
- let traceClient;
- if (!response.error) {
- traceClient = new TraceClient(this, traceActor);
- this.registerClient(traceClient);
- }
- onResponse(response, traceClient);
- return [response, traceClient];
- });
- },
-
- /**
- * Fetch the ChromeActor for the main process or ChildProcessActor for a
- * a given child process ID.
- *
- * @param number id
- * The ID for the process to attach (returned by `listProcesses`).
- * Connected to the main process if omitted, or is 0.
- */
- getProcess: function (id) {
- let packet = {
- to: "root",
- type: "getProcess"
- };
- if (typeof (id) == "number") {
- packet.id = id;
- }
- return this.request(packet);
- },
-
- /**
- * Release an object actor.
- *
- * @param string actor
- * The actor ID to send the request to.
- * @param onResponse function
- * If specified, will be called with the response packet when
- * debugging server responds.
- */
- release: DebuggerClient.requester({
- to: arg(0),
- type: "release"
- }),
-
- /**
- * Send a request to the debugging server.
- *
- * @param packet object
- * A JSON packet to send to the debugging server.
- * @param onResponse function
- * If specified, will be called with the JSON response packet when
- * debugging server responds.
- * @return Request
- * This object emits a number of events to allow you to respond to
- * different parts of the request lifecycle.
- * It is also a Promise object, with a `then` method, that is resolved
- * whenever a JSON or a Bulk response is received; and is rejected
- * if the response is an error.
- * Note: This return value can be ignored if you are using JSON alone,
- * because the callback provided in |onResponse| will be bound to the
- * "json-reply" event automatically.
- *
- * Events emitted:
- * * json-reply: The server replied with a JSON packet, which is
- * passed as event data.
- * * bulk-reply: The server replied with bulk data, which you can read
- * using the event data object containing:
- * * actor: Name of actor that received the packet
- * * type: Name of actor's method that was called on receipt
- * * length: Size of the data to be read
- * * stream: This input stream should only be used directly if you
- * can ensure that you will read exactly |length| bytes
- * and will not close the stream when reading is complete
- * * done: If you use the stream directly (instead of |copyTo|
- * below), you must signal completion by resolving /
- * rejecting this deferred. If it's rejected, the
- * transport will be closed. If an Error is supplied as a
- * rejection value, it will be logged via |dumpn|. If you
- * do use |copyTo|, resolving is taken care of for you
- * when copying completes.
- * * copyTo: A helper function for getting your data out of the
- * stream that meets the stream handling requirements
- * above, and has the following signature:
- * @param output nsIAsyncOutputStream
- * The stream to copy to.
- * @return Promise
- * The promise is resolved when copying completes or
- * rejected if any (unexpected) errors occur.
- * This object also emits "progress" events for each chunk
- * that is copied. See stream-utils.js.
- */
- request: function (packet, onResponse) {
- if (!this.mainRoot) {
- throw Error("Have not yet received a hello packet from the server.");
- }
- let type = packet.type || "";
- if (!packet.to) {
- throw Error("'" + type + "' request packet has no destination.");
- }
-
- // The onResponse callback might modify the response, so we need to call
- // it and resolve the promise with its result if it's truthy.
- const safeOnResponse = response => {
- if (!onResponse) {
- return response;
- }
- return onResponse(response) || response;
- };
-
- if (this._closed) {
- let msg = "'" + type + "' request packet to " +
- "'" + packet.to + "' " +
- "can't be sent as the connection is closed.";
- let resp = { error: "connectionClosed", message: msg };
- return promise.reject(safeOnResponse(resp));
- }
-
- let request = new Request(packet);
- request.format = "json";
- request.stack = getStack();
-
- // Implement a Promise like API on the returned object
- // that resolves/rejects on request response
- let deferred = promise.defer();
- function listenerJson(resp) {
- removeRequestListeners();
- if (resp.error) {
- deferred.reject(safeOnResponse(resp));
- } else {
- deferred.resolve(safeOnResponse(resp));
- }
- }
- function listenerBulk(resp) {
- removeRequestListeners();
- deferred.resolve(safeOnResponse(resp));
- }
-
- const removeRequestListeners = () => {
- request.off("json-reply", listenerJson);
- request.off("bulk-reply", listenerBulk);
- };
-
- request.on("json-reply", listenerJson);
- request.on("bulk-reply", listenerBulk);
-
- this._sendOrQueueRequest(request);
- request.then = deferred.promise.then.bind(deferred.promise);
-
- return request;
- },
-
- /**
- * Transmit streaming data via a bulk request.
- *
- * This method initiates the bulk send process by queuing up the header data.
- * The caller receives eventual access to a stream for writing.
- *
- * Since this opens up more options for how the server might respond (it could
- * send back either JSON or bulk data), and the returned Request object emits
- * events for different stages of the request process that you may want to
- * react to.
- *
- * @param request Object
- * This is modeled after the format of JSON packets above, but does not
- * actually contain the data, but is instead just a routing header:
- * * actor: Name of actor that will receive the packet
- * * type: Name of actor's method that should be called on receipt
- * * length: Size of the data to be sent
- * @return Request
- * This object emits a number of events to allow you to respond to
- * different parts of the request lifecycle.
- *
- * Events emitted:
- * * bulk-send-ready: Ready to send bulk data to the server, using the
- * event data object containing:
- * * stream: This output stream should only be used directly if
- * you can ensure that you will write exactly |length|
- * bytes and will not close the stream when writing is
- * complete
- * * done: If you use the stream directly (instead of |copyFrom|
- * below), you must signal completion by resolving /
- * rejecting this deferred. If it's rejected, the
- * transport will be closed. If an Error is supplied as
- * a rejection value, it will be logged via |dumpn|. If
- * you do use |copyFrom|, resolving is taken care of for
- * you when copying completes.
- * * copyFrom: A helper function for getting your data onto the
- * stream that meets the stream handling requirements
- * above, and has the following signature:
- * @param input nsIAsyncInputStream
- * The stream to copy from.
- * @return Promise
- * The promise is resolved when copying completes or
- * rejected if any (unexpected) errors occur.
- * This object also emits "progress" events for each chunk
- * that is copied. See stream-utils.js.
- * * json-reply: The server replied with a JSON packet, which is
- * passed as event data.
- * * bulk-reply: The server replied with bulk data, which you can read
- * using the event data object containing:
- * * actor: Name of actor that received the packet
- * * type: Name of actor's method that was called on receipt
- * * length: Size of the data to be read
- * * stream: This input stream should only be used directly if you
- * can ensure that you will read exactly |length| bytes
- * and will not close the stream when reading is complete
- * * done: If you use the stream directly (instead of |copyTo|
- * below), you must signal completion by resolving /
- * rejecting this deferred. If it's rejected, the
- * transport will be closed. If an Error is supplied as a
- * rejection value, it will be logged via |dumpn|. If you
- * do use |copyTo|, resolving is taken care of for you
- * when copying completes.
- * * copyTo: A helper function for getting your data out of the
- * stream that meets the stream handling requirements
- * above, and has the following signature:
- * @param output nsIAsyncOutputStream
- * The stream to copy to.
- * @return Promise
- * The promise is resolved when copying completes or
- * rejected if any (unexpected) errors occur.
- * This object also emits "progress" events for each chunk
- * that is copied. See stream-utils.js.
- */
- startBulkRequest: function (request) {
- if (!this.traits.bulk) {
- throw Error("Server doesn't support bulk transfers");
- }
- if (!this.mainRoot) {
- throw Error("Have not yet received a hello packet from the server.");
- }
- if (!request.type) {
- throw Error("Bulk packet is missing the required 'type' field.");
- }
- if (!request.actor) {
- throw Error("'" + request.type + "' bulk packet has no destination.");
- }
- if (!request.length) {
- throw Error("'" + request.type + "' bulk packet has no length.");
- }
-
- request = new Request(request);
- request.format = "bulk";
-
- this._sendOrQueueRequest(request);
-
- return request;
- },
-
- /**
- * If a new request can be sent immediately, do so. Otherwise, queue it.
- */
- _sendOrQueueRequest(request) {
- let actor = request.actor;
- if (!this._activeRequests.has(actor)) {
- this._sendRequest(request);
- } else {
- this._queueRequest(request);
- }
- },
-
- /**
- * Send a request.
- * @throws Error if there is already an active request in flight for the same
- * actor.
- */
- _sendRequest(request) {
- let actor = request.actor;
- this.expectReply(actor, request);
-
- if (request.format === "json") {
- this._transport.send(request.request);
- return;
- }
-
- this._transport.startBulkSend(request.request).then((...args) => {
- request.emit("bulk-send-ready", ...args);
- });
- },
-
- /**
- * Queue a request to be sent later. Queues are only drained when an in
- * flight request to a given actor completes.
- */
- _queueRequest(request) {
- let actor = request.actor;
- let queue = this._pendingRequests.get(actor) || [];
- queue.push(request);
- this._pendingRequests.set(actor, queue);
- },
-
- /**
- * Attempt the next request to a given actor (if any).
- */
- _attemptNextRequest(actor) {
- if (this._activeRequests.has(actor)) {
- return;
- }
- let queue = this._pendingRequests.get(actor);
- if (!queue) {
- return;
- }
- let request = queue.shift();
- if (queue.length === 0) {
- this._pendingRequests.delete(actor);
- }
- this._sendRequest(request);
- },
-
- /**
- * Arrange to hand the next reply from |actor| to the handler bound to
- * |request|.
- *
- * DebuggerClient.prototype.request / startBulkRequest usually takes care of
- * establishing the handler for a given request, but in rare cases (well,
- * greetings from new root actors, is the only case at the moment) we must be
- * prepared for a "reply" that doesn't correspond to any request we sent.
- */
- expectReply: function (actor, request) {
- if (this._activeRequests.has(actor)) {
- throw Error("clashing handlers for next reply from " + actor);
- }
-
- // If a handler is passed directly (as it is with the handler for the root
- // actor greeting), create a dummy request to bind this to.
- if (typeof request === "function") {
- let handler = request;
- request = new Request();
- request.on("json-reply", handler);
- }
-
- this._activeRequests.set(actor, request);
- },
-
- // Transport hooks.
-
- /**
- * Called by DebuggerTransport to dispatch incoming packets as appropriate.
- *
- * @param packet object
- * The incoming packet.
- */
- onPacket: function (packet) {
- if (!packet.from) {
- DevToolsUtils.reportException(
- "onPacket",
- new Error("Server did not specify an actor, dropping packet: " +
- JSON.stringify(packet)));
- return;
- }
-
- // If we have a registered Front for this actor, let it handle the packet
- // and skip all the rest of this unpleasantness.
- let front = this.getActor(packet.from);
- if (front) {
- front.onPacket(packet);
- return;
- }
-
- // Check for "forwardingCancelled" here instead of using a client to handle it.
- // This is necessary because we might receive this event while the client is closing,
- // and the clients have already been removed by that point.
- if (this.mainRoot &&
- packet.from == this.mainRoot.actor &&
- packet.type == "forwardingCancelled") {
- this.purgeRequests(packet.prefix);
- return;
- }
-
- if (this._clients.has(packet.from) && packet.type) {
- let client = this._clients.get(packet.from);
- let type = packet.type;
- if (client.events.indexOf(type) != -1) {
- client.emit(type, packet);
- // we ignore the rest, as the client is expected to handle this packet.
- return;
- }
- }
-
- let activeRequest;
- // See if we have a handler function waiting for a reply from this
- // actor. (Don't count unsolicited notifications or pauses as
- // replies.)
- if (this._activeRequests.has(packet.from) &&
- !(packet.type in UnsolicitedNotifications) &&
- !(packet.type == ThreadStateTypes.paused &&
- packet.why.type in UnsolicitedPauses)) {
- activeRequest = this._activeRequests.get(packet.from);
- this._activeRequests.delete(packet.from);
- }
-
- // If there is a subsequent request for the same actor, hand it off to the
- // transport. Delivery of packets on the other end is always async, even
- // in the local transport case.
- this._attemptNextRequest(packet.from);
-
- // Packets that indicate thread state changes get special treatment.
- if (packet.type in ThreadStateTypes &&
- this._clients.has(packet.from) &&
- typeof this._clients.get(packet.from)._onThreadState == "function") {
- this._clients.get(packet.from)._onThreadState(packet);
- }
-
- // TODO: Bug 1151156 - Remove once Gecko 40 is on b2g-stable.
- if (!this.traits.noNeedToFakeResumptionOnNavigation) {
- // On navigation the server resumes, so the client must resume as well.
- // We achieve that by generating a fake resumption packet that triggers
- // the client's thread state change listeners.
- if (packet.type == UnsolicitedNotifications.tabNavigated &&
- this._clients.has(packet.from) &&
- this._clients.get(packet.from).thread) {
- let thread = this._clients.get(packet.from).thread;
- let resumption = { from: thread._actor, type: "resumed" };
- thread._onThreadState(resumption);
- }
- }
-
- // Only try to notify listeners on events, not responses to requests
- // that lack a packet type.
- if (packet.type) {
- this.emit(packet.type, packet);
- }
-
- if (activeRequest) {
- let emitReply = () => activeRequest.emit("json-reply", packet);
- if (activeRequest.stack) {
- callFunctionWithAsyncStack(emitReply, activeRequest.stack,
- "DevTools RDP");
- } else {
- emitReply();
- }
- }
- },
-
- /**
- * Called by the DebuggerTransport to dispatch incoming bulk packets as
- * appropriate.
- *
- * @param packet object
- * The incoming packet, which contains:
- * * actor: Name of actor that will receive the packet
- * * type: Name of actor's method that should be called on receipt
- * * length: Size of the data to be read
- * * stream: This input stream should only be used directly if you can
- * ensure that you will read exactly |length| bytes and will
- * not close the stream when reading is complete
- * * done: If you use the stream directly (instead of |copyTo|
- * below), you must signal completion by resolving /
- * rejecting this deferred. If it's rejected, the transport
- * will be closed. If an Error is supplied as a rejection
- * value, it will be logged via |dumpn|. If you do use
- * |copyTo|, resolving is taken care of for you when copying
- * completes.
- * * copyTo: A helper function for getting your data out of the stream
- * that meets the stream handling requirements above, and has
- * the following signature:
- * @param output nsIAsyncOutputStream
- * The stream to copy to.
- * @return Promise
- * The promise is resolved when copying completes or rejected
- * if any (unexpected) errors occur.
- * This object also emits "progress" events for each chunk
- * that is copied. See stream-utils.js.
- */
- onBulkPacket: function (packet) {
- let { actor } = packet;
-
- if (!actor) {
- DevToolsUtils.reportException(
- "onBulkPacket",
- new Error("Server did not specify an actor, dropping bulk packet: " +
- JSON.stringify(packet)));
- return;
- }
-
- // See if we have a handler function waiting for a reply from this
- // actor.
- if (!this._activeRequests.has(actor)) {
- return;
- }
-
- let activeRequest = this._activeRequests.get(actor);
- this._activeRequests.delete(actor);
-
- // If there is a subsequent request for the same actor, hand it off to the
- // transport. Delivery of packets on the other end is always async, even
- // in the local transport case.
- this._attemptNextRequest(actor);
-
- activeRequest.emit("bulk-reply", packet);
- },
-
- /**
- * Called by DebuggerTransport when the underlying stream is closed.
- *
- * @param status nsresult
- * The status code that corresponds to the reason for closing
- * the stream.
- */
- onClosed: function () {
- this._closed = true;
- this.emit("closed");
-
- this.purgeRequests();
-
- // The |_pools| array on the client-side currently is used only by
- // protocol.js to store active fronts, mirroring the actor pools found in
- // the server. So, read all usages of "pool" as "protocol.js front".
- //
- // In the normal case where we shutdown cleanly, the toolbox tells each tool
- // to close, and they each call |destroy| on any fronts they were using.
- // When |destroy| or |cleanup| is called on a protocol.js front, it also
- // removes itself from the |_pools| array. Once the toolbox has shutdown,
- // the connection is closed, and we reach here. All fronts (should have
- // been) |destroy|ed, so |_pools| should empty.
- //
- // If the connection instead aborts unexpectedly, we may end up here with
- // all fronts used during the life of the connection. So, we call |cleanup|
- // on them clear their state, reject pending requests, and remove themselves
- // from |_pools|. This saves the toolbox from hanging indefinitely, in case
- // it waits for some server response before shutdown that will now never
- // arrive.
- for (let pool of this._pools) {
- pool.cleanup();
- }
- },
-
- /**
- * Purge pending and active requests in this client.
- *
- * @param prefix string (optional)
- * If a prefix is given, only requests for actor IDs that start with the prefix
- * will be cleaned up. This is useful when forwarding of a portion of requests
- * is cancelled on the server.
- */
- purgeRequests(prefix = "") {
- let reject = function (type, request) {
- // Server can send packets on its own and client only pass a callback
- // to expectReply, so that there is no request object.
- let msg;
- if (request.request) {
- msg = "'" + request.request.type + "' " + type + " request packet" +
- " to '" + request.actor + "' " +
- "can't be sent as the connection just closed.";
- } else {
- msg = "server side packet can't be received as the connection just closed.";
- }
- let packet = { error: "connectionClosed", message: msg };
- request.emit("json-reply", packet);
- };
-
- let pendingRequestsToReject = [];
- this._pendingRequests.forEach((requests, actor) => {
- if (!actor.startsWith(prefix)) {
- return;
- }
- this._pendingRequests.delete(actor);
- pendingRequestsToReject = pendingRequestsToReject.concat(requests);
- });
- pendingRequestsToReject.forEach(request => reject("pending", request));
-
- let activeRequestsToReject = [];
- this._activeRequests.forEach((request, actor) => {
- if (!actor.startsWith(prefix)) {
- return;
- }
- this._activeRequests.delete(actor);
- activeRequestsToReject = activeRequestsToReject.concat(request);
- });
- activeRequestsToReject.forEach(request => reject("active", request));
- },
-
- /**
- * Search for all requests in process for this client, including those made via
- * protocol.js and wait all of them to complete. Since the requests seen when this is
- * first called may in turn trigger more requests, we keep recursing through this
- * function until there is no more activity.
- *
- * This is a fairly heavy weight process, so it's only meant to be used in tests.
- *
- * @return Promise
- * Resolved when all requests have settled.
- */
- waitForRequestsToSettle() {
- let requests = [];
-
- // Gather all pending and active requests in this client
- // The request object supports a Promise API for completion (it has .then())
- this._pendingRequests.forEach(requestsForActor => {
- // Each value is an array of pending requests
- requests = requests.concat(requestsForActor);
- });
- this._activeRequests.forEach(requestForActor => {
- // Each value is a single active request
- requests = requests.concat(requestForActor);
- });
-
- // protocol.js
- // Use a Set because some fronts (like domwalker) seem to have multiple parents.
- let fronts = new Set();
- let poolsToVisit = [...this._pools];
-
- // With protocol.js, each front can potentially have it's own pools containing child
- // fronts, forming a tree. Descend through all the pools to locate all child fronts.
- while (poolsToVisit.length) {
- let pool = poolsToVisit.shift();
- fronts.add(pool);
- for (let child of pool.poolChildren()) {
- poolsToVisit.push(child);
- }
- }
-
- // For each front, wait for its requests to settle
- for (let front of fronts) {
- if (front.hasRequests()) {
- requests.push(front.waitForRequestsToSettle());
- }
- }
-
- // Abort early if there are no requests
- if (!requests.length) {
- return Promise.resolve();
- }
-
- return DevToolsUtils.settleAll(requests).catch(() => {
- // One of the requests might have failed, but ignore that situation here and pipe
- // both success and failure through the same path. The important part is just that
- // we waited.
- }).then(() => {
- // Repeat, more requests may have started in response to those we just waited for
- return this.waitForRequestsToSettle();
- });
- },
-
- registerClient: function (client) {
- let actorID = client.actor;
- if (!actorID) {
- throw new Error("DebuggerServer.registerClient expects " +
- "a client instance with an `actor` attribute.");
- }
- if (!Array.isArray(client.events)) {
- throw new Error("DebuggerServer.registerClient expects " +
- "a client instance with an `events` attribute " +
- "that is an array.");
- }
- if (client.events.length > 0 && typeof (client.emit) != "function") {
- throw new Error("DebuggerServer.registerClient expects " +
- "a client instance with non-empty `events` array to" +
- "have an `emit` function.");
- }
- if (this._clients.has(actorID)) {
- throw new Error("DebuggerServer.registerClient already registered " +
- "a client for this actor.");
- }
- this._clients.set(actorID, client);
- },
-
- unregisterClient: function (client) {
- let actorID = client.actor;
- if (!actorID) {
- throw new Error("DebuggerServer.unregisterClient expects " +
- "a Client instance with a `actor` attribute.");
- }
- this._clients.delete(actorID);
- },
-
- /**
- * Actor lifetime management, echos the server's actor pools.
- */
- __pools: null,
- get _pools() {
- if (this.__pools) {
- return this.__pools;
- }
- this.__pools = new Set();
- return this.__pools;
- },
-
- addActorPool: function (pool) {
- this._pools.add(pool);
- },
- removeActorPool: function (pool) {
- this._pools.delete(pool);
- },
- getActor: function (actorID) {
- let pool = this.poolFor(actorID);
- return pool ? pool.get(actorID) : null;
- },
-
- poolFor: function (actorID) {
- for (let pool of this._pools) {
- if (pool.has(actorID)) {
- return pool;
- }
- }
- return null;
- },
-
- /**
- * Currently attached addon.
- */
- activeAddon: null
-};
-
-eventSource(DebuggerClient.prototype);
-
-class Request extends EventEmitter {
- constructor(request) {
- super();
- this.request = request;
- }
-
- get actor() {
- return this.request.to || this.request.actor;
- }
-}
-
-/**
- * Creates a tab client for the remote debugging protocol server. This client
- * is a front to the tab actor created in the server side, hiding the protocol
- * details in a traditional JavaScript API.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param form object
- * The protocol form for this tab.
- */
-function TabClient(client, form) {
- this.client = client;
- this._actor = form.from;
- this._threadActor = form.threadActor;
- this.javascriptEnabled = form.javascriptEnabled;
- this.cacheDisabled = form.cacheDisabled;
- this.thread = null;
- this.request = this.client.request;
- this.traits = form.traits || {};
- this.events = ["workerListChanged"];
-}
-
-TabClient.prototype = {
- get actor() {
- return this._actor;
- },
- get _transport() {
- return this.client._transport;
- },
-
- /**
- * Attach to a thread actor.
- *
- * @param object options
- * Configuration options.
- * - useSourceMaps: whether to use source maps or not.
- * @param function onResponse
- * Called with the response packet and a ThreadClient
- * (which will be undefined on error).
- */
- attachThread: function (options = {}, onResponse = noop) {
- if (this.thread) {
- DevToolsUtils.executeSoon(() => onResponse({}, this.thread));
- return promise.resolve([{}, this.thread]);
- }
-
- let packet = {
- to: this._threadActor,
- type: "attach",
- options,
- };
- return this.request(packet).then(response => {
- if (!response.error) {
- this.thread = new ThreadClient(this, this._threadActor);
- this.client.registerClient(this.thread);
- }
- onResponse(response, this.thread);
- return [response, this.thread];
- });
- },
-
- /**
- * Detach the client from the tab actor.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- detach: DebuggerClient.requester({
- type: "detach"
- }, {
- before: function (packet) {
- if (this.thread) {
- this.thread.detach();
- }
- return packet;
- },
- after: function (response) {
- this.client.unregisterClient(this);
- return response;
- },
- }),
-
- /**
- * Bring the window to the front.
- */
- focus: DebuggerClient.requester({
- type: "focus"
- }, {}),
-
- /**
- * Reload the page in this tab.
- *
- * @param [optional] object options
- * An object with a `force` property indicating whether or not
- * this reload should skip the cache
- */
- reload: function (options = { force: false }) {
- return this._reload(options);
- },
- _reload: DebuggerClient.requester({
- type: "reload",
- options: arg(0)
- }),
-
- /**
- * Navigate to another URL.
- *
- * @param string url
- * The URL to navigate to.
- */
- navigateTo: DebuggerClient.requester({
- type: "navigateTo",
- url: arg(0)
- }),
-
- /**
- * Reconfigure the tab actor.
- *
- * @param object options
- * A dictionary object of the new options to use in the tab actor.
- * @param function onResponse
- * Called with the response packet.
- */
- reconfigure: DebuggerClient.requester({
- type: "reconfigure",
- options: arg(0)
- }),
-
- listWorkers: DebuggerClient.requester({
- type: "listWorkers"
- }),
-
- attachWorker: function (workerActor, onResponse) {
- return this.client.attachWorker(workerActor, onResponse);
- },
-};
-
-eventSource(TabClient.prototype);
-
-function WorkerClient(client, form) {
- this.client = client;
- this._actor = form.from;
- this._isClosed = false;
- this._url = form.url;
-
- this._onClose = this._onClose.bind(this);
-
- this.addListener("close", this._onClose);
-
- this.traits = {};
-}
-
-WorkerClient.prototype = {
- get _transport() {
- return this.client._transport;
- },
-
- get request() {
- return this.client.request;
- },
-
- get actor() {
- return this._actor;
- },
-
- get url() {
- return this._url;
- },
-
- get isClosed() {
- return this._isClosed;
- },
-
- detach: DebuggerClient.requester({ type: "detach" }, {
- after: function (response) {
- if (this.thread) {
- this.client.unregisterClient(this.thread);
- }
- this.client.unregisterClient(this);
- return response;
- },
- }),
-
- attachThread: function (options = {}, onResponse = noop) {
- if (this.thread) {
- let response = [{
- type: "connected",
- threadActor: this.thread._actor,
- consoleActor: this.consoleActor,
- }, this.thread];
- DevToolsUtils.executeSoon(() => onResponse(response));
- return response;
- }
-
- // The connect call on server doesn't attach the thread as of version 44.
- return this.request({
- to: this._actor,
- type: "connect",
- options,
- }).then(connectResponse => {
- if (connectResponse.error) {
- onResponse(connectResponse, null);
- return [connectResponse, null];
- }
-
- return this.request({
- to: connectResponse.threadActor,
- type: "attach",
- options,
- }).then(attachResponse => {
- if (attachResponse.error) {
- onResponse(attachResponse, null);
- }
-
- this.thread = new ThreadClient(this, connectResponse.threadActor);
- this.consoleActor = connectResponse.consoleActor;
- this.client.registerClient(this.thread);
-
- onResponse(connectResponse, this.thread);
- return [connectResponse, this.thread];
- });
- }, error => {
- onResponse(error, null);
- });
- },
-
- _onClose: function () {
- this.removeListener("close", this._onClose);
-
- if (this.thread) {
- this.client.unregisterClient(this.thread);
- }
- this.client.unregisterClient(this);
- this._isClosed = true;
- },
-
- reconfigure: function () {
- return Promise.resolve();
- },
-
- events: ["close"]
-};
-
-eventSource(WorkerClient.prototype);
-
-function AddonClient(client, actor) {
- this._client = client;
- this._actor = actor;
- this.request = this._client.request;
- this.events = [];
-}
-
-AddonClient.prototype = {
- get actor() {
- return this._actor;
- },
- get _transport() {
- return this._client._transport;
- },
-
- /**
- * Detach the client from the addon actor.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- detach: DebuggerClient.requester({
- type: "detach"
- }, {
- after: function (response) {
- if (this._client.activeAddon === this) {
- this._client.activeAddon = null;
- }
- this._client.unregisterClient(this);
- return response;
- },
- })
-};
-
-/**
- * A RootClient object represents a root actor on the server. Each
- * DebuggerClient keeps a RootClient instance representing the root actor
- * for the initial connection; DebuggerClient's 'listTabs' and
- * 'listChildProcesses' methods forward to that root actor.
- *
- * @param client object
- * The client connection to which this actor belongs.
- * @param greeting string
- * The greeting packet from the root actor we're to represent.
- *
- * Properties of a RootClient instance:
- *
- * @property actor string
- * The name of this child's root actor.
- * @property applicationType string
- * The application type, as given in the root actor's greeting packet.
- * @property traits object
- * The traits object, as given in the root actor's greeting packet.
- */
-function RootClient(client, greeting) {
- this._client = client;
- this.actor = greeting.from;
- this.applicationType = greeting.applicationType;
- this.traits = greeting.traits;
-}
-exports.RootClient = RootClient;
-
-RootClient.prototype = {
- constructor: RootClient,
-
- /**
- * Gets the "root" form, which lists all the global actors that affect the entire
- * browser. This can replace usages of `listTabs` that only wanted the global actors
- * and didn't actually care about tabs.
- */
- getRoot: DebuggerClient.requester({ type: "getRoot" }),
-
- /**
- * List the open tabs.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- listTabs: DebuggerClient.requester({ type: "listTabs" }),
-
- /**
- * List the installed addons.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- listAddons: DebuggerClient.requester({ type: "listAddons" }),
-
- /**
- * List the registered workers.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- listWorkers: DebuggerClient.requester({ type: "listWorkers" }),
-
- /**
- * List the registered service workers.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- listServiceWorkerRegistrations: DebuggerClient.requester({
- type: "listServiceWorkerRegistrations"
- }),
-
- /**
- * List the running processes.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- listProcesses: DebuggerClient.requester({ type: "listProcesses" }),
-
- /**
- * Fetch the TabActor for the currently selected tab, or for a specific
- * tab given as first parameter.
- *
- * @param [optional] object filter
- * A dictionary object with following optional attributes:
- * - outerWindowID: used to match tabs in parent process
- * - tabId: used to match tabs in child processes
- * - tab: a reference to xul:tab element
- * If nothing is specified, returns the actor for the currently
- * selected tab.
- */
- getTab: function (filter) {
- let packet = {
- to: this.actor,
- type: "getTab"
- };
-
- if (filter) {
- if (typeof (filter.outerWindowID) == "number") {
- packet.outerWindowID = filter.outerWindowID;
- } else if (typeof (filter.tabId) == "number") {
- packet.tabId = filter.tabId;
- } else if ("tab" in filter) {
- let browser = filter.tab.linkedBrowser;
- if (browser.frameLoader.tabParent) {
- // Tabs in child process
- packet.tabId = browser.frameLoader.tabParent.tabId;
- } else if (browser.outerWindowID) {
- // <xul:browser> tabs in parent process
- packet.outerWindowID = browser.outerWindowID;
- } else {
- // <iframe mozbrowser> tabs in parent process
- let windowUtils = browser.contentWindow
- .QueryInterface(Ci.nsIInterfaceRequestor)
- .getInterface(Ci.nsIDOMWindowUtils);
- packet.outerWindowID = windowUtils.outerWindowID;
- }
- } else {
- // Throw if a filter object have been passed but without
- // any clearly idenfified filter.
- throw new Error("Unsupported argument given to getTab request");
- }
- }
-
- return this.request(packet);
- },
-
- /**
- * Fetch the WindowActor for a specific window, like a browser window in
- * Firefox, but it can be used to reach any window in the process.
- *
- * @param number outerWindowID
- * The outerWindowID of the top level window you are looking for.
- */
- getWindow: function ({ outerWindowID }) {
- if (!outerWindowID) {
- throw new Error("Must specify outerWindowID");
- }
-
- let packet = {
- to: this.actor,
- type: "getWindow",
- outerWindowID,
- };
-
- return this.request(packet);
- },
-
- /**
- * Description of protocol's actors and methods.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- protocolDescription: DebuggerClient.requester({ type: "protocolDescription" }),
-
- /*
- * Methods constructed by DebuggerClient.requester require these forwards
- * on their 'this'.
- */
- get _transport() {
- return this._client._transport;
- },
- get request() {
- return this._client.request;
- }
-};
-
-/**
- * Creates a thread client for the remote debugging protocol server. This client
- * is a front to the thread actor created in the server side, hiding the
- * protocol details in a traditional JavaScript API.
- *
- * @param client DebuggerClient|TabClient
- * The parent of the thread (tab for tab-scoped debuggers, DebuggerClient
- * for chrome debuggers).
- * @param actor string
- * The actor ID for this thread.
- */
-function ThreadClient(client, actor) {
- this._parent = client;
- this.client = client instanceof DebuggerClient ? client : client.client;
- this._actor = actor;
- this._frameCache = [];
- this._scriptCache = {};
- this._pauseGrips = {};
- this._threadGrips = {};
- this.request = this.client.request;
-}
-
-ThreadClient.prototype = {
- _state: "paused",
- get state() {
- return this._state;
- },
- get paused() {
- return this._state === "paused";
- },
-
- _pauseOnExceptions: false,
- _ignoreCaughtExceptions: false,
- _pauseOnDOMEvents: null,
-
- _actor: null,
- get actor() {
- return this._actor;
- },
-
- get _transport() {
- return this.client._transport;
- },
-
- _assertPaused: function (command) {
- if (!this.paused) {
- throw Error(command + " command sent while not paused. Currently " + this._state);
- }
- },
-
- /**
- * Resume a paused thread. If the optional limit parameter is present, then
- * the thread will also pause when that limit is reached.
- *
- * @param [optional] object limit
- * An object with a type property set to the appropriate limit (next,
- * step, or finish) per the remote debugging protocol specification.
- * Use null to specify no limit.
- * @param function onResponse
- * Called with the response packet.
- */
- _doResume: DebuggerClient.requester({
- type: "resume",
- resumeLimit: arg(0)
- }, {
- before: function (packet) {
- this._assertPaused("resume");
-
- // Put the client in a tentative "resuming" state so we can prevent
- // further requests that should only be sent in the paused state.
- this._previousState = this._state;
- this._state = "resuming";
-
- if (this._pauseOnExceptions) {
- packet.pauseOnExceptions = this._pauseOnExceptions;
- }
- if (this._ignoreCaughtExceptions) {
- packet.ignoreCaughtExceptions = this._ignoreCaughtExceptions;
- }
- if (this._pauseOnDOMEvents) {
- packet.pauseOnDOMEvents = this._pauseOnDOMEvents;
- }
- return packet;
- },
- after: function (response) {
- if (response.error && this._state == "resuming") {
- // There was an error resuming, update the state to the new one
- // reported by the server, if given (only on wrongState), otherwise
- // reset back to the previous state.
- if (response.state) {
- this._state = ThreadStateTypes[response.state];
- } else {
- this._state = this._previousState;
- }
- }
- delete this._previousState;
- return response;
- },
- }),
-
- /**
- * Reconfigure the thread actor.
- *
- * @param object options
- * A dictionary object of the new options to use in the thread actor.
- * @param function onResponse
- * Called with the response packet.
- */
- reconfigure: DebuggerClient.requester({
- type: "reconfigure",
- options: arg(0)
- }),
-
- /**
- * Resume a paused thread.
- */
- resume: function (onResponse) {
- return this._doResume(null, onResponse);
- },
-
- /**
- * Resume then pause without stepping.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- resumeThenPause: function (onResponse) {
- return this._doResume({ type: "break" }, onResponse);
- },
-
- /**
- * Step over a function call.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- stepOver: function (onResponse) {
- return this._doResume({ type: "next" }, onResponse);
- },
-
- /**
- * Step into a function call.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- stepIn: function (onResponse) {
- return this._doResume({ type: "step" }, onResponse);
- },
-
- /**
- * Step out of a function call.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- stepOut: function (onResponse) {
- return this._doResume({ type: "finish" }, onResponse);
- },
-
- /**
- * Immediately interrupt a running thread.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- interrupt: function (onResponse) {
- return this._doInterrupt(null, onResponse);
- },
-
- /**
- * Pause execution right before the next JavaScript bytecode is executed.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- breakOnNext: function (onResponse) {
- return this._doInterrupt("onNext", onResponse);
- },
-
- /**
- * Interrupt a running thread.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- _doInterrupt: DebuggerClient.requester({
- type: "interrupt",
- when: arg(0)
- }),
-
- /**
- * Enable or disable pausing when an exception is thrown.
- *
- * @param boolean pauseOnExceptions
- * Enables pausing if true, disables otherwise.
- * @param boolean ignoreCaughtExceptions
- * Whether to ignore caught exceptions
- * @param function onResponse
- * Called with the response packet.
- */
- pauseOnExceptions: function (pauseOnExceptions,
- ignoreCaughtExceptions,
- onResponse = noop) {
- this._pauseOnExceptions = pauseOnExceptions;
- this._ignoreCaughtExceptions = ignoreCaughtExceptions;
-
- // Otherwise send the flag using a standard resume request.
- if (!this.paused) {
- return this.interrupt(response => {
- if (response.error) {
- // Can't continue if pausing failed.
- onResponse(response);
- return response;
- }
- return this.resume(onResponse);
- });
- }
-
- onResponse();
- return promise.resolve();
- },
-
- /**
- * Enable pausing when the specified DOM events are triggered. Disabling
- * pausing on an event can be realized by calling this method with the updated
- * array of events that doesn't contain it.
- *
- * @param array|string events
- * An array of strings, representing the DOM event types to pause on,
- * or "*" to pause on all DOM events. Pass an empty array to
- * completely disable pausing on DOM events.
- * @param function onResponse
- * Called with the response packet in a future turn of the event loop.
- */
- pauseOnDOMEvents: function (events, onResponse = noop) {
- this._pauseOnDOMEvents = events;
- // If the debuggee is paused, the value of the array will be communicated in
- // the next resumption. Otherwise we have to force a pause in order to send
- // the array.
- if (this.paused) {
- DevToolsUtils.executeSoon(() => onResponse({}));
- return {};
- }
- return this.interrupt(response => {
- // Can't continue if pausing failed.
- if (response.error) {
- onResponse(response);
- return response;
- }
- return this.resume(onResponse);
- });
- },
-
- /**
- * Send a clientEvaluate packet to the debuggee. Response
- * will be a resume packet.
- *
- * @param string frame
- * The actor ID of the frame where the evaluation should take place.
- * @param string expression
- * The expression that will be evaluated in the scope of the frame
- * above.
- * @param function onResponse
- * Called with the response packet.
- */
- eval: DebuggerClient.requester({
- type: "clientEvaluate",
- frame: arg(0),
- expression: arg(1)
- }, {
- before: function (packet) {
- this._assertPaused("eval");
- // Put the client in a tentative "resuming" state so we can prevent
- // further requests that should only be sent in the paused state.
- this._state = "resuming";
- return packet;
- },
- after: function (response) {
- if (response.error) {
- // There was an error resuming, back to paused state.
- this._state = "paused";
- }
- return response;
- },
- }),
-
- /**
- * Detach from the thread actor.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- detach: DebuggerClient.requester({
- type: "detach"
- }, {
- after: function (response) {
- this.client.unregisterClient(this);
- this._parent.thread = null;
- return response;
- },
- }),
-
- /**
- * Release multiple thread-lifetime object actors. If any pause-lifetime
- * actors are included in the request, a |notReleasable| error will return,
- * but all the thread-lifetime ones will have been released.
- *
- * @param array actors
- * An array with actor IDs to release.
- */
- releaseMany: DebuggerClient.requester({
- type: "releaseMany",
- actors: arg(0),
- }),
-
- /**
- * Promote multiple pause-lifetime object actors to thread-lifetime ones.
- *
- * @param array actors
- * An array with actor IDs to promote.
- */
- threadGrips: DebuggerClient.requester({
- type: "threadGrips",
- actors: arg(0)
- }),
-
- /**
- * Return the event listeners defined on the page.
- *
- * @param onResponse Function
- * Called with the thread's response.
- */
- eventListeners: DebuggerClient.requester({
- type: "eventListeners"
- }),
-
- /**
- * Request the loaded sources for the current thread.
- *
- * @param onResponse Function
- * Called with the thread's response.
- */
- getSources: DebuggerClient.requester({
- type: "sources"
- }),
-
- /**
- * Clear the thread's source script cache. A scriptscleared event
- * will be sent.
- */
- _clearScripts: function () {
- if (Object.keys(this._scriptCache).length > 0) {
- this._scriptCache = {};
- this.emit("scriptscleared");
- }
- },
-
- /**
- * Request frames from the callstack for the current thread.
- *
- * @param start integer
- * The number of the youngest stack frame to return (the youngest
- * frame is 0).
- * @param count integer
- * The maximum number of frames to return, or null to return all
- * frames.
- * @param onResponse function
- * Called with the thread's response.
- */
- getFrames: DebuggerClient.requester({
- type: "frames",
- start: arg(0),
- count: arg(1)
- }),
-
- /**
- * An array of cached frames. Clients can observe the framesadded and
- * framescleared event to keep up to date on changes to this cache,
- * and can fill it using the fillFrames method.
- */
- get cachedFrames() {
- return this._frameCache;
- },
-
- /**
- * true if there are more stack frames available on the server.
- */
- get moreFrames() {
- return this.paused && (!this._frameCache || this._frameCache.length == 0
- || !this._frameCache[this._frameCache.length - 1].oldest);
- },
-
- /**
- * Request the frame environment.
- *
- * @param frameId string
- */
- getEnvironment: function (frameId) {
- return this.request({ to: frameId, type: "getEnvironment" });
- },
-
- /**
- * Ensure that at least total stack frames have been loaded in the
- * ThreadClient's stack frame cache. A framesadded event will be
- * sent when the stack frame cache is updated.
- *
- * @param total number
- * The minimum number of stack frames to be included.
- * @param callback function
- * Optional callback function called when frames have been loaded
- * @returns true if a framesadded notification should be expected.
- */
- fillFrames: function (total, callback = noop) {
- this._assertPaused("fillFrames");
- if (this._frameCache.length >= total) {
- return false;
- }
-
- let numFrames = this._frameCache.length;
-
- this.getFrames(numFrames, total - numFrames, (response) => {
- if (response.error) {
- callback(response);
- return;
- }
-
- let threadGrips = DevToolsUtils.values(this._threadGrips);
-
- for (let i in response.frames) {
- let frame = response.frames[i];
- if (!frame.where.source) {
- // Older servers use urls instead, so we need to resolve
- // them to source actors
- for (let grip of threadGrips) {
- if (grip instanceof SourceClient && grip.url === frame.url) {
- frame.where.source = grip._form;
- }
- }
- }
-
- this._frameCache[frame.depth] = frame;
- }
-
- // If we got as many frames as we asked for, there might be more
- // frames available.
- this.emit("framesadded");
-
- callback(response);
- });
-
- return true;
- },
-
- /**
- * Clear the thread's stack frame cache. A framescleared event
- * will be sent.
- */
- _clearFrames: function () {
- if (this._frameCache.length > 0) {
- this._frameCache = [];
- this.emit("framescleared");
- }
- },
-
- /**
- * Return a ObjectClient object for the given object grip.
- *
- * @param grip object
- * A pause-lifetime object grip returned by the protocol.
- */
- pauseGrip: function (grip) {
- if (grip.actor in this._pauseGrips) {
- return this._pauseGrips[grip.actor];
- }
-
- let client = new ObjectClient(this.client, grip);
- this._pauseGrips[grip.actor] = client;
- return client;
- },
-
- /**
- * Get or create a long string client, checking the grip client cache if it
- * already exists.
- *
- * @param grip Object
- * The long string grip returned by the protocol.
- * @param gripCacheName String
- * The property name of the grip client cache to check for existing
- * clients in.
- */
- _longString: function (grip, gripCacheName) {
- if (grip.actor in this[gripCacheName]) {
- return this[gripCacheName][grip.actor];
- }
-
- let client = new LongStringClient(this.client, grip);
- this[gripCacheName][grip.actor] = client;
- return client;
- },
-
- /**
- * Return an instance of LongStringClient for the given long string grip that
- * is scoped to the current pause.
- *
- * @param grip Object
- * The long string grip returned by the protocol.
- */
- pauseLongString: function (grip) {
- return this._longString(grip, "_pauseGrips");
- },
-
- /**
- * Return an instance of LongStringClient for the given long string grip that
- * is scoped to the thread lifetime.
- *
- * @param grip Object
- * The long string grip returned by the protocol.
- */
- threadLongString: function (grip) {
- return this._longString(grip, "_threadGrips");
- },
-
- /**
- * Get or create an ArrayBuffer client, checking the grip client cache if it
- * already exists.
- *
- * @param grip Object
- * The ArrayBuffer grip returned by the protocol.
- * @param gripCacheName String
- * The property name of the grip client cache to check for existing
- * clients in.
- */
- _arrayBuffer: function (grip, gripCacheName) {
- if (grip.actor in this[gripCacheName]) {
- return this[gripCacheName][grip.actor];
- }
-
- let client = new ArrayBufferClient(this.client, grip);
- this[gripCacheName][grip.actor] = client;
- return client;
- },
-
- /**
- * Return an instance of ArrayBufferClient for the given ArrayBuffer grip that
- * is scoped to the thread lifetime.
- *
- * @param grip Object
- * The ArrayBuffer grip returned by the protocol.
- */
- threadArrayBuffer: function (grip) {
- return this._arrayBuffer(grip, "_threadGrips");
- },
-
- /**
- * Clear and invalidate all the grip clients from the given cache.
- *
- * @param gripCacheName
- * The property name of the grip cache we want to clear.
- */
- _clearObjectClients: function (gripCacheName) {
- for (let id in this[gripCacheName]) {
- this[gripCacheName][id].valid = false;
- }
- this[gripCacheName] = {};
- },
-
- /**
- * Invalidate pause-lifetime grip clients and clear the list of current grip
- * clients.
- */
- _clearPauseGrips: function () {
- this._clearObjectClients("_pauseGrips");
- },
-
- /**
- * Invalidate thread-lifetime grip clients and clear the list of current grip
- * clients.
- */
- _clearThreadGrips: function () {
- this._clearObjectClients("_threadGrips");
- },
-
- /**
- * Handle thread state change by doing necessary cleanup and notifying all
- * registered listeners.
- */
- _onThreadState: function (packet) {
- this._state = ThreadStateTypes[packet.type];
- // The debugger UI may not be initialized yet so we want to keep
- // the packet around so it knows what to pause state to display
- // when it's initialized
- this._lastPausePacket = packet.type === "resumed" ? null : packet;
- this._clearFrames();
- this._clearPauseGrips();
- packet.type === ThreadStateTypes.detached && this._clearThreadGrips();
- this.client._eventsEnabled && this.emit(packet.type, packet);
- },
-
- getLastPausePacket: function () {
- return this._lastPausePacket;
- },
-
- /**
- * Return an EnvironmentClient instance for the given environment actor form.
- */
- environment: function (form) {
- return new EnvironmentClient(this.client, form);
- },
-
- /**
- * Return an instance of SourceClient for the given source actor form.
- */
- source: function (form) {
- if (form.actor in this._threadGrips) {
- return this._threadGrips[form.actor];
- }
-
- this._threadGrips[form.actor] = new SourceClient(this, form);
- return this._threadGrips[form.actor];
- },
-
- /**
- * Request the prototype and own properties of mutlipleObjects.
- *
- * @param onResponse function
- * Called with the request's response.
- * @param actors [string]
- * List of actor ID of the queried objects.
- */
- getPrototypesAndProperties: DebuggerClient.requester({
- type: "prototypesAndProperties",
- actors: arg(0)
- }),
-
- events: ["newSource"]
-};
-
-eventSource(ThreadClient.prototype);
-
-/**
- * Creates a tracing profiler client for the remote debugging protocol
- * server. This client is a front to the trace actor created on the
- * server side, hiding the protocol details in a traditional
- * JavaScript API.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param actor string
- * The actor ID for this thread.
- */
-function TraceClient(client, actor) {
- this._client = client;
- this._actor = actor;
- this._activeTraces = new Set();
- this._waitingPackets = new Map();
- this._expectedPacket = 0;
- this.request = this._client.request;
- this.events = [];
-}
-
-TraceClient.prototype = {
- get actor() {
- return this._actor;
- },
- get tracing() {
- return this._activeTraces.size > 0;
- },
-
- get _transport() {
- return this._client._transport;
- },
-
- /**
- * Detach from the trace actor.
- */
- detach: DebuggerClient.requester({
- type: "detach"
- }, {
- after: function (response) {
- this._client.unregisterClient(this);
- return response;
- },
- }),
-
- /**
- * Start a new trace.
- *
- * @param trace [string]
- * An array of trace types to be recorded by the new trace.
- *
- * @param name string
- * The name of the new trace.
- *
- * @param onResponse function
- * Called with the request's response.
- */
- startTrace: DebuggerClient.requester({
- type: "startTrace",
- name: arg(1),
- trace: arg(0)
- }, {
- after: function (response) {
- if (response.error) {
- return response;
- }
-
- if (!this.tracing) {
- this._waitingPackets.clear();
- this._expectedPacket = 0;
- }
- this._activeTraces.add(response.name);
-
- return response;
- },
- }),
-
- /**
- * End a trace. If a name is provided, stop the named
- * trace. Otherwise, stop the most recently started trace.
- *
- * @param name string
- * The name of the trace to stop.
- *
- * @param onResponse function
- * Called with the request's response.
- */
- stopTrace: DebuggerClient.requester({
- type: "stopTrace",
- name: arg(0)
- }, {
- after: function (response) {
- if (response.error) {
- return response;
- }
-
- this._activeTraces.delete(response.name);
-
- return response;
- },
- })
-};
-
-/**
- * Grip clients are used to retrieve information about the relevant object.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param grip object
- * A pause-lifetime object grip returned by the protocol.
- */
-function ObjectClient(client, grip) {
- this._grip = grip;
- this._client = client;
- this.request = this._client.request;
-}
-exports.ObjectClient = ObjectClient;
-
-ObjectClient.prototype = {
- get actor() {
- return this._grip.actor;
- },
- get _transport() {
- return this._client._transport;
- },
-
- valid: true,
-
- get isFrozen() {
- return this._grip.frozen;
- },
- get isSealed() {
- return this._grip.sealed;
- },
- get isExtensible() {
- return this._grip.extensible;
- },
-
- getDefinitionSite: DebuggerClient.requester({
- type: "definitionSite"
- }, {
- before: function (packet) {
- if (this._grip.class != "Function") {
- throw new Error("getDefinitionSite is only valid for function grips.");
- }
- return packet;
- }
- }),
-
- /**
- * Request the names of a function's formal parameters.
- *
- * @param onResponse function
- * Called with an object of the form:
- * { parameterNames:[<parameterName>, ...] }
- * where each <parameterName> is the name of a parameter.
- */
- getParameterNames: DebuggerClient.requester({
- type: "parameterNames"
- }, {
- before: function (packet) {
- if (this._grip.class !== "Function") {
- throw new Error("getParameterNames is only valid for function grips.");
- }
- return packet;
- },
- }),
-
- /**
- * Request the names of the properties defined on the object and not its
- * prototype.
- *
- * @param onResponse function Called with the request's response.
- */
- getOwnPropertyNames: DebuggerClient.requester({
- type: "ownPropertyNames"
- }),
-
- /**
- * Request the prototype and own properties of the object.
- *
- * @param onResponse function Called with the request's response.
- */
- getPrototypeAndProperties: DebuggerClient.requester({
- type: "prototypeAndProperties"
- }),
-
- /**
- * Request a PropertyIteratorClient instance to ease listing
- * properties for this object.
- *
- * @param options Object
- * A dictionary object with various boolean attributes:
- * - ignoreIndexedProperties Boolean
- * If true, filters out Array items.
- * e.g. properties names between `0` and `object.length`.
- * - ignoreNonIndexedProperties Boolean
- * If true, filters out items that aren't array items
- * e.g. properties names that are not a number between `0`
- * and `object.length`.
- * - sort Boolean
- * If true, the iterator will sort the properties by name
- * before dispatching them.
- * @param onResponse function Called with the client instance.
- */
- enumProperties: DebuggerClient.requester({
- type: "enumProperties",
- options: arg(0)
- }, {
- after: function (response) {
- if (response.iterator) {
- return { iterator: new PropertyIteratorClient(this._client, response.iterator) };
- }
- return response;
- },
- }),
-
- /**
- * Request a PropertyIteratorClient instance to enumerate entries in a
- * Map/Set-like object.
- *
- * @param onResponse function Called with the request's response.
- */
- enumEntries: DebuggerClient.requester({
- type: "enumEntries"
- }, {
- before: function (packet) {
- if (!["Map", "WeakMap", "Set", "WeakSet"].includes(this._grip.class)) {
- throw new Error("enumEntries is only valid for Map/Set-like grips.");
- }
- return packet;
- },
- after: function (response) {
- if (response.iterator) {
- return {
- iterator: new PropertyIteratorClient(this._client, response.iterator)
- };
- }
- return response;
- }
- }),
-
- /**
- * Request a SymbolIteratorClient instance to enumerate symbols in an object.
- *
- * @param onResponse function Called with the request's response.
- */
- enumSymbols: DebuggerClient.requester({
- type: "enumSymbols"
- }, {
- before: function (packet) {
- if (this._grip.type !== "object") {
- throw new Error("enumSymbols is only valid for objects grips.");
- }
- return packet;
- },
- after: function (response) {
- if (response.iterator) {
- return {
- iterator: new SymbolIteratorClient(this._client, response.iterator)
- };
- }
- return response;
- }
- }),
-
- /**
- * Request the property descriptor of the object's specified property.
- *
- * @param name string The name of the requested property.
- * @param onResponse function Called with the request's response.
- */
- getProperty: DebuggerClient.requester({
- type: "property",
- name: arg(0)
- }),
-
- /**
- * Request the prototype of the object.
- *
- * @param onResponse function Called with the request's response.
- */
- getPrototype: DebuggerClient.requester({
- type: "prototype"
- }),
-
- /**
- * Request the display string of the object.
- *
- * @param onResponse function Called with the request's response.
- */
- getDisplayString: DebuggerClient.requester({
- type: "displayString"
- }),
-
- /**
- * Request the scope of the object.
- *
- * @param onResponse function Called with the request's response.
- */
- getScope: DebuggerClient.requester({
- type: "scope"
- }, {
- before: function (packet) {
- if (this._grip.class !== "Function") {
- throw new Error("scope is only valid for function grips.");
- }
- return packet;
- },
- }),
-
- /**
- * Request the promises directly depending on the current promise.
- */
- getDependentPromises: DebuggerClient.requester({
- type: "dependentPromises"
- }, {
- before: function (packet) {
- if (this._grip.class !== "Promise") {
- throw new Error("getDependentPromises is only valid for promise " +
- "grips.");
- }
- return packet;
- }
- }),
-
- /**
- * Request the stack to the promise's allocation point.
- */
- getPromiseAllocationStack: DebuggerClient.requester({
- type: "allocationStack"
- }, {
- before: function (packet) {
- if (this._grip.class !== "Promise") {
- throw new Error("getAllocationStack is only valid for promise grips.");
- }
- return packet;
- }
- }),
-
- /**
- * Request the stack to the promise's fulfillment point.
- */
- getPromiseFulfillmentStack: DebuggerClient.requester({
- type: "fulfillmentStack"
- }, {
- before: function (packet) {
- if (this._grip.class !== "Promise") {
- throw new Error("getPromiseFulfillmentStack is only valid for " +
- "promise grips.");
- }
- return packet;
- }
- }),
-
- /**
- * Request the stack to the promise's rejection point.
- */
- getPromiseRejectionStack: DebuggerClient.requester({
- type: "rejectionStack"
- }, {
- before: function (packet) {
- if (this._grip.class !== "Promise") {
- throw new Error("getPromiseRejectionStack is only valid for " +
- "promise grips.");
- }
- return packet;
- }
- })
-};
-
-/**
- * A PropertyIteratorClient provides a way to access to property names and
- * values of an object efficiently, slice by slice.
- * Note that the properties can be sorted in the backend,
- * this is controled while creating the PropertyIteratorClient
- * from ObjectClient.enumProperties.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param grip Object
- * A PropertyIteratorActor grip returned by the protocol via
- * TabActor.enumProperties request.
- */
-function PropertyIteratorClient(client, grip) {
- this._grip = grip;
- this._client = client;
- this.request = this._client.request;
-}
-
-PropertyIteratorClient.prototype = {
- get actor() {
- return this._grip.actor;
- },
-
- /**
- * Get the total number of properties available in the iterator.
- */
- get count() {
- return this._grip.count;
- },
-
- /**
- * Get one or more property names that correspond to the positions in the
- * indexes parameter.
- *
- * @param indexes Array
- * An array of property indexes.
- * @param callback Function
- * The function called when we receive the property names.
- */
- names: DebuggerClient.requester({
- type: "names",
- indexes: arg(0)
- }, {}),
-
- /**
- * Get a set of following property value(s).
- *
- * @param start Number
- * The index of the first property to fetch.
- * @param count Number
- * The number of properties to fetch.
- * @param callback Function
- * The function called when we receive the property values.
- */
- slice: DebuggerClient.requester({
- type: "slice",
- start: arg(0),
- count: arg(1)
- }, {}),
-
- /**
- * Get all the property values.
- *
- * @param callback Function
- * The function called when we receive the property values.
- */
- all: DebuggerClient.requester({
- type: "all"
- }, {}),
-};
+const {arg, DebuggerClient} = require("./debugger-client");
/**
* A SymbolIteratorClient provides a way to access to symbols
* of an object efficiently, slice by slice.
*
* @param client DebuggerClient
* The debugger client parent.
* @param grip Object
@@ -2902,525 +56,9 @@ SymbolIteratorClient.prototype = {
* @param callback Function
* The function called when we receive the symbols.
*/
all: DebuggerClient.requester({
type: "all"
}, {}),
};
-/**
- * A ArrayBufferClient provides a way to access ArrayBuffer from the
- * debugger server.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param grip Object
- * A pause-lifetime ArrayBuffer grip returned by the protocol.
- */
-function ArrayBufferClient(client, grip) {
- this._grip = grip;
- this._client = client;
- this.request = this._client.request;
-}
-ArrayBufferClient.prototype = {
- get actor() {
- return this._grip.actor;
- },
- get length() {
- return this._grip.length;
- },
- get _transport() {
- return this._client._transport;
- },
-
- valid: true,
-
- slice: DebuggerClient.requester({
- type: "slice",
- start: arg(0),
- count: arg(1)
- }),
-};
-
-/**
- * A LongStringClient provides a way to access "very long" strings from the
- * debugger server.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param grip Object
- * A pause-lifetime long string grip returned by the protocol.
- */
-function LongStringClient(client, grip) {
- this._grip = grip;
- this._client = client;
- this.request = this._client.request;
-}
-exports.LongStringClient = LongStringClient;
-
-LongStringClient.prototype = {
- get actor() {
- return this._grip.actor;
- },
- get length() {
- return this._grip.length;
- },
- get initial() {
- return this._grip.initial;
- },
- get _transport() {
- return this._client._transport;
- },
-
- valid: true,
-
- /**
- * Get the substring of this LongString from start to end.
- *
- * @param start Number
- * The starting index.
- * @param end Number
- * The ending index.
- * @param callback Function
- * The function called when we receive the substring.
- */
- substring: DebuggerClient.requester({
- type: "substring",
- start: arg(0),
- end: arg(1)
- }),
-};
-
-/**
- * A SourceClient provides a way to access the source text of a script.
- *
- * @param client ThreadClient
- * The thread client parent.
- * @param form Object
- * The form sent across the remote debugging protocol.
- */
-function SourceClient(client, form) {
- this._form = form;
- this._isBlackBoxed = form.isBlackBoxed;
- this._isPrettyPrinted = form.isPrettyPrinted;
- this._activeThread = client;
- this._client = client.client;
-}
-
-SourceClient.prototype = {
- get _transport() {
- return this._client._transport;
- },
- get isBlackBoxed() {
- return this._isBlackBoxed;
- },
- get isPrettyPrinted() {
- return this._isPrettyPrinted;
- },
- get actor() {
- return this._form.actor;
- },
- get request() {
- return this._client.request;
- },
- get url() {
- return this._form.url;
- },
-
- /**
- * Black box this SourceClient's source.
- *
- * @param callback Function
- * The callback function called when we receive the response from the server.
- */
- blackBox: DebuggerClient.requester({
- type: "blackbox"
- }, {
- after: function (response) {
- if (!response.error) {
- this._isBlackBoxed = true;
- if (this._activeThread) {
- this._activeThread.emit("blackboxchange", this);
- }
- }
- return response;
- }
- }),
-
- /**
- * Un-black box this SourceClient's source.
- *
- * @param callback Function
- * The callback function called when we receive the response from the server.
- */
- unblackBox: DebuggerClient.requester({
- type: "unblackbox"
- }, {
- after: function (response) {
- if (!response.error) {
- this._isBlackBoxed = false;
- if (this._activeThread) {
- this._activeThread.emit("blackboxchange", this);
- }
- }
- return response;
- }
- }),
-
- /**
- * Get Executable Lines from a source
- *
- * @param callback Function
- * The callback function called when we receive the response from the server.
- */
- getExecutableLines: function (cb = noop) {
- let packet = {
- to: this._form.actor,
- type: "getExecutableLines"
- };
-
- return this._client.request(packet).then(res => {
- cb(res.lines);
- return res.lines;
- });
- },
-
- /**
- * Get a long string grip for this SourceClient's source.
- */
- source: function (callback = noop) {
- let packet = {
- to: this._form.actor,
- type: "source"
- };
- return this._client.request(packet).then(response => {
- return this._onSourceResponse(response, callback);
- });
- },
-
- /**
- * Pretty print this source's text.
- */
- prettyPrint: function (indent, callback = noop) {
- const packet = {
- to: this._form.actor,
- type: "prettyPrint",
- indent
- };
- return this._client.request(packet).then(response => {
- if (!response.error) {
- this._isPrettyPrinted = true;
- this._activeThread._clearFrames();
- this._activeThread.emit("prettyprintchange", this);
- }
- return this._onSourceResponse(response, callback);
- });
- },
-
- /**
- * Stop pretty printing this source's text.
- */
- disablePrettyPrint: function (callback = noop) {
- const packet = {
- to: this._form.actor,
- type: "disablePrettyPrint"
- };
- return this._client.request(packet).then(response => {
- if (!response.error) {
- this._isPrettyPrinted = false;
- this._activeThread._clearFrames();
- this._activeThread.emit("prettyprintchange", this);
- }
- return this._onSourceResponse(response, callback);
- });
- },
-
- _onSourceResponse: function (response, callback) {
- if (response.error) {
- callback(response);
- return response;
- }
-
- if (typeof response.source === "string") {
- callback(response);
- return response;
- }
-
- let { contentType, source } = response;
- if (source.type === "arrayBuffer") {
- let arrayBuffer = this._activeThread.threadArrayBuffer(source);
- return arrayBuffer.slice(0, arrayBuffer.length).then(function (resp) {
- if (resp.error) {
- callback(resp);
- return resp;
- }
- // Keeping str as a string, ArrayBuffer/Uint8Array will not survive
- // setIn/mergeIn operations.
- const str = atob(resp.encoded);
- let newResponse = {
- source: {
- binary: str,
- toString: () => "[wasm]",
- },
- contentType,
- };
- callback(newResponse);
- return newResponse;
- });
- }
-
- let longString = this._activeThread.threadLongString(source);
- return longString.substring(0, longString.length).then(function (resp) {
- if (resp.error) {
- callback(resp);
- return resp;
- }
-
- let newResponse = {
- source: resp.substring,
- contentType: contentType
- };
- callback(newResponse);
- return newResponse;
- });
- },
-
- /**
- * Request to set a breakpoint in the specified location.
- *
- * @param object location
- * The location and condition of the breakpoint in
- * the form of { line[, column, condition] }.
- * @param function onResponse
- * Called with the thread's response.
- */
- setBreakpoint: function ({ line, column, condition, noSliding }, onResponse = noop) {
- // A helper function that sets the breakpoint.
- let doSetBreakpoint = callback => {
- let root = this._client.mainRoot;
- let location = {
- line,
- column,
- };
-
- let packet = {
- to: this.actor,
- type: "setBreakpoint",
- location,
- condition,
- noSliding,
- };
-
- // Backwards compatibility: send the breakpoint request to the
- // thread if the server doesn't support Debugger.Source actors.
- if (!root.traits.debuggerSourceActors) {
- packet.to = this._activeThread.actor;
- packet.location.url = this.url;
- }
-
- return this._client.request(packet).then(response => {
- // Ignoring errors, since the user may be setting a breakpoint in a
- // dead script that will reappear on a page reload.
- let bpClient;
- if (response.actor) {
- bpClient = new BreakpointClient(
- this._client,
- this,
- response.actor,
- location,
- root.traits.conditionalBreakpoints ? condition : undefined
- );
- }
- onResponse(response, bpClient);
- if (callback) {
- callback();
- }
- return [response, bpClient];
- });
- };
-
- // If the debuggee is paused, just set the breakpoint.
- if (this._activeThread.paused) {
- return doSetBreakpoint();
- }
- // Otherwise, force a pause in order to set the breakpoint.
- return this._activeThread.interrupt().then(response => {
- if (response.error) {
- // Can't set the breakpoint if pausing failed.
- onResponse(response);
- return response;
- }
-
- const { type, why } = response;
- const cleanUp = type == "paused" && why.type == "interrupted"
- ? () => this._activeThread.resume()
- : noop;
-
- return doSetBreakpoint(cleanUp);
- });
- }
-};
-
-/**
- * Breakpoint clients are used to remove breakpoints that are no longer used.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param sourceClient SourceClient
- * The source where this breakpoint exists
- * @param actor string
- * The actor ID for this breakpoint.
- * @param location object
- * The location of the breakpoint. This is an object with two properties:
- * url and line.
- * @param condition string
- * The conditional expression of the breakpoint
- */
-function BreakpointClient(client, sourceClient, actor, location, condition) {
- this._client = client;
- this._actor = actor;
- this.location = location;
- this.location.actor = sourceClient.actor;
- this.location.url = sourceClient.url;
- this.source = sourceClient;
- this.request = this._client.request;
-
- // The condition property should only exist if it's a truthy value
- if (condition) {
- this.condition = condition;
- }
-}
-
-BreakpointClient.prototype = {
-
- _actor: null,
- get actor() {
- return this._actor;
- },
- get _transport() {
- return this._client._transport;
- },
-
- /**
- * Remove the breakpoint from the server.
- */
- remove: DebuggerClient.requester({
- type: "delete"
- }),
-
- /**
- * Determines if this breakpoint has a condition
- */
- hasCondition: function () {
- let root = this._client.mainRoot;
- // XXX bug 990137: We will remove support for client-side handling of
- // conditional breakpoints
- if (root.traits.conditionalBreakpoints) {
- return "condition" in this;
- }
- return "conditionalExpression" in this;
- },
-
- /**
- * Get the condition of this breakpoint. Currently we have to
- * support locally emulated conditional breakpoints until the
- * debugger servers are updated (see bug 990137). We used a
- * different property when moving it server-side to ensure that we
- * are testing the right code.
- */
- getCondition: function () {
- let root = this._client.mainRoot;
- if (root.traits.conditionalBreakpoints) {
- return this.condition;
- }
- return this.conditionalExpression;
- },
-
- /**
- * Set the condition of this breakpoint
- */
- setCondition: function (gThreadClient, condition) {
- let root = this._client.mainRoot;
- let deferred = promise.defer();
-
- if (root.traits.conditionalBreakpoints) {
- let info = {
- line: this.location.line,
- column: this.location.column,
- condition: condition
- };
-
- // Remove the current breakpoint and add a new one with the
- // condition.
- this.remove(response => {
- if (response && response.error) {
- deferred.reject(response);
- return;
- }
-
- this.source.setBreakpoint(info, (resp, newBreakpoint) => {
- if (resp && resp.error) {
- deferred.reject(resp);
- } else {
- deferred.resolve(newBreakpoint);
- }
- });
- });
- } else {
- // The property shouldn't even exist if the condition is blank
- if (condition === "") {
- delete this.conditionalExpression;
- } else {
- this.conditionalExpression = condition;
- }
- deferred.resolve(this);
- }
-
- return deferred.promise;
- }
-};
-
-eventSource(BreakpointClient.prototype);
-
-/**
- * Environment clients are used to manipulate the lexical environment actors.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param form Object
- * The form sent across the remote debugging protocol.
- */
-function EnvironmentClient(client, form) {
- this._client = client;
- this._form = form;
- this.request = this._client.request;
-}
-exports.EnvironmentClient = EnvironmentClient;
-
-EnvironmentClient.prototype = {
-
- get actor() {
- return this._form.actor;
- },
- get _transport() {
- return this._client._transport;
- },
-
- /**
- * Fetches the bindings introduced by this lexical environment.
- */
- getBindings: DebuggerClient.requester({
- type: "bindings"
- }),
-
- /**
- * Changes the value of the identifier whose name is name (a string) to that
- * represented by value (a grip).
- */
- assign: DebuggerClient.requester({
- type: "assign",
- name: arg(0),
- value: arg(1)
- })
-};
-
-eventSource(EnvironmentClient.prototype);
+module.exports = SymbolIteratorClient;
copy from devtools/shared/client/main.js
copy to devtools/shared/client/tab-client.js
--- a/devtools/shared/client/main.js
+++ b/devtools/shared/client/tab-client.js
@@ -1,1332 +1,25 @@
-/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
-/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
-const { Ci, Cu } = require("chrome");
-const DevToolsUtils = require("devtools/shared/DevToolsUtils");
-const { getStack, callFunctionWithAsyncStack } = require("devtools/shared/platform/stack");
-
+const { Cu } = require("chrome");
const promise = Cu.import("resource://devtools/shared/deprecated-sync-thenables.js", {}).Promise;
-loader.lazyRequireGetter(this, "EventEmitter", "devtools/shared/event-emitter");
-loader.lazyRequireGetter(this, "WebConsoleClient", "devtools/shared/webconsole/client", true);
-loader.lazyRequireGetter(this, "DebuggerSocket", "devtools/shared/security/socket", true);
-loader.lazyRequireGetter(this, "Authentication", "devtools/shared/security/auth");
+const DevToolsUtils = require("devtools/shared/DevToolsUtils");
+const eventSource = require("./event-source");
+const {arg, DebuggerClient} = require("./debugger-client");
+loader.lazyRequireGetter(this, "ThreadClient", "devtools/shared/client/thread-client");
const noop = () => {};
/**
- * TODO: Get rid of this API in favor of EventTarget (bug 1042642)
- *
- * Add simple event notification to a prototype object. Any object that has
- * some use for event notifications or the observer pattern in general can be
- * augmented with the necessary facilities by passing its prototype to this
- * function.
- *
- * @param proto object
- * The prototype object that will be modified.
- */
-function eventSource(proto) {
- /**
- * Add a listener to the event source for a given event.
- *
- * @param name string
- * The event to listen for.
- * @param listener function
- * Called when the event is fired. If the same listener
- * is added more than once, it will be called once per
- * addListener call.
- */
- proto.addListener = function (name, listener) {
- if (typeof listener != "function") {
- throw TypeError("Listeners must be functions.");
- }
-
- if (!this._listeners) {
- this._listeners = {};
- }
-
- this._getListeners(name).push(listener);
- };
-
- /**
- * Add a listener to the event source for a given event. The
- * listener will be removed after it is called for the first time.
- *
- * @param name string
- * The event to listen for.
- * @param listener function
- * Called when the event is fired.
- */
- proto.addOneTimeListener = function (name, listener) {
- let l = (...args) => {
- this.removeListener(name, l);
- listener.apply(null, args);
- };
- this.addListener(name, l);
- };
-
- /**
- * Remove a listener from the event source previously added with
- * addListener().
- *
- * @param name string
- * The event name used during addListener to add the listener.
- * @param listener function
- * The callback to remove. If addListener was called multiple
- * times, all instances will be removed.
- */
- proto.removeListener = function (name, listener) {
- if (!this._listeners || (listener && !this._listeners[name])) {
- return;
- }
-
- if (!listener) {
- this._listeners[name] = [];
- } else {
- this._listeners[name] =
- this._listeners[name].filter(l => l != listener);
- }
- };
-
- /**
- * Returns the listeners for the specified event name. If none are defined it
- * initializes an empty list and returns that.
- *
- * @param name string
- * The event name.
- */
- proto._getListeners = function (name) {
- if (name in this._listeners) {
- return this._listeners[name];
- }
- this._listeners[name] = [];
- return this._listeners[name];
- };
-
- /**
- * Notify listeners of an event.
- *
- * @param name string
- * The event to fire.
- * @param arguments
- * All arguments will be passed along to the listeners,
- * including the name argument.
- */
- proto.emit = function () {
- if (!this._listeners) {
- return;
- }
-
- let name = arguments[0];
- let listeners = this._getListeners(name).slice(0);
-
- for (let listener of listeners) {
- try {
- listener.apply(null, arguments);
- } catch (e) {
- // Prevent a bad listener from interfering with the others.
- DevToolsUtils.reportException("notify event '" + name + "'", e);
- }
- }
- };
-}
-
-/**
- * Set of protocol messages that affect thread state, and the
- * state the actor is in after each message.
- */
-const ThreadStateTypes = {
- "paused": "paused",
- "resumed": "attached",
- "detached": "detached",
- "running": "attached"
-};
-
-/**
- * Set of protocol messages that are sent by the server without a prior request
- * by the client.
- */
-const UnsolicitedNotifications = {
- "consoleAPICall": "consoleAPICall",
- "eventNotification": "eventNotification",
- "fileActivity": "fileActivity",
- "lastPrivateContextExited": "lastPrivateContextExited",
- "logMessage": "logMessage",
- "networkEvent": "networkEvent",
- "networkEventUpdate": "networkEventUpdate",
- "newGlobal": "newGlobal",
- "newScript": "newScript",
- "tabDetached": "tabDetached",
- "tabListChanged": "tabListChanged",
- "reflowActivity": "reflowActivity",
- "addonListChanged": "addonListChanged",
- "workerListChanged": "workerListChanged",
- "serviceWorkerRegistrationListChanged": "serviceWorkerRegistrationList",
- "tabNavigated": "tabNavigated",
- "frameUpdate": "frameUpdate",
- "pageError": "pageError",
- "documentLoad": "documentLoad",
- "enteredFrame": "enteredFrame",
- "exitedFrame": "exitedFrame",
- "appOpen": "appOpen",
- "appClose": "appClose",
- "appInstall": "appInstall",
- "appUninstall": "appUninstall",
- "evaluationResult": "evaluationResult",
- "newSource": "newSource",
- "updatedSource": "updatedSource",
- "inspectObject": "inspectObject"
-};
-
-/**
- * Set of pause types that are sent by the server and not as an immediate
- * response to a client request.
- */
-const UnsolicitedPauses = {
- "resumeLimit": "resumeLimit",
- "debuggerStatement": "debuggerStatement",
- "breakpoint": "breakpoint",
- "DOMEvent": "DOMEvent",
- "watchpoint": "watchpoint",
- "exception": "exception"
-};
-
-/**
- * Creates a client for the remote debugging protocol server. This client
- * provides the means to communicate with the server and exchange the messages
- * required by the protocol in a traditional JavaScript API.
- */
-const DebuggerClient = exports.DebuggerClient = function (transport) {
- this._transport = transport;
- this._transport.hooks = this;
-
- // Map actor ID to client instance for each actor type.
- this._clients = new Map();
-
- this._pendingRequests = new Map();
- this._activeRequests = new Map();
- this._eventsEnabled = true;
-
- this.traits = {};
-
- this.request = this.request.bind(this);
- this.localTransport = this._transport.onOutputStreamReady === undefined;
-
- /*
- * As the first thing on the connection, expect a greeting packet from
- * the connection's root actor.
- */
- this.mainRoot = null;
- this.expectReply("root", (packet) => {
- this.mainRoot = new RootClient(this, packet);
- this.emit("connected", packet.applicationType, packet.traits);
- });
-};
-
-/**
- * A declarative helper for defining methods that send requests to the server.
- *
- * @param packetSkeleton
- * The form of the packet to send. Can specify fields to be filled from
- * the parameters by using the |arg| function.
- * @param before
- * The function to call before sending the packet. Is passed the packet,
- * and the return value is used as the new packet. The |this| context is
- * the instance of the client object we are defining a method for.
- * @param after
- * The function to call after the response is received. It is passed the
- * response, and the return value is considered the new response that
- * will be passed to the callback. The |this| context is the instance of
- * the client object we are defining a method for.
- * @return Request
- * The `Request` object that is a Promise object and resolves once
- * we receive the response. (See request method for more details)
- */
-DebuggerClient.requester = function (packetSkeleton, config = {}) {
- let { before, after } = config;
- return DevToolsUtils.makeInfallible(function (...args) {
- let outgoingPacket = {
- to: packetSkeleton.to || this.actor
- };
-
- let maxPosition = -1;
- for (let k of Object.keys(packetSkeleton)) {
- if (packetSkeleton[k] instanceof DebuggerClient.Argument) {
- let { position } = packetSkeleton[k];
- outgoingPacket[k] = packetSkeleton[k].getArgument(args);
- maxPosition = Math.max(position, maxPosition);
- } else {
- outgoingPacket[k] = packetSkeleton[k];
- }
- }
-
- if (before) {
- outgoingPacket = before.call(this, outgoingPacket);
- }
-
- return this.request(outgoingPacket, DevToolsUtils.makeInfallible((response) => {
- if (after) {
- let { from } = response;
- response = after.call(this, response);
- if (!response.from) {
- response.from = from;
- }
- }
-
- // The callback is always the last parameter.
- let thisCallback = args[maxPosition + 1];
- if (thisCallback) {
- thisCallback(response);
- }
- return response;
- }, "DebuggerClient.requester request callback"));
- }, "DebuggerClient.requester");
-};
-
-function arg(pos) {
- return new DebuggerClient.Argument(pos);
-}
-
-DebuggerClient.Argument = function (position) {
- this.position = position;
-};
-
-DebuggerClient.Argument.prototype.getArgument = function (params) {
- if (!(this.position in params)) {
- throw new Error("Bad index into params: " + this.position);
- }
- return params[this.position];
-};
-
-// Expose these to save callers the trouble of importing DebuggerSocket
-DebuggerClient.socketConnect = function (options) {
- // Defined here instead of just copying the function to allow lazy-load
- return DebuggerSocket.connect(options);
-};
-DevToolsUtils.defineLazyGetter(DebuggerClient, "Authenticators", () => {
- return Authentication.Authenticators;
-});
-DevToolsUtils.defineLazyGetter(DebuggerClient, "AuthenticationResult", () => {
- return Authentication.AuthenticationResult;
-});
-
-DebuggerClient.prototype = {
- /**
- * Connect to the server and start exchanging protocol messages.
- *
- * @param onConnected function
- * If specified, will be called when the greeting packet is
- * received from the debugging server.
- *
- * @return Promise
- * Resolves once connected with an array whose first element
- * is the application type, by default "browser", and the second
- * element is the traits object (help figure out the features
- * and behaviors of the server we connect to. See RootActor).
- */
- connect: function (onConnected) {
- let deferred = promise.defer();
- this.emit("connect");
-
- // Also emit the event on the |DebuggerClient| object (not on the instance),
- // so it's possible to track all instances.
- EventEmitter.emit(DebuggerClient, "connect", this);
-
- this.addOneTimeListener("connected", (name, applicationType, traits) => {
- this.traits = traits;
- if (onConnected) {
- onConnected(applicationType, traits);
- }
- deferred.resolve([applicationType, traits]);
- });
-
- this._transport.ready();
- return deferred.promise;
- },
-
- /**
- * Shut down communication with the debugging server.
- *
- * @param onClosed function
- * If specified, will be called when the debugging connection
- * has been closed. This parameter is deprecated - please use
- * the returned Promise.
- * @return Promise
- * Resolves after the underlying transport is closed.
- */
- close: function (onClosed) {
- let deferred = promise.defer();
- if (onClosed) {
- deferred.promise.then(onClosed);
- }
-
- // Disable detach event notifications, because event handlers will be in a
- // cleared scope by the time they run.
- this._eventsEnabled = false;
-
- let cleanup = () => {
- this._transport.close();
- this._transport = null;
- };
-
- // If the connection is already closed,
- // there is no need to detach client
- // as we won't be able to send any message.
- if (this._closed) {
- cleanup();
- deferred.resolve();
- return deferred.promise;
- }
-
- this.addOneTimeListener("closed", deferred.resolve);
-
- // Call each client's `detach` method by calling
- // lastly registered ones first to give a chance
- // to detach child clients first.
- let clients = [...this._clients.values()];
- this._clients.clear();
- const detachClients = () => {
- let client = clients.pop();
- if (!client) {
- // All clients detached.
- cleanup();
- return;
- }
- if (client.detach) {
- client.detach(detachClients);
- return;
- }
- detachClients();
- };
- detachClients();
-
- return deferred.promise;
- },
-
- /*
- * This function exists only to preserve DebuggerClient's interface;
- * new code should say 'client.mainRoot.listTabs()'.
- */
- listTabs: function (onResponse) {
- return this.mainRoot.listTabs(onResponse);
- },
-
- /*
- * This function exists only to preserve DebuggerClient's interface;
- * new code should say 'client.mainRoot.listAddons()'.
- */
- listAddons: function (onResponse) {
- return this.mainRoot.listAddons(onResponse);
- },
-
- getTab: function (filter) {
- return this.mainRoot.getTab(filter);
- },
-
- /**
- * Attach to a tab actor.
- *
- * @param string tabActor
- * The actor ID for the tab to attach.
- * @param function onResponse
- * Called with the response packet and a TabClient
- * (which will be undefined on error).
- */
- attachTab: function (tabActor, onResponse = noop) {
- if (this._clients.has(tabActor)) {
- let cachedTab = this._clients.get(tabActor);
- let cachedResponse = {
- cacheDisabled: cachedTab.cacheDisabled,
- javascriptEnabled: cachedTab.javascriptEnabled,
- traits: cachedTab.traits,
- };
- DevToolsUtils.executeSoon(() => onResponse(cachedResponse, cachedTab));
- return promise.resolve([cachedResponse, cachedTab]);
- }
-
- let packet = {
- to: tabActor,
- type: "attach"
- };
- return this.request(packet).then(response => {
- let tabClient;
- if (!response.error) {
- tabClient = new TabClient(this, response);
- this.registerClient(tabClient);
- }
- onResponse(response, tabClient);
- return [response, tabClient];
- });
- },
-
- attachWorker: function (workerActor, onResponse = noop) {
- let workerClient = this._clients.get(workerActor);
- if (workerClient !== undefined) {
- let response = {
- from: workerClient.actor,
- type: "attached",
- url: workerClient.url
- };
- DevToolsUtils.executeSoon(() => onResponse(response, workerClient));
- return promise.resolve([response, workerClient]);
- }
-
- return this.request({ to: workerActor, type: "attach" }).then(response => {
- if (response.error) {
- onResponse(response, null);
- return [response, null];
- }
-
- workerClient = new WorkerClient(this, response);
- this.registerClient(workerClient);
- onResponse(response, workerClient);
- return [response, workerClient];
- });
- },
-
- /**
- * Attach to an addon actor.
- *
- * @param string addonActor
- * The actor ID for the addon to attach.
- * @param function onResponse
- * Called with the response packet and a AddonClient
- * (which will be undefined on error).
- */
- attachAddon: function (addonActor, onResponse = noop) {
- let packet = {
- to: addonActor,
- type: "attach"
- };
- return this.request(packet).then(response => {
- let addonClient;
- if (!response.error) {
- addonClient = new AddonClient(this, addonActor);
- this.registerClient(addonClient);
- this.activeAddon = addonClient;
- }
- onResponse(response, addonClient);
- return [response, addonClient];
- });
- },
-
- /**
- * Attach to a Web Console actor.
- *
- * @param string consoleActor
- * The ID for the console actor to attach to.
- * @param array listeners
- * The console listeners you want to start.
- * @param function onResponse
- * Called with the response packet and a WebConsoleClient
- * instance (which will be undefined on error).
- */
- attachConsole:
- function (consoleActor, listeners, onResponse = noop) {
- let packet = {
- to: consoleActor,
- type: "startListeners",
- listeners: listeners,
- };
-
- return this.request(packet).then(response => {
- let consoleClient;
- if (!response.error) {
- if (this._clients.has(consoleActor)) {
- consoleClient = this._clients.get(consoleActor);
- } else {
- consoleClient = new WebConsoleClient(this, response);
- this.registerClient(consoleClient);
- }
- }
- onResponse(response, consoleClient);
- return [response, consoleClient];
- });
- },
-
- /**
- * Attach to a global-scoped thread actor for chrome debugging.
- *
- * @param string threadActor
- * The actor ID for the thread to attach.
- * @param function onResponse
- * Called with the response packet and a ThreadClient
- * (which will be undefined on error).
- * @param object options
- * Configuration options.
- * - useSourceMaps: whether to use source maps or not.
- */
- attachThread: function (threadActor, onResponse = noop, options = {}) {
- if (this._clients.has(threadActor)) {
- let client = this._clients.get(threadActor);
- DevToolsUtils.executeSoon(() => onResponse({}, client));
- return promise.resolve([{}, client]);
- }
-
- let packet = {
- to: threadActor,
- type: "attach",
- options,
- };
- return this.request(packet).then(response => {
- let threadClient;
- if (!response.error) {
- threadClient = new ThreadClient(this, threadActor);
- this.registerClient(threadClient);
- }
- onResponse(response, threadClient);
- return [response, threadClient];
- });
- },
-
- /**
- * Attach to a trace actor.
- *
- * @param string traceActor
- * The actor ID for the tracer to attach.
- * @param function onResponse
- * Called with the response packet and a TraceClient
- * (which will be undefined on error).
- */
- attachTracer: function (traceActor, onResponse = noop) {
- if (this._clients.has(traceActor)) {
- let client = this._clients.get(traceActor);
- DevToolsUtils.executeSoon(() => onResponse({}, client));
- return promise.resolve([{}, client]);
- }
-
- let packet = {
- to: traceActor,
- type: "attach"
- };
- return this.request(packet).then(response => {
- let traceClient;
- if (!response.error) {
- traceClient = new TraceClient(this, traceActor);
- this.registerClient(traceClient);
- }
- onResponse(response, traceClient);
- return [response, traceClient];
- });
- },
-
- /**
- * Fetch the ChromeActor for the main process or ChildProcessActor for a
- * a given child process ID.
- *
- * @param number id
- * The ID for the process to attach (returned by `listProcesses`).
- * Connected to the main process if omitted, or is 0.
- */
- getProcess: function (id) {
- let packet = {
- to: "root",
- type: "getProcess"
- };
- if (typeof (id) == "number") {
- packet.id = id;
- }
- return this.request(packet);
- },
-
- /**
- * Release an object actor.
- *
- * @param string actor
- * The actor ID to send the request to.
- * @param onResponse function
- * If specified, will be called with the response packet when
- * debugging server responds.
- */
- release: DebuggerClient.requester({
- to: arg(0),
- type: "release"
- }),
-
- /**
- * Send a request to the debugging server.
- *
- * @param packet object
- * A JSON packet to send to the debugging server.
- * @param onResponse function
- * If specified, will be called with the JSON response packet when
- * debugging server responds.
- * @return Request
- * This object emits a number of events to allow you to respond to
- * different parts of the request lifecycle.
- * It is also a Promise object, with a `then` method, that is resolved
- * whenever a JSON or a Bulk response is received; and is rejected
- * if the response is an error.
- * Note: This return value can be ignored if you are using JSON alone,
- * because the callback provided in |onResponse| will be bound to the
- * "json-reply" event automatically.
- *
- * Events emitted:
- * * json-reply: The server replied with a JSON packet, which is
- * passed as event data.
- * * bulk-reply: The server replied with bulk data, which you can read
- * using the event data object containing:
- * * actor: Name of actor that received the packet
- * * type: Name of actor's method that was called on receipt
- * * length: Size of the data to be read
- * * stream: This input stream should only be used directly if you
- * can ensure that you will read exactly |length| bytes
- * and will not close the stream when reading is complete
- * * done: If you use the stream directly (instead of |copyTo|
- * below), you must signal completion by resolving /
- * rejecting this deferred. If it's rejected, the
- * transport will be closed. If an Error is supplied as a
- * rejection value, it will be logged via |dumpn|. If you
- * do use |copyTo|, resolving is taken care of for you
- * when copying completes.
- * * copyTo: A helper function for getting your data out of the
- * stream that meets the stream handling requirements
- * above, and has the following signature:
- * @param output nsIAsyncOutputStream
- * The stream to copy to.
- * @return Promise
- * The promise is resolved when copying completes or
- * rejected if any (unexpected) errors occur.
- * This object also emits "progress" events for each chunk
- * that is copied. See stream-utils.js.
- */
- request: function (packet, onResponse) {
- if (!this.mainRoot) {
- throw Error("Have not yet received a hello packet from the server.");
- }
- let type = packet.type || "";
- if (!packet.to) {
- throw Error("'" + type + "' request packet has no destination.");
- }
-
- // The onResponse callback might modify the response, so we need to call
- // it and resolve the promise with its result if it's truthy.
- const safeOnResponse = response => {
- if (!onResponse) {
- return response;
- }
- return onResponse(response) || response;
- };
-
- if (this._closed) {
- let msg = "'" + type + "' request packet to " +
- "'" + packet.to + "' " +
- "can't be sent as the connection is closed.";
- let resp = { error: "connectionClosed", message: msg };
- return promise.reject(safeOnResponse(resp));
- }
-
- let request = new Request(packet);
- request.format = "json";
- request.stack = getStack();
-
- // Implement a Promise like API on the returned object
- // that resolves/rejects on request response
- let deferred = promise.defer();
- function listenerJson(resp) {
- removeRequestListeners();
- if (resp.error) {
- deferred.reject(safeOnResponse(resp));
- } else {
- deferred.resolve(safeOnResponse(resp));
- }
- }
- function listenerBulk(resp) {
- removeRequestListeners();
- deferred.resolve(safeOnResponse(resp));
- }
-
- const removeRequestListeners = () => {
- request.off("json-reply", listenerJson);
- request.off("bulk-reply", listenerBulk);
- };
-
- request.on("json-reply", listenerJson);
- request.on("bulk-reply", listenerBulk);
-
- this._sendOrQueueRequest(request);
- request.then = deferred.promise.then.bind(deferred.promise);
-
- return request;
- },
-
- /**
- * Transmit streaming data via a bulk request.
- *
- * This method initiates the bulk send process by queuing up the header data.
- * The caller receives eventual access to a stream for writing.
- *
- * Since this opens up more options for how the server might respond (it could
- * send back either JSON or bulk data), and the returned Request object emits
- * events for different stages of the request process that you may want to
- * react to.
- *
- * @param request Object
- * This is modeled after the format of JSON packets above, but does not
- * actually contain the data, but is instead just a routing header:
- * * actor: Name of actor that will receive the packet
- * * type: Name of actor's method that should be called on receipt
- * * length: Size of the data to be sent
- * @return Request
- * This object emits a number of events to allow you to respond to
- * different parts of the request lifecycle.
- *
- * Events emitted:
- * * bulk-send-ready: Ready to send bulk data to the server, using the
- * event data object containing:
- * * stream: This output stream should only be used directly if
- * you can ensure that you will write exactly |length|
- * bytes and will not close the stream when writing is
- * complete
- * * done: If you use the stream directly (instead of |copyFrom|
- * below), you must signal completion by resolving /
- * rejecting this deferred. If it's rejected, the
- * transport will be closed. If an Error is supplied as
- * a rejection value, it will be logged via |dumpn|. If
- * you do use |copyFrom|, resolving is taken care of for
- * you when copying completes.
- * * copyFrom: A helper function for getting your data onto the
- * stream that meets the stream handling requirements
- * above, and has the following signature:
- * @param input nsIAsyncInputStream
- * The stream to copy from.
- * @return Promise
- * The promise is resolved when copying completes or
- * rejected if any (unexpected) errors occur.
- * This object also emits "progress" events for each chunk
- * that is copied. See stream-utils.js.
- * * json-reply: The server replied with a JSON packet, which is
- * passed as event data.
- * * bulk-reply: The server replied with bulk data, which you can read
- * using the event data object containing:
- * * actor: Name of actor that received the packet
- * * type: Name of actor's method that was called on receipt
- * * length: Size of the data to be read
- * * stream: This input stream should only be used directly if you
- * can ensure that you will read exactly |length| bytes
- * and will not close the stream when reading is complete
- * * done: If you use the stream directly (instead of |copyTo|
- * below), you must signal completion by resolving /
- * rejecting this deferred. If it's rejected, the
- * transport will be closed. If an Error is supplied as a
- * rejection value, it will be logged via |dumpn|. If you
- * do use |copyTo|, resolving is taken care of for you
- * when copying completes.
- * * copyTo: A helper function for getting your data out of the
- * stream that meets the stream handling requirements
- * above, and has the following signature:
- * @param output nsIAsyncOutputStream
- * The stream to copy to.
- * @return Promise
- * The promise is resolved when copying completes or
- * rejected if any (unexpected) errors occur.
- * This object also emits "progress" events for each chunk
- * that is copied. See stream-utils.js.
- */
- startBulkRequest: function (request) {
- if (!this.traits.bulk) {
- throw Error("Server doesn't support bulk transfers");
- }
- if (!this.mainRoot) {
- throw Error("Have not yet received a hello packet from the server.");
- }
- if (!request.type) {
- throw Error("Bulk packet is missing the required 'type' field.");
- }
- if (!request.actor) {
- throw Error("'" + request.type + "' bulk packet has no destination.");
- }
- if (!request.length) {
- throw Error("'" + request.type + "' bulk packet has no length.");
- }
-
- request = new Request(request);
- request.format = "bulk";
-
- this._sendOrQueueRequest(request);
-
- return request;
- },
-
- /**
- * If a new request can be sent immediately, do so. Otherwise, queue it.
- */
- _sendOrQueueRequest(request) {
- let actor = request.actor;
- if (!this._activeRequests.has(actor)) {
- this._sendRequest(request);
- } else {
- this._queueRequest(request);
- }
- },
-
- /**
- * Send a request.
- * @throws Error if there is already an active request in flight for the same
- * actor.
- */
- _sendRequest(request) {
- let actor = request.actor;
- this.expectReply(actor, request);
-
- if (request.format === "json") {
- this._transport.send(request.request);
- return;
- }
-
- this._transport.startBulkSend(request.request).then((...args) => {
- request.emit("bulk-send-ready", ...args);
- });
- },
-
- /**
- * Queue a request to be sent later. Queues are only drained when an in
- * flight request to a given actor completes.
- */
- _queueRequest(request) {
- let actor = request.actor;
- let queue = this._pendingRequests.get(actor) || [];
- queue.push(request);
- this._pendingRequests.set(actor, queue);
- },
-
- /**
- * Attempt the next request to a given actor (if any).
- */
- _attemptNextRequest(actor) {
- if (this._activeRequests.has(actor)) {
- return;
- }
- let queue = this._pendingRequests.get(actor);
- if (!queue) {
- return;
- }
- let request = queue.shift();
- if (queue.length === 0) {
- this._pendingRequests.delete(actor);
- }
- this._sendRequest(request);
- },
-
- /**
- * Arrange to hand the next reply from |actor| to the handler bound to
- * |request|.
- *
- * DebuggerClient.prototype.request / startBulkRequest usually takes care of
- * establishing the handler for a given request, but in rare cases (well,
- * greetings from new root actors, is the only case at the moment) we must be
- * prepared for a "reply" that doesn't correspond to any request we sent.
- */
- expectReply: function (actor, request) {
- if (this._activeRequests.has(actor)) {
- throw Error("clashing handlers for next reply from " + actor);
- }
-
- // If a handler is passed directly (as it is with the handler for the root
- // actor greeting), create a dummy request to bind this to.
- if (typeof request === "function") {
- let handler = request;
- request = new Request();
- request.on("json-reply", handler);
- }
-
- this._activeRequests.set(actor, request);
- },
-
- // Transport hooks.
-
- /**
- * Called by DebuggerTransport to dispatch incoming packets as appropriate.
- *
- * @param packet object
- * The incoming packet.
- */
- onPacket: function (packet) {
- if (!packet.from) {
- DevToolsUtils.reportException(
- "onPacket",
- new Error("Server did not specify an actor, dropping packet: " +
- JSON.stringify(packet)));
- return;
- }
-
- // If we have a registered Front for this actor, let it handle the packet
- // and skip all the rest of this unpleasantness.
- let front = this.getActor(packet.from);
- if (front) {
- front.onPacket(packet);
- return;
- }
-
- // Check for "forwardingCancelled" here instead of using a client to handle it.
- // This is necessary because we might receive this event while the client is closing,
- // and the clients have already been removed by that point.
- if (this.mainRoot &&
- packet.from == this.mainRoot.actor &&
- packet.type == "forwardingCancelled") {
- this.purgeRequests(packet.prefix);
- return;
- }
-
- if (this._clients.has(packet.from) && packet.type) {
- let client = this._clients.get(packet.from);
- let type = packet.type;
- if (client.events.indexOf(type) != -1) {
- client.emit(type, packet);
- // we ignore the rest, as the client is expected to handle this packet.
- return;
- }
- }
-
- let activeRequest;
- // See if we have a handler function waiting for a reply from this
- // actor. (Don't count unsolicited notifications or pauses as
- // replies.)
- if (this._activeRequests.has(packet.from) &&
- !(packet.type in UnsolicitedNotifications) &&
- !(packet.type == ThreadStateTypes.paused &&
- packet.why.type in UnsolicitedPauses)) {
- activeRequest = this._activeRequests.get(packet.from);
- this._activeRequests.delete(packet.from);
- }
-
- // If there is a subsequent request for the same actor, hand it off to the
- // transport. Delivery of packets on the other end is always async, even
- // in the local transport case.
- this._attemptNextRequest(packet.from);
-
- // Packets that indicate thread state changes get special treatment.
- if (packet.type in ThreadStateTypes &&
- this._clients.has(packet.from) &&
- typeof this._clients.get(packet.from)._onThreadState == "function") {
- this._clients.get(packet.from)._onThreadState(packet);
- }
-
- // TODO: Bug 1151156 - Remove once Gecko 40 is on b2g-stable.
- if (!this.traits.noNeedToFakeResumptionOnNavigation) {
- // On navigation the server resumes, so the client must resume as well.
- // We achieve that by generating a fake resumption packet that triggers
- // the client's thread state change listeners.
- if (packet.type == UnsolicitedNotifications.tabNavigated &&
- this._clients.has(packet.from) &&
- this._clients.get(packet.from).thread) {
- let thread = this._clients.get(packet.from).thread;
- let resumption = { from: thread._actor, type: "resumed" };
- thread._onThreadState(resumption);
- }
- }
-
- // Only try to notify listeners on events, not responses to requests
- // that lack a packet type.
- if (packet.type) {
- this.emit(packet.type, packet);
- }
-
- if (activeRequest) {
- let emitReply = () => activeRequest.emit("json-reply", packet);
- if (activeRequest.stack) {
- callFunctionWithAsyncStack(emitReply, activeRequest.stack,
- "DevTools RDP");
- } else {
- emitReply();
- }
- }
- },
-
- /**
- * Called by the DebuggerTransport to dispatch incoming bulk packets as
- * appropriate.
- *
- * @param packet object
- * The incoming packet, which contains:
- * * actor: Name of actor that will receive the packet
- * * type: Name of actor's method that should be called on receipt
- * * length: Size of the data to be read
- * * stream: This input stream should only be used directly if you can
- * ensure that you will read exactly |length| bytes and will
- * not close the stream when reading is complete
- * * done: If you use the stream directly (instead of |copyTo|
- * below), you must signal completion by resolving /
- * rejecting this deferred. If it's rejected, the transport
- * will be closed. If an Error is supplied as a rejection
- * value, it will be logged via |dumpn|. If you do use
- * |copyTo|, resolving is taken care of for you when copying
- * completes.
- * * copyTo: A helper function for getting your data out of the stream
- * that meets the stream handling requirements above, and has
- * the following signature:
- * @param output nsIAsyncOutputStream
- * The stream to copy to.
- * @return Promise
- * The promise is resolved when copying completes or rejected
- * if any (unexpected) errors occur.
- * This object also emits "progress" events for each chunk
- * that is copied. See stream-utils.js.
- */
- onBulkPacket: function (packet) {
- let { actor } = packet;
-
- if (!actor) {
- DevToolsUtils.reportException(
- "onBulkPacket",
- new Error("Server did not specify an actor, dropping bulk packet: " +
- JSON.stringify(packet)));
- return;
- }
-
- // See if we have a handler function waiting for a reply from this
- // actor.
- if (!this._activeRequests.has(actor)) {
- return;
- }
-
- let activeRequest = this._activeRequests.get(actor);
- this._activeRequests.delete(actor);
-
- // If there is a subsequent request for the same actor, hand it off to the
- // transport. Delivery of packets on the other end is always async, even
- // in the local transport case.
- this._attemptNextRequest(actor);
-
- activeRequest.emit("bulk-reply", packet);
- },
-
- /**
- * Called by DebuggerTransport when the underlying stream is closed.
- *
- * @param status nsresult
- * The status code that corresponds to the reason for closing
- * the stream.
- */
- onClosed: function () {
- this._closed = true;
- this.emit("closed");
-
- this.purgeRequests();
-
- // The |_pools| array on the client-side currently is used only by
- // protocol.js to store active fronts, mirroring the actor pools found in
- // the server. So, read all usages of "pool" as "protocol.js front".
- //
- // In the normal case where we shutdown cleanly, the toolbox tells each tool
- // to close, and they each call |destroy| on any fronts they were using.
- // When |destroy| or |cleanup| is called on a protocol.js front, it also
- // removes itself from the |_pools| array. Once the toolbox has shutdown,
- // the connection is closed, and we reach here. All fronts (should have
- // been) |destroy|ed, so |_pools| should empty.
- //
- // If the connection instead aborts unexpectedly, we may end up here with
- // all fronts used during the life of the connection. So, we call |cleanup|
- // on them clear their state, reject pending requests, and remove themselves
- // from |_pools|. This saves the toolbox from hanging indefinitely, in case
- // it waits for some server response before shutdown that will now never
- // arrive.
- for (let pool of this._pools) {
- pool.cleanup();
- }
- },
-
- /**
- * Purge pending and active requests in this client.
- *
- * @param prefix string (optional)
- * If a prefix is given, only requests for actor IDs that start with the prefix
- * will be cleaned up. This is useful when forwarding of a portion of requests
- * is cancelled on the server.
- */
- purgeRequests(prefix = "") {
- let reject = function (type, request) {
- // Server can send packets on its own and client only pass a callback
- // to expectReply, so that there is no request object.
- let msg;
- if (request.request) {
- msg = "'" + request.request.type + "' " + type + " request packet" +
- " to '" + request.actor + "' " +
- "can't be sent as the connection just closed.";
- } else {
- msg = "server side packet can't be received as the connection just closed.";
- }
- let packet = { error: "connectionClosed", message: msg };
- request.emit("json-reply", packet);
- };
-
- let pendingRequestsToReject = [];
- this._pendingRequests.forEach((requests, actor) => {
- if (!actor.startsWith(prefix)) {
- return;
- }
- this._pendingRequests.delete(actor);
- pendingRequestsToReject = pendingRequestsToReject.concat(requests);
- });
- pendingRequestsToReject.forEach(request => reject("pending", request));
-
- let activeRequestsToReject = [];
- this._activeRequests.forEach((request, actor) => {
- if (!actor.startsWith(prefix)) {
- return;
- }
- this._activeRequests.delete(actor);
- activeRequestsToReject = activeRequestsToReject.concat(request);
- });
- activeRequestsToReject.forEach(request => reject("active", request));
- },
-
- /**
- * Search for all requests in process for this client, including those made via
- * protocol.js and wait all of them to complete. Since the requests seen when this is
- * first called may in turn trigger more requests, we keep recursing through this
- * function until there is no more activity.
- *
- * This is a fairly heavy weight process, so it's only meant to be used in tests.
- *
- * @return Promise
- * Resolved when all requests have settled.
- */
- waitForRequestsToSettle() {
- let requests = [];
-
- // Gather all pending and active requests in this client
- // The request object supports a Promise API for completion (it has .then())
- this._pendingRequests.forEach(requestsForActor => {
- // Each value is an array of pending requests
- requests = requests.concat(requestsForActor);
- });
- this._activeRequests.forEach(requestForActor => {
- // Each value is a single active request
- requests = requests.concat(requestForActor);
- });
-
- // protocol.js
- // Use a Set because some fronts (like domwalker) seem to have multiple parents.
- let fronts = new Set();
- let poolsToVisit = [...this._pools];
-
- // With protocol.js, each front can potentially have it's own pools containing child
- // fronts, forming a tree. Descend through all the pools to locate all child fronts.
- while (poolsToVisit.length) {
- let pool = poolsToVisit.shift();
- fronts.add(pool);
- for (let child of pool.poolChildren()) {
- poolsToVisit.push(child);
- }
- }
-
- // For each front, wait for its requests to settle
- for (let front of fronts) {
- if (front.hasRequests()) {
- requests.push(front.waitForRequestsToSettle());
- }
- }
-
- // Abort early if there are no requests
- if (!requests.length) {
- return Promise.resolve();
- }
-
- return DevToolsUtils.settleAll(requests).catch(() => {
- // One of the requests might have failed, but ignore that situation here and pipe
- // both success and failure through the same path. The important part is just that
- // we waited.
- }).then(() => {
- // Repeat, more requests may have started in response to those we just waited for
- return this.waitForRequestsToSettle();
- });
- },
-
- registerClient: function (client) {
- let actorID = client.actor;
- if (!actorID) {
- throw new Error("DebuggerServer.registerClient expects " +
- "a client instance with an `actor` attribute.");
- }
- if (!Array.isArray(client.events)) {
- throw new Error("DebuggerServer.registerClient expects " +
- "a client instance with an `events` attribute " +
- "that is an array.");
- }
- if (client.events.length > 0 && typeof (client.emit) != "function") {
- throw new Error("DebuggerServer.registerClient expects " +
- "a client instance with non-empty `events` array to" +
- "have an `emit` function.");
- }
- if (this._clients.has(actorID)) {
- throw new Error("DebuggerServer.registerClient already registered " +
- "a client for this actor.");
- }
- this._clients.set(actorID, client);
- },
-
- unregisterClient: function (client) {
- let actorID = client.actor;
- if (!actorID) {
- throw new Error("DebuggerServer.unregisterClient expects " +
- "a Client instance with a `actor` attribute.");
- }
- this._clients.delete(actorID);
- },
-
- /**
- * Actor lifetime management, echos the server's actor pools.
- */
- __pools: null,
- get _pools() {
- if (this.__pools) {
- return this.__pools;
- }
- this.__pools = new Set();
- return this.__pools;
- },
-
- addActorPool: function (pool) {
- this._pools.add(pool);
- },
- removeActorPool: function (pool) {
- this._pools.delete(pool);
- },
- getActor: function (actorID) {
- let pool = this.poolFor(actorID);
- return pool ? pool.get(actorID) : null;
- },
-
- poolFor: function (actorID) {
- for (let pool of this._pools) {
- if (pool.has(actorID)) {
- return pool;
- }
- }
- return null;
- },
-
- /**
- * Currently attached addon.
- */
- activeAddon: null
-};
-
-eventSource(DebuggerClient.prototype);
-
-class Request extends EventEmitter {
- constructor(request) {
- super();
- this.request = request;
- }
-
- get actor() {
- return this.request.to || this.request.actor;
- }
-}
-
-/**
* Creates a tab client for the remote debugging protocol server. This client
* is a front to the tab actor created in the server side, hiding the protocol
* details in a traditional JavaScript API.
*
* @param client DebuggerClient
* The debugger client parent.
* @param form object
* The protocol form for this tab.
@@ -1455,1972 +148,9 @@ TabClient.prototype = {
attachWorker: function (workerActor, onResponse) {
return this.client.attachWorker(workerActor, onResponse);
},
};
eventSource(TabClient.prototype);
-function WorkerClient(client, form) {
- this.client = client;
- this._actor = form.from;
- this._isClosed = false;
- this._url = form.url;
-
- this._onClose = this._onClose.bind(this);
-
- this.addListener("close", this._onClose);
-
- this.traits = {};
-}
-
-WorkerClient.prototype = {
- get _transport() {
- return this.client._transport;
- },
-
- get request() {
- return this.client.request;
- },
-
- get actor() {
- return this._actor;
- },
-
- get url() {
- return this._url;
- },
-
- get isClosed() {
- return this._isClosed;
- },
-
- detach: DebuggerClient.requester({ type: "detach" }, {
- after: function (response) {
- if (this.thread) {
- this.client.unregisterClient(this.thread);
- }
- this.client.unregisterClient(this);
- return response;
- },
- }),
-
- attachThread: function (options = {}, onResponse = noop) {
- if (this.thread) {
- let response = [{
- type: "connected",
- threadActor: this.thread._actor,
- consoleActor: this.consoleActor,
- }, this.thread];
- DevToolsUtils.executeSoon(() => onResponse(response));
- return response;
- }
-
- // The connect call on server doesn't attach the thread as of version 44.
- return this.request({
- to: this._actor,
- type: "connect",
- options,
- }).then(connectResponse => {
- if (connectResponse.error) {
- onResponse(connectResponse, null);
- return [connectResponse, null];
- }
-
- return this.request({
- to: connectResponse.threadActor,
- type: "attach",
- options,
- }).then(attachResponse => {
- if (attachResponse.error) {
- onResponse(attachResponse, null);
- }
-
- this.thread = new ThreadClient(this, connectResponse.threadActor);
- this.consoleActor = connectResponse.consoleActor;
- this.client.registerClient(this.thread);
-
- onResponse(connectResponse, this.thread);
- return [connectResponse, this.thread];
- });
- }, error => {
- onResponse(error, null);
- });
- },
-
- _onClose: function () {
- this.removeListener("close", this._onClose);
-
- if (this.thread) {
- this.client.unregisterClient(this.thread);
- }
- this.client.unregisterClient(this);
- this._isClosed = true;
- },
-
- reconfigure: function () {
- return Promise.resolve();
- },
-
- events: ["close"]
-};
-
-eventSource(WorkerClient.prototype);
-
-function AddonClient(client, actor) {
- this._client = client;
- this._actor = actor;
- this.request = this._client.request;
- this.events = [];
-}
-
-AddonClient.prototype = {
- get actor() {
- return this._actor;
- },
- get _transport() {
- return this._client._transport;
- },
-
- /**
- * Detach the client from the addon actor.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- detach: DebuggerClient.requester({
- type: "detach"
- }, {
- after: function (response) {
- if (this._client.activeAddon === this) {
- this._client.activeAddon = null;
- }
- this._client.unregisterClient(this);
- return response;
- },
- })
-};
-
-/**
- * A RootClient object represents a root actor on the server. Each
- * DebuggerClient keeps a RootClient instance representing the root actor
- * for the initial connection; DebuggerClient's 'listTabs' and
- * 'listChildProcesses' methods forward to that root actor.
- *
- * @param client object
- * The client connection to which this actor belongs.
- * @param greeting string
- * The greeting packet from the root actor we're to represent.
- *
- * Properties of a RootClient instance:
- *
- * @property actor string
- * The name of this child's root actor.
- * @property applicationType string
- * The application type, as given in the root actor's greeting packet.
- * @property traits object
- * The traits object, as given in the root actor's greeting packet.
- */
-function RootClient(client, greeting) {
- this._client = client;
- this.actor = greeting.from;
- this.applicationType = greeting.applicationType;
- this.traits = greeting.traits;
-}
-exports.RootClient = RootClient;
-
-RootClient.prototype = {
- constructor: RootClient,
-
- /**
- * Gets the "root" form, which lists all the global actors that affect the entire
- * browser. This can replace usages of `listTabs` that only wanted the global actors
- * and didn't actually care about tabs.
- */
- getRoot: DebuggerClient.requester({ type: "getRoot" }),
-
- /**
- * List the open tabs.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- listTabs: DebuggerClient.requester({ type: "listTabs" }),
-
- /**
- * List the installed addons.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- listAddons: DebuggerClient.requester({ type: "listAddons" }),
-
- /**
- * List the registered workers.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- listWorkers: DebuggerClient.requester({ type: "listWorkers" }),
-
- /**
- * List the registered service workers.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- listServiceWorkerRegistrations: DebuggerClient.requester({
- type: "listServiceWorkerRegistrations"
- }),
-
- /**
- * List the running processes.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- listProcesses: DebuggerClient.requester({ type: "listProcesses" }),
-
- /**
- * Fetch the TabActor for the currently selected tab, or for a specific
- * tab given as first parameter.
- *
- * @param [optional] object filter
- * A dictionary object with following optional attributes:
- * - outerWindowID: used to match tabs in parent process
- * - tabId: used to match tabs in child processes
- * - tab: a reference to xul:tab element
- * If nothing is specified, returns the actor for the currently
- * selected tab.
- */
- getTab: function (filter) {
- let packet = {
- to: this.actor,
- type: "getTab"
- };
-
- if (filter) {
- if (typeof (filter.outerWindowID) == "number") {
- packet.outerWindowID = filter.outerWindowID;
- } else if (typeof (filter.tabId) == "number") {
- packet.tabId = filter.tabId;
- } else if ("tab" in filter) {
- let browser = filter.tab.linkedBrowser;
- if (browser.frameLoader.tabParent) {
- // Tabs in child process
- packet.tabId = browser.frameLoader.tabParent.tabId;
- } else if (browser.outerWindowID) {
- // <xul:browser> tabs in parent process
- packet.outerWindowID = browser.outerWindowID;
- } else {
- // <iframe mozbrowser> tabs in parent process
- let windowUtils = browser.contentWindow
- .QueryInterface(Ci.nsIInterfaceRequestor)
- .getInterface(Ci.nsIDOMWindowUtils);
- packet.outerWindowID = windowUtils.outerWindowID;
- }
- } else {
- // Throw if a filter object have been passed but without
- // any clearly idenfified filter.
- throw new Error("Unsupported argument given to getTab request");
- }
- }
-
- return this.request(packet);
- },
-
- /**
- * Fetch the WindowActor for a specific window, like a browser window in
- * Firefox, but it can be used to reach any window in the process.
- *
- * @param number outerWindowID
- * The outerWindowID of the top level window you are looking for.
- */
- getWindow: function ({ outerWindowID }) {
- if (!outerWindowID) {
- throw new Error("Must specify outerWindowID");
- }
-
- let packet = {
- to: this.actor,
- type: "getWindow",
- outerWindowID,
- };
-
- return this.request(packet);
- },
-
- /**
- * Description of protocol's actors and methods.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- protocolDescription: DebuggerClient.requester({ type: "protocolDescription" }),
-
- /*
- * Methods constructed by DebuggerClient.requester require these forwards
- * on their 'this'.
- */
- get _transport() {
- return this._client._transport;
- },
- get request() {
- return this._client.request;
- }
-};
-
-/**
- * Creates a thread client for the remote debugging protocol server. This client
- * is a front to the thread actor created in the server side, hiding the
- * protocol details in a traditional JavaScript API.
- *
- * @param client DebuggerClient|TabClient
- * The parent of the thread (tab for tab-scoped debuggers, DebuggerClient
- * for chrome debuggers).
- * @param actor string
- * The actor ID for this thread.
- */
-function ThreadClient(client, actor) {
- this._parent = client;
- this.client = client instanceof DebuggerClient ? client : client.client;
- this._actor = actor;
- this._frameCache = [];
- this._scriptCache = {};
- this._pauseGrips = {};
- this._threadGrips = {};
- this.request = this.client.request;
-}
-
-ThreadClient.prototype = {
- _state: "paused",
- get state() {
- return this._state;
- },
- get paused() {
- return this._state === "paused";
- },
-
- _pauseOnExceptions: false,
- _ignoreCaughtExceptions: false,
- _pauseOnDOMEvents: null,
-
- _actor: null,
- get actor() {
- return this._actor;
- },
-
- get _transport() {
- return this.client._transport;
- },
-
- _assertPaused: function (command) {
- if (!this.paused) {
- throw Error(command + " command sent while not paused. Currently " + this._state);
- }
- },
-
- /**
- * Resume a paused thread. If the optional limit parameter is present, then
- * the thread will also pause when that limit is reached.
- *
- * @param [optional] object limit
- * An object with a type property set to the appropriate limit (next,
- * step, or finish) per the remote debugging protocol specification.
- * Use null to specify no limit.
- * @param function onResponse
- * Called with the response packet.
- */
- _doResume: DebuggerClient.requester({
- type: "resume",
- resumeLimit: arg(0)
- }, {
- before: function (packet) {
- this._assertPaused("resume");
-
- // Put the client in a tentative "resuming" state so we can prevent
- // further requests that should only be sent in the paused state.
- this._previousState = this._state;
- this._state = "resuming";
-
- if (this._pauseOnExceptions) {
- packet.pauseOnExceptions = this._pauseOnExceptions;
- }
- if (this._ignoreCaughtExceptions) {
- packet.ignoreCaughtExceptions = this._ignoreCaughtExceptions;
- }
- if (this._pauseOnDOMEvents) {
- packet.pauseOnDOMEvents = this._pauseOnDOMEvents;
- }
- return packet;
- },
- after: function (response) {
- if (response.error && this._state == "resuming") {
- // There was an error resuming, update the state to the new one
- // reported by the server, if given (only on wrongState), otherwise
- // reset back to the previous state.
- if (response.state) {
- this._state = ThreadStateTypes[response.state];
- } else {
- this._state = this._previousState;
- }
- }
- delete this._previousState;
- return response;
- },
- }),
-
- /**
- * Reconfigure the thread actor.
- *
- * @param object options
- * A dictionary object of the new options to use in the thread actor.
- * @param function onResponse
- * Called with the response packet.
- */
- reconfigure: DebuggerClient.requester({
- type: "reconfigure",
- options: arg(0)
- }),
-
- /**
- * Resume a paused thread.
- */
- resume: function (onResponse) {
- return this._doResume(null, onResponse);
- },
-
- /**
- * Resume then pause without stepping.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- resumeThenPause: function (onResponse) {
- return this._doResume({ type: "break" }, onResponse);
- },
-
- /**
- * Step over a function call.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- stepOver: function (onResponse) {
- return this._doResume({ type: "next" }, onResponse);
- },
-
- /**
- * Step into a function call.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- stepIn: function (onResponse) {
- return this._doResume({ type: "step" }, onResponse);
- },
-
- /**
- * Step out of a function call.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- stepOut: function (onResponse) {
- return this._doResume({ type: "finish" }, onResponse);
- },
-
- /**
- * Immediately interrupt a running thread.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- interrupt: function (onResponse) {
- return this._doInterrupt(null, onResponse);
- },
-
- /**
- * Pause execution right before the next JavaScript bytecode is executed.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- breakOnNext: function (onResponse) {
- return this._doInterrupt("onNext", onResponse);
- },
-
- /**
- * Interrupt a running thread.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- _doInterrupt: DebuggerClient.requester({
- type: "interrupt",
- when: arg(0)
- }),
-
- /**
- * Enable or disable pausing when an exception is thrown.
- *
- * @param boolean pauseOnExceptions
- * Enables pausing if true, disables otherwise.
- * @param boolean ignoreCaughtExceptions
- * Whether to ignore caught exceptions
- * @param function onResponse
- * Called with the response packet.
- */
- pauseOnExceptions: function (pauseOnExceptions,
- ignoreCaughtExceptions,
- onResponse = noop) {
- this._pauseOnExceptions = pauseOnExceptions;
- this._ignoreCaughtExceptions = ignoreCaughtExceptions;
-
- // Otherwise send the flag using a standard resume request.
- if (!this.paused) {
- return this.interrupt(response => {
- if (response.error) {
- // Can't continue if pausing failed.
- onResponse(response);
- return response;
- }
- return this.resume(onResponse);
- });
- }
-
- onResponse();
- return promise.resolve();
- },
-
- /**
- * Enable pausing when the specified DOM events are triggered. Disabling
- * pausing on an event can be realized by calling this method with the updated
- * array of events that doesn't contain it.
- *
- * @param array|string events
- * An array of strings, representing the DOM event types to pause on,
- * or "*" to pause on all DOM events. Pass an empty array to
- * completely disable pausing on DOM events.
- * @param function onResponse
- * Called with the response packet in a future turn of the event loop.
- */
- pauseOnDOMEvents: function (events, onResponse = noop) {
- this._pauseOnDOMEvents = events;
- // If the debuggee is paused, the value of the array will be communicated in
- // the next resumption. Otherwise we have to force a pause in order to send
- // the array.
- if (this.paused) {
- DevToolsUtils.executeSoon(() => onResponse({}));
- return {};
- }
- return this.interrupt(response => {
- // Can't continue if pausing failed.
- if (response.error) {
- onResponse(response);
- return response;
- }
- return this.resume(onResponse);
- });
- },
-
- /**
- * Send a clientEvaluate packet to the debuggee. Response
- * will be a resume packet.
- *
- * @param string frame
- * The actor ID of the frame where the evaluation should take place.
- * @param string expression
- * The expression that will be evaluated in the scope of the frame
- * above.
- * @param function onResponse
- * Called with the response packet.
- */
- eval: DebuggerClient.requester({
- type: "clientEvaluate",
- frame: arg(0),
- expression: arg(1)
- }, {
- before: function (packet) {
- this._assertPaused("eval");
- // Put the client in a tentative "resuming" state so we can prevent
- // further requests that should only be sent in the paused state.
- this._state = "resuming";
- return packet;
- },
- after: function (response) {
- if (response.error) {
- // There was an error resuming, back to paused state.
- this._state = "paused";
- }
- return response;
- },
- }),
-
- /**
- * Detach from the thread actor.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- detach: DebuggerClient.requester({
- type: "detach"
- }, {
- after: function (response) {
- this.client.unregisterClient(this);
- this._parent.thread = null;
- return response;
- },
- }),
-
- /**
- * Release multiple thread-lifetime object actors. If any pause-lifetime
- * actors are included in the request, a |notReleasable| error will return,
- * but all the thread-lifetime ones will have been released.
- *
- * @param array actors
- * An array with actor IDs to release.
- */
- releaseMany: DebuggerClient.requester({
- type: "releaseMany",
- actors: arg(0),
- }),
-
- /**
- * Promote multiple pause-lifetime object actors to thread-lifetime ones.
- *
- * @param array actors
- * An array with actor IDs to promote.
- */
- threadGrips: DebuggerClient.requester({
- type: "threadGrips",
- actors: arg(0)
- }),
-
- /**
- * Return the event listeners defined on the page.
- *
- * @param onResponse Function
- * Called with the thread's response.
- */
- eventListeners: DebuggerClient.requester({
- type: "eventListeners"
- }),
-
- /**
- * Request the loaded sources for the current thread.
- *
- * @param onResponse Function
- * Called with the thread's response.
- */
- getSources: DebuggerClient.requester({
- type: "sources"
- }),
-
- /**
- * Clear the thread's source script cache. A scriptscleared event
- * will be sent.
- */
- _clearScripts: function () {
- if (Object.keys(this._scriptCache).length > 0) {
- this._scriptCache = {};
- this.emit("scriptscleared");
- }
- },
-
- /**
- * Request frames from the callstack for the current thread.
- *
- * @param start integer
- * The number of the youngest stack frame to return (the youngest
- * frame is 0).
- * @param count integer
- * The maximum number of frames to return, or null to return all
- * frames.
- * @param onResponse function
- * Called with the thread's response.
- */
- getFrames: DebuggerClient.requester({
- type: "frames",
- start: arg(0),
- count: arg(1)
- }),
-
- /**
- * An array of cached frames. Clients can observe the framesadded and
- * framescleared event to keep up to date on changes to this cache,
- * and can fill it using the fillFrames method.
- */
- get cachedFrames() {
- return this._frameCache;
- },
-
- /**
- * true if there are more stack frames available on the server.
- */
- get moreFrames() {
- return this.paused && (!this._frameCache || this._frameCache.length == 0
- || !this._frameCache[this._frameCache.length - 1].oldest);
- },
-
- /**
- * Request the frame environment.
- *
- * @param frameId string
- */
- getEnvironment: function (frameId) {
- return this.request({ to: frameId, type: "getEnvironment" });
- },
-
- /**
- * Ensure that at least total stack frames have been loaded in the
- * ThreadClient's stack frame cache. A framesadded event will be
- * sent when the stack frame cache is updated.
- *
- * @param total number
- * The minimum number of stack frames to be included.
- * @param callback function
- * Optional callback function called when frames have been loaded
- * @returns true if a framesadded notification should be expected.
- */
- fillFrames: function (total, callback = noop) {
- this._assertPaused("fillFrames");
- if (this._frameCache.length >= total) {
- return false;
- }
-
- let numFrames = this._frameCache.length;
-
- this.getFrames(numFrames, total - numFrames, (response) => {
- if (response.error) {
- callback(response);
- return;
- }
-
- let threadGrips = DevToolsUtils.values(this._threadGrips);
-
- for (let i in response.frames) {
- let frame = response.frames[i];
- if (!frame.where.source) {
- // Older servers use urls instead, so we need to resolve
- // them to source actors
- for (let grip of threadGrips) {
- if (grip instanceof SourceClient && grip.url === frame.url) {
- frame.where.source = grip._form;
- }
- }
- }
-
- this._frameCache[frame.depth] = frame;
- }
-
- // If we got as many frames as we asked for, there might be more
- // frames available.
- this.emit("framesadded");
-
- callback(response);
- });
-
- return true;
- },
-
- /**
- * Clear the thread's stack frame cache. A framescleared event
- * will be sent.
- */
- _clearFrames: function () {
- if (this._frameCache.length > 0) {
- this._frameCache = [];
- this.emit("framescleared");
- }
- },
-
- /**
- * Return a ObjectClient object for the given object grip.
- *
- * @param grip object
- * A pause-lifetime object grip returned by the protocol.
- */
- pauseGrip: function (grip) {
- if (grip.actor in this._pauseGrips) {
- return this._pauseGrips[grip.actor];
- }
-
- let client = new ObjectClient(this.client, grip);
- this._pauseGrips[grip.actor] = client;
- return client;
- },
-
- /**
- * Get or create a long string client, checking the grip client cache if it
- * already exists.
- *
- * @param grip Object
- * The long string grip returned by the protocol.
- * @param gripCacheName String
- * The property name of the grip client cache to check for existing
- * clients in.
- */
- _longString: function (grip, gripCacheName) {
- if (grip.actor in this[gripCacheName]) {
- return this[gripCacheName][grip.actor];
- }
-
- let client = new LongStringClient(this.client, grip);
- this[gripCacheName][grip.actor] = client;
- return client;
- },
-
- /**
- * Return an instance of LongStringClient for the given long string grip that
- * is scoped to the current pause.
- *
- * @param grip Object
- * The long string grip returned by the protocol.
- */
- pauseLongString: function (grip) {
- return this._longString(grip, "_pauseGrips");
- },
-
- /**
- * Return an instance of LongStringClient for the given long string grip that
- * is scoped to the thread lifetime.
- *
- * @param grip Object
- * The long string grip returned by the protocol.
- */
- threadLongString: function (grip) {
- return this._longString(grip, "_threadGrips");
- },
-
- /**
- * Get or create an ArrayBuffer client, checking the grip client cache if it
- * already exists.
- *
- * @param grip Object
- * The ArrayBuffer grip returned by the protocol.
- * @param gripCacheName String
- * The property name of the grip client cache to check for existing
- * clients in.
- */
- _arrayBuffer: function (grip, gripCacheName) {
- if (grip.actor in this[gripCacheName]) {
- return this[gripCacheName][grip.actor];
- }
-
- let client = new ArrayBufferClient(this.client, grip);
- this[gripCacheName][grip.actor] = client;
- return client;
- },
-
- /**
- * Return an instance of ArrayBufferClient for the given ArrayBuffer grip that
- * is scoped to the thread lifetime.
- *
- * @param grip Object
- * The ArrayBuffer grip returned by the protocol.
- */
- threadArrayBuffer: function (grip) {
- return this._arrayBuffer(grip, "_threadGrips");
- },
-
- /**
- * Clear and invalidate all the grip clients from the given cache.
- *
- * @param gripCacheName
- * The property name of the grip cache we want to clear.
- */
- _clearObjectClients: function (gripCacheName) {
- for (let id in this[gripCacheName]) {
- this[gripCacheName][id].valid = false;
- }
- this[gripCacheName] = {};
- },
-
- /**
- * Invalidate pause-lifetime grip clients and clear the list of current grip
- * clients.
- */
- _clearPauseGrips: function () {
- this._clearObjectClients("_pauseGrips");
- },
-
- /**
- * Invalidate thread-lifetime grip clients and clear the list of current grip
- * clients.
- */
- _clearThreadGrips: function () {
- this._clearObjectClients("_threadGrips");
- },
-
- /**
- * Handle thread state change by doing necessary cleanup and notifying all
- * registered listeners.
- */
- _onThreadState: function (packet) {
- this._state = ThreadStateTypes[packet.type];
- // The debugger UI may not be initialized yet so we want to keep
- // the packet around so it knows what to pause state to display
- // when it's initialized
- this._lastPausePacket = packet.type === "resumed" ? null : packet;
- this._clearFrames();
- this._clearPauseGrips();
- packet.type === ThreadStateTypes.detached && this._clearThreadGrips();
- this.client._eventsEnabled && this.emit(packet.type, packet);
- },
-
- getLastPausePacket: function () {
- return this._lastPausePacket;
- },
-
- /**
- * Return an EnvironmentClient instance for the given environment actor form.
- */
- environment: function (form) {
- return new EnvironmentClient(this.client, form);
- },
-
- /**
- * Return an instance of SourceClient for the given source actor form.
- */
- source: function (form) {
- if (form.actor in this._threadGrips) {
- return this._threadGrips[form.actor];
- }
-
- this._threadGrips[form.actor] = new SourceClient(this, form);
- return this._threadGrips[form.actor];
- },
-
- /**
- * Request the prototype and own properties of mutlipleObjects.
- *
- * @param onResponse function
- * Called with the request's response.
- * @param actors [string]
- * List of actor ID of the queried objects.
- */
- getPrototypesAndProperties: DebuggerClient.requester({
- type: "prototypesAndProperties",
- actors: arg(0)
- }),
-
- events: ["newSource"]
-};
-
-eventSource(ThreadClient.prototype);
-
-/**
- * Creates a tracing profiler client for the remote debugging protocol
- * server. This client is a front to the trace actor created on the
- * server side, hiding the protocol details in a traditional
- * JavaScript API.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param actor string
- * The actor ID for this thread.
- */
-function TraceClient(client, actor) {
- this._client = client;
- this._actor = actor;
- this._activeTraces = new Set();
- this._waitingPackets = new Map();
- this._expectedPacket = 0;
- this.request = this._client.request;
- this.events = [];
-}
-
-TraceClient.prototype = {
- get actor() {
- return this._actor;
- },
- get tracing() {
- return this._activeTraces.size > 0;
- },
-
- get _transport() {
- return this._client._transport;
- },
-
- /**
- * Detach from the trace actor.
- */
- detach: DebuggerClient.requester({
- type: "detach"
- }, {
- after: function (response) {
- this._client.unregisterClient(this);
- return response;
- },
- }),
-
- /**
- * Start a new trace.
- *
- * @param trace [string]
- * An array of trace types to be recorded by the new trace.
- *
- * @param name string
- * The name of the new trace.
- *
- * @param onResponse function
- * Called with the request's response.
- */
- startTrace: DebuggerClient.requester({
- type: "startTrace",
- name: arg(1),
- trace: arg(0)
- }, {
- after: function (response) {
- if (response.error) {
- return response;
- }
-
- if (!this.tracing) {
- this._waitingPackets.clear();
- this._expectedPacket = 0;
- }
- this._activeTraces.add(response.name);
-
- return response;
- },
- }),
-
- /**
- * End a trace. If a name is provided, stop the named
- * trace. Otherwise, stop the most recently started trace.
- *
- * @param name string
- * The name of the trace to stop.
- *
- * @param onResponse function
- * Called with the request's response.
- */
- stopTrace: DebuggerClient.requester({
- type: "stopTrace",
- name: arg(0)
- }, {
- after: function (response) {
- if (response.error) {
- return response;
- }
-
- this._activeTraces.delete(response.name);
-
- return response;
- },
- })
-};
-
-/**
- * Grip clients are used to retrieve information about the relevant object.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param grip object
- * A pause-lifetime object grip returned by the protocol.
- */
-function ObjectClient(client, grip) {
- this._grip = grip;
- this._client = client;
- this.request = this._client.request;
-}
-exports.ObjectClient = ObjectClient;
-
-ObjectClient.prototype = {
- get actor() {
- return this._grip.actor;
- },
- get _transport() {
- return this._client._transport;
- },
-
- valid: true,
-
- get isFrozen() {
- return this._grip.frozen;
- },
- get isSealed() {
- return this._grip.sealed;
- },
- get isExtensible() {
- return this._grip.extensible;
- },
-
- getDefinitionSite: DebuggerClient.requester({
- type: "definitionSite"
- }, {
- before: function (packet) {
- if (this._grip.class != "Function") {
- throw new Error("getDefinitionSite is only valid for function grips.");
- }
- return packet;
- }
- }),
-
- /**
- * Request the names of a function's formal parameters.
- *
- * @param onResponse function
- * Called with an object of the form:
- * { parameterNames:[<parameterName>, ...] }
- * where each <parameterName> is the name of a parameter.
- */
- getParameterNames: DebuggerClient.requester({
- type: "parameterNames"
- }, {
- before: function (packet) {
- if (this._grip.class !== "Function") {
- throw new Error("getParameterNames is only valid for function grips.");
- }
- return packet;
- },
- }),
-
- /**
- * Request the names of the properties defined on the object and not its
- * prototype.
- *
- * @param onResponse function Called with the request's response.
- */
- getOwnPropertyNames: DebuggerClient.requester({
- type: "ownPropertyNames"
- }),
-
- /**
- * Request the prototype and own properties of the object.
- *
- * @param onResponse function Called with the request's response.
- */
- getPrototypeAndProperties: DebuggerClient.requester({
- type: "prototypeAndProperties"
- }),
-
- /**
- * Request a PropertyIteratorClient instance to ease listing
- * properties for this object.
- *
- * @param options Object
- * A dictionary object with various boolean attributes:
- * - ignoreIndexedProperties Boolean
- * If true, filters out Array items.
- * e.g. properties names between `0` and `object.length`.
- * - ignoreNonIndexedProperties Boolean
- * If true, filters out items that aren't array items
- * e.g. properties names that are not a number between `0`
- * and `object.length`.
- * - sort Boolean
- * If true, the iterator will sort the properties by name
- * before dispatching them.
- * @param onResponse function Called with the client instance.
- */
- enumProperties: DebuggerClient.requester({
- type: "enumProperties",
- options: arg(0)
- }, {
- after: function (response) {
- if (response.iterator) {
- return { iterator: new PropertyIteratorClient(this._client, response.iterator) };
- }
- return response;
- },
- }),
-
- /**
- * Request a PropertyIteratorClient instance to enumerate entries in a
- * Map/Set-like object.
- *
- * @param onResponse function Called with the request's response.
- */
- enumEntries: DebuggerClient.requester({
- type: "enumEntries"
- }, {
- before: function (packet) {
- if (!["Map", "WeakMap", "Set", "WeakSet"].includes(this._grip.class)) {
- throw new Error("enumEntries is only valid for Map/Set-like grips.");
- }
- return packet;
- },
- after: function (response) {
- if (response.iterator) {
- return {
- iterator: new PropertyIteratorClient(this._client, response.iterator)
- };
- }
- return response;
- }
- }),
-
- /**
- * Request a SymbolIteratorClient instance to enumerate symbols in an object.
- *
- * @param onResponse function Called with the request's response.
- */
- enumSymbols: DebuggerClient.requester({
- type: "enumSymbols"
- }, {
- before: function (packet) {
- if (this._grip.type !== "object") {
- throw new Error("enumSymbols is only valid for objects grips.");
- }
- return packet;
- },
- after: function (response) {
- if (response.iterator) {
- return {
- iterator: new SymbolIteratorClient(this._client, response.iterator)
- };
- }
- return response;
- }
- }),
-
- /**
- * Request the property descriptor of the object's specified property.
- *
- * @param name string The name of the requested property.
- * @param onResponse function Called with the request's response.
- */
- getProperty: DebuggerClient.requester({
- type: "property",
- name: arg(0)
- }),
-
- /**
- * Request the prototype of the object.
- *
- * @param onResponse function Called with the request's response.
- */
- getPrototype: DebuggerClient.requester({
- type: "prototype"
- }),
-
- /**
- * Request the display string of the object.
- *
- * @param onResponse function Called with the request's response.
- */
- getDisplayString: DebuggerClient.requester({
- type: "displayString"
- }),
-
- /**
- * Request the scope of the object.
- *
- * @param onResponse function Called with the request's response.
- */
- getScope: DebuggerClient.requester({
- type: "scope"
- }, {
- before: function (packet) {
- if (this._grip.class !== "Function") {
- throw new Error("scope is only valid for function grips.");
- }
- return packet;
- },
- }),
-
- /**
- * Request the promises directly depending on the current promise.
- */
- getDependentPromises: DebuggerClient.requester({
- type: "dependentPromises"
- }, {
- before: function (packet) {
- if (this._grip.class !== "Promise") {
- throw new Error("getDependentPromises is only valid for promise " +
- "grips.");
- }
- return packet;
- }
- }),
-
- /**
- * Request the stack to the promise's allocation point.
- */
- getPromiseAllocationStack: DebuggerClient.requester({
- type: "allocationStack"
- }, {
- before: function (packet) {
- if (this._grip.class !== "Promise") {
- throw new Error("getAllocationStack is only valid for promise grips.");
- }
- return packet;
- }
- }),
-
- /**
- * Request the stack to the promise's fulfillment point.
- */
- getPromiseFulfillmentStack: DebuggerClient.requester({
- type: "fulfillmentStack"
- }, {
- before: function (packet) {
- if (this._grip.class !== "Promise") {
- throw new Error("getPromiseFulfillmentStack is only valid for " +
- "promise grips.");
- }
- return packet;
- }
- }),
-
- /**
- * Request the stack to the promise's rejection point.
- */
- getPromiseRejectionStack: DebuggerClient.requester({
- type: "rejectionStack"
- }, {
- before: function (packet) {
- if (this._grip.class !== "Promise") {
- throw new Error("getPromiseRejectionStack is only valid for " +
- "promise grips.");
- }
- return packet;
- }
- })
-};
-
-/**
- * A PropertyIteratorClient provides a way to access to property names and
- * values of an object efficiently, slice by slice.
- * Note that the properties can be sorted in the backend,
- * this is controled while creating the PropertyIteratorClient
- * from ObjectClient.enumProperties.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param grip Object
- * A PropertyIteratorActor grip returned by the protocol via
- * TabActor.enumProperties request.
- */
-function PropertyIteratorClient(client, grip) {
- this._grip = grip;
- this._client = client;
- this.request = this._client.request;
-}
-
-PropertyIteratorClient.prototype = {
- get actor() {
- return this._grip.actor;
- },
-
- /**
- * Get the total number of properties available in the iterator.
- */
- get count() {
- return this._grip.count;
- },
-
- /**
- * Get one or more property names that correspond to the positions in the
- * indexes parameter.
- *
- * @param indexes Array
- * An array of property indexes.
- * @param callback Function
- * The function called when we receive the property names.
- */
- names: DebuggerClient.requester({
- type: "names",
- indexes: arg(0)
- }, {}),
-
- /**
- * Get a set of following property value(s).
- *
- * @param start Number
- * The index of the first property to fetch.
- * @param count Number
- * The number of properties to fetch.
- * @param callback Function
- * The function called when we receive the property values.
- */
- slice: DebuggerClient.requester({
- type: "slice",
- start: arg(0),
- count: arg(1)
- }, {}),
-
- /**
- * Get all the property values.
- *
- * @param callback Function
- * The function called when we receive the property values.
- */
- all: DebuggerClient.requester({
- type: "all"
- }, {}),
-};
-
-/**
- * A SymbolIteratorClient provides a way to access to symbols
- * of an object efficiently, slice by slice.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param grip Object
- * A SymbolIteratorActor grip returned by the protocol via
- * TabActor.enumSymbols request.
- */
-function SymbolIteratorClient(client, grip) {
- this._grip = grip;
- this._client = client;
- this.request = this._client.request;
-}
-
-SymbolIteratorClient.prototype = {
- get actor() {
- return this._grip.actor;
- },
-
- /**
- * Get the total number of symbols available in the iterator.
- */
- get count() {
- return this._grip.count;
- },
-
- /**
- * Get a set of following symbols.
- *
- * @param start Number
- * The index of the first symbol to fetch.
- * @param count Number
- * The number of symbols to fetch.
- * @param callback Function
- * The function called when we receive the symbols.
- */
- slice: DebuggerClient.requester({
- type: "slice",
- start: arg(0),
- count: arg(1)
- }, {}),
-
- /**
- * Get all the symbols.
- *
- * @param callback Function
- * The function called when we receive the symbols.
- */
- all: DebuggerClient.requester({
- type: "all"
- }, {}),
-};
-
-/**
- * A ArrayBufferClient provides a way to access ArrayBuffer from the
- * debugger server.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param grip Object
- * A pause-lifetime ArrayBuffer grip returned by the protocol.
- */
-function ArrayBufferClient(client, grip) {
- this._grip = grip;
- this._client = client;
- this.request = this._client.request;
-}
-ArrayBufferClient.prototype = {
- get actor() {
- return this._grip.actor;
- },
- get length() {
- return this._grip.length;
- },
- get _transport() {
- return this._client._transport;
- },
-
- valid: true,
-
- slice: DebuggerClient.requester({
- type: "slice",
- start: arg(0),
- count: arg(1)
- }),
-};
-
-/**
- * A LongStringClient provides a way to access "very long" strings from the
- * debugger server.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param grip Object
- * A pause-lifetime long string grip returned by the protocol.
- */
-function LongStringClient(client, grip) {
- this._grip = grip;
- this._client = client;
- this.request = this._client.request;
-}
-exports.LongStringClient = LongStringClient;
-
-LongStringClient.prototype = {
- get actor() {
- return this._grip.actor;
- },
- get length() {
- return this._grip.length;
- },
- get initial() {
- return this._grip.initial;
- },
- get _transport() {
- return this._client._transport;
- },
-
- valid: true,
-
- /**
- * Get the substring of this LongString from start to end.
- *
- * @param start Number
- * The starting index.
- * @param end Number
- * The ending index.
- * @param callback Function
- * The function called when we receive the substring.
- */
- substring: DebuggerClient.requester({
- type: "substring",
- start: arg(0),
- end: arg(1)
- }),
-};
-
-/**
- * A SourceClient provides a way to access the source text of a script.
- *
- * @param client ThreadClient
- * The thread client parent.
- * @param form Object
- * The form sent across the remote debugging protocol.
- */
-function SourceClient(client, form) {
- this._form = form;
- this._isBlackBoxed = form.isBlackBoxed;
- this._isPrettyPrinted = form.isPrettyPrinted;
- this._activeThread = client;
- this._client = client.client;
-}
-
-SourceClient.prototype = {
- get _transport() {
- return this._client._transport;
- },
- get isBlackBoxed() {
- return this._isBlackBoxed;
- },
- get isPrettyPrinted() {
- return this._isPrettyPrinted;
- },
- get actor() {
- return this._form.actor;
- },
- get request() {
- return this._client.request;
- },
- get url() {
- return this._form.url;
- },
-
- /**
- * Black box this SourceClient's source.
- *
- * @param callback Function
- * The callback function called when we receive the response from the server.
- */
- blackBox: DebuggerClient.requester({
- type: "blackbox"
- }, {
- after: function (response) {
- if (!response.error) {
- this._isBlackBoxed = true;
- if (this._activeThread) {
- this._activeThread.emit("blackboxchange", this);
- }
- }
- return response;
- }
- }),
-
- /**
- * Un-black box this SourceClient's source.
- *
- * @param callback Function
- * The callback function called when we receive the response from the server.
- */
- unblackBox: DebuggerClient.requester({
- type: "unblackbox"
- }, {
- after: function (response) {
- if (!response.error) {
- this._isBlackBoxed = false;
- if (this._activeThread) {
- this._activeThread.emit("blackboxchange", this);
- }
- }
- return response;
- }
- }),
-
- /**
- * Get Executable Lines from a source
- *
- * @param callback Function
- * The callback function called when we receive the response from the server.
- */
- getExecutableLines: function (cb = noop) {
- let packet = {
- to: this._form.actor,
- type: "getExecutableLines"
- };
-
- return this._client.request(packet).then(res => {
- cb(res.lines);
- return res.lines;
- });
- },
-
- /**
- * Get a long string grip for this SourceClient's source.
- */
- source: function (callback = noop) {
- let packet = {
- to: this._form.actor,
- type: "source"
- };
- return this._client.request(packet).then(response => {
- return this._onSourceResponse(response, callback);
- });
- },
-
- /**
- * Pretty print this source's text.
- */
- prettyPrint: function (indent, callback = noop) {
- const packet = {
- to: this._form.actor,
- type: "prettyPrint",
- indent
- };
- return this._client.request(packet).then(response => {
- if (!response.error) {
- this._isPrettyPrinted = true;
- this._activeThread._clearFrames();
- this._activeThread.emit("prettyprintchange", this);
- }
- return this._onSourceResponse(response, callback);
- });
- },
-
- /**
- * Stop pretty printing this source's text.
- */
- disablePrettyPrint: function (callback = noop) {
- const packet = {
- to: this._form.actor,
- type: "disablePrettyPrint"
- };
- return this._client.request(packet).then(response => {
- if (!response.error) {
- this._isPrettyPrinted = false;
- this._activeThread._clearFrames();
- this._activeThread.emit("prettyprintchange", this);
- }
- return this._onSourceResponse(response, callback);
- });
- },
-
- _onSourceResponse: function (response, callback) {
- if (response.error) {
- callback(response);
- return response;
- }
-
- if (typeof response.source === "string") {
- callback(response);
- return response;
- }
-
- let { contentType, source } = response;
- if (source.type === "arrayBuffer") {
- let arrayBuffer = this._activeThread.threadArrayBuffer(source);
- return arrayBuffer.slice(0, arrayBuffer.length).then(function (resp) {
- if (resp.error) {
- callback(resp);
- return resp;
- }
- // Keeping str as a string, ArrayBuffer/Uint8Array will not survive
- // setIn/mergeIn operations.
- const str = atob(resp.encoded);
- let newResponse = {
- source: {
- binary: str,
- toString: () => "[wasm]",
- },
- contentType,
- };
- callback(newResponse);
- return newResponse;
- });
- }
-
- let longString = this._activeThread.threadLongString(source);
- return longString.substring(0, longString.length).then(function (resp) {
- if (resp.error) {
- callback(resp);
- return resp;
- }
-
- let newResponse = {
- source: resp.substring,
- contentType: contentType
- };
- callback(newResponse);
- return newResponse;
- });
- },
-
- /**
- * Request to set a breakpoint in the specified location.
- *
- * @param object location
- * The location and condition of the breakpoint in
- * the form of { line[, column, condition] }.
- * @param function onResponse
- * Called with the thread's response.
- */
- setBreakpoint: function ({ line, column, condition, noSliding }, onResponse = noop) {
- // A helper function that sets the breakpoint.
- let doSetBreakpoint = callback => {
- let root = this._client.mainRoot;
- let location = {
- line,
- column,
- };
-
- let packet = {
- to: this.actor,
- type: "setBreakpoint",
- location,
- condition,
- noSliding,
- };
-
- // Backwards compatibility: send the breakpoint request to the
- // thread if the server doesn't support Debugger.Source actors.
- if (!root.traits.debuggerSourceActors) {
- packet.to = this._activeThread.actor;
- packet.location.url = this.url;
- }
-
- return this._client.request(packet).then(response => {
- // Ignoring errors, since the user may be setting a breakpoint in a
- // dead script that will reappear on a page reload.
- let bpClient;
- if (response.actor) {
- bpClient = new BreakpointClient(
- this._client,
- this,
- response.actor,
- location,
- root.traits.conditionalBreakpoints ? condition : undefined
- );
- }
- onResponse(response, bpClient);
- if (callback) {
- callback();
- }
- return [response, bpClient];
- });
- };
-
- // If the debuggee is paused, just set the breakpoint.
- if (this._activeThread.paused) {
- return doSetBreakpoint();
- }
- // Otherwise, force a pause in order to set the breakpoint.
- return this._activeThread.interrupt().then(response => {
- if (response.error) {
- // Can't set the breakpoint if pausing failed.
- onResponse(response);
- return response;
- }
-
- const { type, why } = response;
- const cleanUp = type == "paused" && why.type == "interrupted"
- ? () => this._activeThread.resume()
- : noop;
-
- return doSetBreakpoint(cleanUp);
- });
- }
-};
-
-/**
- * Breakpoint clients are used to remove breakpoints that are no longer used.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param sourceClient SourceClient
- * The source where this breakpoint exists
- * @param actor string
- * The actor ID for this breakpoint.
- * @param location object
- * The location of the breakpoint. This is an object with two properties:
- * url and line.
- * @param condition string
- * The conditional expression of the breakpoint
- */
-function BreakpointClient(client, sourceClient, actor, location, condition) {
- this._client = client;
- this._actor = actor;
- this.location = location;
- this.location.actor = sourceClient.actor;
- this.location.url = sourceClient.url;
- this.source = sourceClient;
- this.request = this._client.request;
-
- // The condition property should only exist if it's a truthy value
- if (condition) {
- this.condition = condition;
- }
-}
-
-BreakpointClient.prototype = {
-
- _actor: null,
- get actor() {
- return this._actor;
- },
- get _transport() {
- return this._client._transport;
- },
-
- /**
- * Remove the breakpoint from the server.
- */
- remove: DebuggerClient.requester({
- type: "delete"
- }),
-
- /**
- * Determines if this breakpoint has a condition
- */
- hasCondition: function () {
- let root = this._client.mainRoot;
- // XXX bug 990137: We will remove support for client-side handling of
- // conditional breakpoints
- if (root.traits.conditionalBreakpoints) {
- return "condition" in this;
- }
- return "conditionalExpression" in this;
- },
-
- /**
- * Get the condition of this breakpoint. Currently we have to
- * support locally emulated conditional breakpoints until the
- * debugger servers are updated (see bug 990137). We used a
- * different property when moving it server-side to ensure that we
- * are testing the right code.
- */
- getCondition: function () {
- let root = this._client.mainRoot;
- if (root.traits.conditionalBreakpoints) {
- return this.condition;
- }
- return this.conditionalExpression;
- },
-
- /**
- * Set the condition of this breakpoint
- */
- setCondition: function (gThreadClient, condition) {
- let root = this._client.mainRoot;
- let deferred = promise.defer();
-
- if (root.traits.conditionalBreakpoints) {
- let info = {
- line: this.location.line,
- column: this.location.column,
- condition: condition
- };
-
- // Remove the current breakpoint and add a new one with the
- // condition.
- this.remove(response => {
- if (response && response.error) {
- deferred.reject(response);
- return;
- }
-
- this.source.setBreakpoint(info, (resp, newBreakpoint) => {
- if (resp && resp.error) {
- deferred.reject(resp);
- } else {
- deferred.resolve(newBreakpoint);
- }
- });
- });
- } else {
- // The property shouldn't even exist if the condition is blank
- if (condition === "") {
- delete this.conditionalExpression;
- } else {
- this.conditionalExpression = condition;
- }
- deferred.resolve(this);
- }
-
- return deferred.promise;
- }
-};
-
-eventSource(BreakpointClient.prototype);
-
-/**
- * Environment clients are used to manipulate the lexical environment actors.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param form Object
- * The form sent across the remote debugging protocol.
- */
-function EnvironmentClient(client, form) {
- this._client = client;
- this._form = form;
- this.request = this._client.request;
-}
-exports.EnvironmentClient = EnvironmentClient;
-
-EnvironmentClient.prototype = {
-
- get actor() {
- return this._form.actor;
- },
- get _transport() {
- return this._client._transport;
- },
-
- /**
- * Fetches the bindings introduced by this lexical environment.
- */
- getBindings: DebuggerClient.requester({
- type: "bindings"
- }),
-
- /**
- * Changes the value of the identifier whose name is name (a string) to that
- * represented by value (a grip).
- */
- assign: DebuggerClient.requester({
- type: "assign",
- name: arg(0),
- value: arg(1)
- })
-};
-
-eventSource(EnvironmentClient.prototype);
+module.exports = TabClient;
copy from devtools/shared/client/main.js
copy to devtools/shared/client/thread-client.js
--- a/devtools/shared/client/main.js
+++ b/devtools/shared/client/thread-client.js
@@ -1,1780 +1,31 @@
-/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
-/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
-const { Ci, Cu } = require("chrome");
-const DevToolsUtils = require("devtools/shared/DevToolsUtils");
-const { getStack, callFunctionWithAsyncStack } = require("devtools/shared/platform/stack");
-
+const { Cu } = require("chrome");
const promise = Cu.import("resource://devtools/shared/deprecated-sync-thenables.js", {}).Promise;
-loader.lazyRequireGetter(this, "EventEmitter", "devtools/shared/event-emitter");
-loader.lazyRequireGetter(this, "WebConsoleClient", "devtools/shared/webconsole/client", true);
-loader.lazyRequireGetter(this, "DebuggerSocket", "devtools/shared/security/socket", true);
-loader.lazyRequireGetter(this, "Authentication", "devtools/shared/security/auth");
+const DevToolsUtils = require("devtools/shared/DevToolsUtils");
+const {arg, DebuggerClient} = require("./debugger-client");
+const eventSource = require("./event-source");
+const {ThreadStateTypes} = require("./constants");
+
+loader.lazyRequireGetter(this, "ArrayBufferClient", "devtools/shared/client/array-buffer-client");
+loader.lazyRequireGetter(this, "EnvironmentClient", "devtools/shared/client/environment-client");
+loader.lazyRequireGetter(this, "LongStringClient", "devtools/shared/client/long-string-client");
+loader.lazyRequireGetter(this, "ObjectClient", "devtools/shared/client/object-client");
+loader.lazyRequireGetter(this, "SourceClient", "devtools/shared/client/source-client");
const noop = () => {};
/**
- * TODO: Get rid of this API in favor of EventTarget (bug 1042642)
- *
- * Add simple event notification to a prototype object. Any object that has
- * some use for event notifications or the observer pattern in general can be
- * augmented with the necessary facilities by passing its prototype to this
- * function.
- *
- * @param proto object
- * The prototype object that will be modified.
- */
-function eventSource(proto) {
- /**
- * Add a listener to the event source for a given event.
- *
- * @param name string
- * The event to listen for.
- * @param listener function
- * Called when the event is fired. If the same listener
- * is added more than once, it will be called once per
- * addListener call.
- */
- proto.addListener = function (name, listener) {
- if (typeof listener != "function") {
- throw TypeError("Listeners must be functions.");
- }
-
- if (!this._listeners) {
- this._listeners = {};
- }
-
- this._getListeners(name).push(listener);
- };
-
- /**
- * Add a listener to the event source for a given event. The
- * listener will be removed after it is called for the first time.
- *
- * @param name string
- * The event to listen for.
- * @param listener function
- * Called when the event is fired.
- */
- proto.addOneTimeListener = function (name, listener) {
- let l = (...args) => {
- this.removeListener(name, l);
- listener.apply(null, args);
- };
- this.addListener(name, l);
- };
-
- /**
- * Remove a listener from the event source previously added with
- * addListener().
- *
- * @param name string
- * The event name used during addListener to add the listener.
- * @param listener function
- * The callback to remove. If addListener was called multiple
- * times, all instances will be removed.
- */
- proto.removeListener = function (name, listener) {
- if (!this._listeners || (listener && !this._listeners[name])) {
- return;
- }
-
- if (!listener) {
- this._listeners[name] = [];
- } else {
- this._listeners[name] =
- this._listeners[name].filter(l => l != listener);
- }
- };
-
- /**
- * Returns the listeners for the specified event name. If none are defined it
- * initializes an empty list and returns that.
- *
- * @param name string
- * The event name.
- */
- proto._getListeners = function (name) {
- if (name in this._listeners) {
- return this._listeners[name];
- }
- this._listeners[name] = [];
- return this._listeners[name];
- };
-
- /**
- * Notify listeners of an event.
- *
- * @param name string
- * The event to fire.
- * @param arguments
- * All arguments will be passed along to the listeners,
- * including the name argument.
- */
- proto.emit = function () {
- if (!this._listeners) {
- return;
- }
-
- let name = arguments[0];
- let listeners = this._getListeners(name).slice(0);
-
- for (let listener of listeners) {
- try {
- listener.apply(null, arguments);
- } catch (e) {
- // Prevent a bad listener from interfering with the others.
- DevToolsUtils.reportException("notify event '" + name + "'", e);
- }
- }
- };
-}
-
-/**
- * Set of protocol messages that affect thread state, and the
- * state the actor is in after each message.
- */
-const ThreadStateTypes = {
- "paused": "paused",
- "resumed": "attached",
- "detached": "detached",
- "running": "attached"
-};
-
-/**
- * Set of protocol messages that are sent by the server without a prior request
- * by the client.
- */
-const UnsolicitedNotifications = {
- "consoleAPICall": "consoleAPICall",
- "eventNotification": "eventNotification",
- "fileActivity": "fileActivity",
- "lastPrivateContextExited": "lastPrivateContextExited",
- "logMessage": "logMessage",
- "networkEvent": "networkEvent",
- "networkEventUpdate": "networkEventUpdate",
- "newGlobal": "newGlobal",
- "newScript": "newScript",
- "tabDetached": "tabDetached",
- "tabListChanged": "tabListChanged",
- "reflowActivity": "reflowActivity",
- "addonListChanged": "addonListChanged",
- "workerListChanged": "workerListChanged",
- "serviceWorkerRegistrationListChanged": "serviceWorkerRegistrationList",
- "tabNavigated": "tabNavigated",
- "frameUpdate": "frameUpdate",
- "pageError": "pageError",
- "documentLoad": "documentLoad",
- "enteredFrame": "enteredFrame",
- "exitedFrame": "exitedFrame",
- "appOpen": "appOpen",
- "appClose": "appClose",
- "appInstall": "appInstall",
- "appUninstall": "appUninstall",
- "evaluationResult": "evaluationResult",
- "newSource": "newSource",
- "updatedSource": "updatedSource",
- "inspectObject": "inspectObject"
-};
-
-/**
- * Set of pause types that are sent by the server and not as an immediate
- * response to a client request.
- */
-const UnsolicitedPauses = {
- "resumeLimit": "resumeLimit",
- "debuggerStatement": "debuggerStatement",
- "breakpoint": "breakpoint",
- "DOMEvent": "DOMEvent",
- "watchpoint": "watchpoint",
- "exception": "exception"
-};
-
-/**
- * Creates a client for the remote debugging protocol server. This client
- * provides the means to communicate with the server and exchange the messages
- * required by the protocol in a traditional JavaScript API.
- */
-const DebuggerClient = exports.DebuggerClient = function (transport) {
- this._transport = transport;
- this._transport.hooks = this;
-
- // Map actor ID to client instance for each actor type.
- this._clients = new Map();
-
- this._pendingRequests = new Map();
- this._activeRequests = new Map();
- this._eventsEnabled = true;
-
- this.traits = {};
-
- this.request = this.request.bind(this);
- this.localTransport = this._transport.onOutputStreamReady === undefined;
-
- /*
- * As the first thing on the connection, expect a greeting packet from
- * the connection's root actor.
- */
- this.mainRoot = null;
- this.expectReply("root", (packet) => {
- this.mainRoot = new RootClient(this, packet);
- this.emit("connected", packet.applicationType, packet.traits);
- });
-};
-
-/**
- * A declarative helper for defining methods that send requests to the server.
- *
- * @param packetSkeleton
- * The form of the packet to send. Can specify fields to be filled from
- * the parameters by using the |arg| function.
- * @param before
- * The function to call before sending the packet. Is passed the packet,
- * and the return value is used as the new packet. The |this| context is
- * the instance of the client object we are defining a method for.
- * @param after
- * The function to call after the response is received. It is passed the
- * response, and the return value is considered the new response that
- * will be passed to the callback. The |this| context is the instance of
- * the client object we are defining a method for.
- * @return Request
- * The `Request` object that is a Promise object and resolves once
- * we receive the response. (See request method for more details)
- */
-DebuggerClient.requester = function (packetSkeleton, config = {}) {
- let { before, after } = config;
- return DevToolsUtils.makeInfallible(function (...args) {
- let outgoingPacket = {
- to: packetSkeleton.to || this.actor
- };
-
- let maxPosition = -1;
- for (let k of Object.keys(packetSkeleton)) {
- if (packetSkeleton[k] instanceof DebuggerClient.Argument) {
- let { position } = packetSkeleton[k];
- outgoingPacket[k] = packetSkeleton[k].getArgument(args);
- maxPosition = Math.max(position, maxPosition);
- } else {
- outgoingPacket[k] = packetSkeleton[k];
- }
- }
-
- if (before) {
- outgoingPacket = before.call(this, outgoingPacket);
- }
-
- return this.request(outgoingPacket, DevToolsUtils.makeInfallible((response) => {
- if (after) {
- let { from } = response;
- response = after.call(this, response);
- if (!response.from) {
- response.from = from;
- }
- }
-
- // The callback is always the last parameter.
- let thisCallback = args[maxPosition + 1];
- if (thisCallback) {
- thisCallback(response);
- }
- return response;
- }, "DebuggerClient.requester request callback"));
- }, "DebuggerClient.requester");
-};
-
-function arg(pos) {
- return new DebuggerClient.Argument(pos);
-}
-
-DebuggerClient.Argument = function (position) {
- this.position = position;
-};
-
-DebuggerClient.Argument.prototype.getArgument = function (params) {
- if (!(this.position in params)) {
- throw new Error("Bad index into params: " + this.position);
- }
- return params[this.position];
-};
-
-// Expose these to save callers the trouble of importing DebuggerSocket
-DebuggerClient.socketConnect = function (options) {
- // Defined here instead of just copying the function to allow lazy-load
- return DebuggerSocket.connect(options);
-};
-DevToolsUtils.defineLazyGetter(DebuggerClient, "Authenticators", () => {
- return Authentication.Authenticators;
-});
-DevToolsUtils.defineLazyGetter(DebuggerClient, "AuthenticationResult", () => {
- return Authentication.AuthenticationResult;
-});
-
-DebuggerClient.prototype = {
- /**
- * Connect to the server and start exchanging protocol messages.
- *
- * @param onConnected function
- * If specified, will be called when the greeting packet is
- * received from the debugging server.
- *
- * @return Promise
- * Resolves once connected with an array whose first element
- * is the application type, by default "browser", and the second
- * element is the traits object (help figure out the features
- * and behaviors of the server we connect to. See RootActor).
- */
- connect: function (onConnected) {
- let deferred = promise.defer();
- this.emit("connect");
-
- // Also emit the event on the |DebuggerClient| object (not on the instance),
- // so it's possible to track all instances.
- EventEmitter.emit(DebuggerClient, "connect", this);
-
- this.addOneTimeListener("connected", (name, applicationType, traits) => {
- this.traits = traits;
- if (onConnected) {
- onConnected(applicationType, traits);
- }
- deferred.resolve([applicationType, traits]);
- });
-
- this._transport.ready();
- return deferred.promise;
- },
-
- /**
- * Shut down communication with the debugging server.
- *
- * @param onClosed function
- * If specified, will be called when the debugging connection
- * has been closed. This parameter is deprecated - please use
- * the returned Promise.
- * @return Promise
- * Resolves after the underlying transport is closed.
- */
- close: function (onClosed) {
- let deferred = promise.defer();
- if (onClosed) {
- deferred.promise.then(onClosed);
- }
-
- // Disable detach event notifications, because event handlers will be in a
- // cleared scope by the time they run.
- this._eventsEnabled = false;
-
- let cleanup = () => {
- this._transport.close();
- this._transport = null;
- };
-
- // If the connection is already closed,
- // there is no need to detach client
- // as we won't be able to send any message.
- if (this._closed) {
- cleanup();
- deferred.resolve();
- return deferred.promise;
- }
-
- this.addOneTimeListener("closed", deferred.resolve);
-
- // Call each client's `detach` method by calling
- // lastly registered ones first to give a chance
- // to detach child clients first.
- let clients = [...this._clients.values()];
- this._clients.clear();
- const detachClients = () => {
- let client = clients.pop();
- if (!client) {
- // All clients detached.
- cleanup();
- return;
- }
- if (client.detach) {
- client.detach(detachClients);
- return;
- }
- detachClients();
- };
- detachClients();
-
- return deferred.promise;
- },
-
- /*
- * This function exists only to preserve DebuggerClient's interface;
- * new code should say 'client.mainRoot.listTabs()'.
- */
- listTabs: function (onResponse) {
- return this.mainRoot.listTabs(onResponse);
- },
-
- /*
- * This function exists only to preserve DebuggerClient's interface;
- * new code should say 'client.mainRoot.listAddons()'.
- */
- listAddons: function (onResponse) {
- return this.mainRoot.listAddons(onResponse);
- },
-
- getTab: function (filter) {
- return this.mainRoot.getTab(filter);
- },
-
- /**
- * Attach to a tab actor.
- *
- * @param string tabActor
- * The actor ID for the tab to attach.
- * @param function onResponse
- * Called with the response packet and a TabClient
- * (which will be undefined on error).
- */
- attachTab: function (tabActor, onResponse = noop) {
- if (this._clients.has(tabActor)) {
- let cachedTab = this._clients.get(tabActor);
- let cachedResponse = {
- cacheDisabled: cachedTab.cacheDisabled,
- javascriptEnabled: cachedTab.javascriptEnabled,
- traits: cachedTab.traits,
- };
- DevToolsUtils.executeSoon(() => onResponse(cachedResponse, cachedTab));
- return promise.resolve([cachedResponse, cachedTab]);
- }
-
- let packet = {
- to: tabActor,
- type: "attach"
- };
- return this.request(packet).then(response => {
- let tabClient;
- if (!response.error) {
- tabClient = new TabClient(this, response);
- this.registerClient(tabClient);
- }
- onResponse(response, tabClient);
- return [response, tabClient];
- });
- },
-
- attachWorker: function (workerActor, onResponse = noop) {
- let workerClient = this._clients.get(workerActor);
- if (workerClient !== undefined) {
- let response = {
- from: workerClient.actor,
- type: "attached",
- url: workerClient.url
- };
- DevToolsUtils.executeSoon(() => onResponse(response, workerClient));
- return promise.resolve([response, workerClient]);
- }
-
- return this.request({ to: workerActor, type: "attach" }).then(response => {
- if (response.error) {
- onResponse(response, null);
- return [response, null];
- }
-
- workerClient = new WorkerClient(this, response);
- this.registerClient(workerClient);
- onResponse(response, workerClient);
- return [response, workerClient];
- });
- },
-
- /**
- * Attach to an addon actor.
- *
- * @param string addonActor
- * The actor ID for the addon to attach.
- * @param function onResponse
- * Called with the response packet and a AddonClient
- * (which will be undefined on error).
- */
- attachAddon: function (addonActor, onResponse = noop) {
- let packet = {
- to: addonActor,
- type: "attach"
- };
- return this.request(packet).then(response => {
- let addonClient;
- if (!response.error) {
- addonClient = new AddonClient(this, addonActor);
- this.registerClient(addonClient);
- this.activeAddon = addonClient;
- }
- onResponse(response, addonClient);
- return [response, addonClient];
- });
- },
-
- /**
- * Attach to a Web Console actor.
- *
- * @param string consoleActor
- * The ID for the console actor to attach to.
- * @param array listeners
- * The console listeners you want to start.
- * @param function onResponse
- * Called with the response packet and a WebConsoleClient
- * instance (which will be undefined on error).
- */
- attachConsole:
- function (consoleActor, listeners, onResponse = noop) {
- let packet = {
- to: consoleActor,
- type: "startListeners",
- listeners: listeners,
- };
-
- return this.request(packet).then(response => {
- let consoleClient;
- if (!response.error) {
- if (this._clients.has(consoleActor)) {
- consoleClient = this._clients.get(consoleActor);
- } else {
- consoleClient = new WebConsoleClient(this, response);
- this.registerClient(consoleClient);
- }
- }
- onResponse(response, consoleClient);
- return [response, consoleClient];
- });
- },
-
- /**
- * Attach to a global-scoped thread actor for chrome debugging.
- *
- * @param string threadActor
- * The actor ID for the thread to attach.
- * @param function onResponse
- * Called with the response packet and a ThreadClient
- * (which will be undefined on error).
- * @param object options
- * Configuration options.
- * - useSourceMaps: whether to use source maps or not.
- */
- attachThread: function (threadActor, onResponse = noop, options = {}) {
- if (this._clients.has(threadActor)) {
- let client = this._clients.get(threadActor);
- DevToolsUtils.executeSoon(() => onResponse({}, client));
- return promise.resolve([{}, client]);
- }
-
- let packet = {
- to: threadActor,
- type: "attach",
- options,
- };
- return this.request(packet).then(response => {
- let threadClient;
- if (!response.error) {
- threadClient = new ThreadClient(this, threadActor);
- this.registerClient(threadClient);
- }
- onResponse(response, threadClient);
- return [response, threadClient];
- });
- },
-
- /**
- * Attach to a trace actor.
- *
- * @param string traceActor
- * The actor ID for the tracer to attach.
- * @param function onResponse
- * Called with the response packet and a TraceClient
- * (which will be undefined on error).
- */
- attachTracer: function (traceActor, onResponse = noop) {
- if (this._clients.has(traceActor)) {
- let client = this._clients.get(traceActor);
- DevToolsUtils.executeSoon(() => onResponse({}, client));
- return promise.resolve([{}, client]);
- }
-
- let packet = {
- to: traceActor,
- type: "attach"
- };
- return this.request(packet).then(response => {
- let traceClient;
- if (!response.error) {
- traceClient = new TraceClient(this, traceActor);
- this.registerClient(traceClient);
- }
- onResponse(response, traceClient);
- return [response, traceClient];
- });
- },
-
- /**
- * Fetch the ChromeActor for the main process or ChildProcessActor for a
- * a given child process ID.
- *
- * @param number id
- * The ID for the process to attach (returned by `listProcesses`).
- * Connected to the main process if omitted, or is 0.
- */
- getProcess: function (id) {
- let packet = {
- to: "root",
- type: "getProcess"
- };
- if (typeof (id) == "number") {
- packet.id = id;
- }
- return this.request(packet);
- },
-
- /**
- * Release an object actor.
- *
- * @param string actor
- * The actor ID to send the request to.
- * @param onResponse function
- * If specified, will be called with the response packet when
- * debugging server responds.
- */
- release: DebuggerClient.requester({
- to: arg(0),
- type: "release"
- }),
-
- /**
- * Send a request to the debugging server.
- *
- * @param packet object
- * A JSON packet to send to the debugging server.
- * @param onResponse function
- * If specified, will be called with the JSON response packet when
- * debugging server responds.
- * @return Request
- * This object emits a number of events to allow you to respond to
- * different parts of the request lifecycle.
- * It is also a Promise object, with a `then` method, that is resolved
- * whenever a JSON or a Bulk response is received; and is rejected
- * if the response is an error.
- * Note: This return value can be ignored if you are using JSON alone,
- * because the callback provided in |onResponse| will be bound to the
- * "json-reply" event automatically.
- *
- * Events emitted:
- * * json-reply: The server replied with a JSON packet, which is
- * passed as event data.
- * * bulk-reply: The server replied with bulk data, which you can read
- * using the event data object containing:
- * * actor: Name of actor that received the packet
- * * type: Name of actor's method that was called on receipt
- * * length: Size of the data to be read
- * * stream: This input stream should only be used directly if you
- * can ensure that you will read exactly |length| bytes
- * and will not close the stream when reading is complete
- * * done: If you use the stream directly (instead of |copyTo|
- * below), you must signal completion by resolving /
- * rejecting this deferred. If it's rejected, the
- * transport will be closed. If an Error is supplied as a
- * rejection value, it will be logged via |dumpn|. If you
- * do use |copyTo|, resolving is taken care of for you
- * when copying completes.
- * * copyTo: A helper function for getting your data out of the
- * stream that meets the stream handling requirements
- * above, and has the following signature:
- * @param output nsIAsyncOutputStream
- * The stream to copy to.
- * @return Promise
- * The promise is resolved when copying completes or
- * rejected if any (unexpected) errors occur.
- * This object also emits "progress" events for each chunk
- * that is copied. See stream-utils.js.
- */
- request: function (packet, onResponse) {
- if (!this.mainRoot) {
- throw Error("Have not yet received a hello packet from the server.");
- }
- let type = packet.type || "";
- if (!packet.to) {
- throw Error("'" + type + "' request packet has no destination.");
- }
-
- // The onResponse callback might modify the response, so we need to call
- // it and resolve the promise with its result if it's truthy.
- const safeOnResponse = response => {
- if (!onResponse) {
- return response;
- }
- return onResponse(response) || response;
- };
-
- if (this._closed) {
- let msg = "'" + type + "' request packet to " +
- "'" + packet.to + "' " +
- "can't be sent as the connection is closed.";
- let resp = { error: "connectionClosed", message: msg };
- return promise.reject(safeOnResponse(resp));
- }
-
- let request = new Request(packet);
- request.format = "json";
- request.stack = getStack();
-
- // Implement a Promise like API on the returned object
- // that resolves/rejects on request response
- let deferred = promise.defer();
- function listenerJson(resp) {
- removeRequestListeners();
- if (resp.error) {
- deferred.reject(safeOnResponse(resp));
- } else {
- deferred.resolve(safeOnResponse(resp));
- }
- }
- function listenerBulk(resp) {
- removeRequestListeners();
- deferred.resolve(safeOnResponse(resp));
- }
-
- const removeRequestListeners = () => {
- request.off("json-reply", listenerJson);
- request.off("bulk-reply", listenerBulk);
- };
-
- request.on("json-reply", listenerJson);
- request.on("bulk-reply", listenerBulk);
-
- this._sendOrQueueRequest(request);
- request.then = deferred.promise.then.bind(deferred.promise);
-
- return request;
- },
-
- /**
- * Transmit streaming data via a bulk request.
- *
- * This method initiates the bulk send process by queuing up the header data.
- * The caller receives eventual access to a stream for writing.
- *
- * Since this opens up more options for how the server might respond (it could
- * send back either JSON or bulk data), and the returned Request object emits
- * events for different stages of the request process that you may want to
- * react to.
- *
- * @param request Object
- * This is modeled after the format of JSON packets above, but does not
- * actually contain the data, but is instead just a routing header:
- * * actor: Name of actor that will receive the packet
- * * type: Name of actor's method that should be called on receipt
- * * length: Size of the data to be sent
- * @return Request
- * This object emits a number of events to allow you to respond to
- * different parts of the request lifecycle.
- *
- * Events emitted:
- * * bulk-send-ready: Ready to send bulk data to the server, using the
- * event data object containing:
- * * stream: This output stream should only be used directly if
- * you can ensure that you will write exactly |length|
- * bytes and will not close the stream when writing is
- * complete
- * * done: If you use the stream directly (instead of |copyFrom|
- * below), you must signal completion by resolving /
- * rejecting this deferred. If it's rejected, the
- * transport will be closed. If an Error is supplied as
- * a rejection value, it will be logged via |dumpn|. If
- * you do use |copyFrom|, resolving is taken care of for
- * you when copying completes.
- * * copyFrom: A helper function for getting your data onto the
- * stream that meets the stream handling requirements
- * above, and has the following signature:
- * @param input nsIAsyncInputStream
- * The stream to copy from.
- * @return Promise
- * The promise is resolved when copying completes or
- * rejected if any (unexpected) errors occur.
- * This object also emits "progress" events for each chunk
- * that is copied. See stream-utils.js.
- * * json-reply: The server replied with a JSON packet, which is
- * passed as event data.
- * * bulk-reply: The server replied with bulk data, which you can read
- * using the event data object containing:
- * * actor: Name of actor that received the packet
- * * type: Name of actor's method that was called on receipt
- * * length: Size of the data to be read
- * * stream: This input stream should only be used directly if you
- * can ensure that you will read exactly |length| bytes
- * and will not close the stream when reading is complete
- * * done: If you use the stream directly (instead of |copyTo|
- * below), you must signal completion by resolving /
- * rejecting this deferred. If it's rejected, the
- * transport will be closed. If an Error is supplied as a
- * rejection value, it will be logged via |dumpn|. If you
- * do use |copyTo|, resolving is taken care of for you
- * when copying completes.
- * * copyTo: A helper function for getting your data out of the
- * stream that meets the stream handling requirements
- * above, and has the following signature:
- * @param output nsIAsyncOutputStream
- * The stream to copy to.
- * @return Promise
- * The promise is resolved when copying completes or
- * rejected if any (unexpected) errors occur.
- * This object also emits "progress" events for each chunk
- * that is copied. See stream-utils.js.
- */
- startBulkRequest: function (request) {
- if (!this.traits.bulk) {
- throw Error("Server doesn't support bulk transfers");
- }
- if (!this.mainRoot) {
- throw Error("Have not yet received a hello packet from the server.");
- }
- if (!request.type) {
- throw Error("Bulk packet is missing the required 'type' field.");
- }
- if (!request.actor) {
- throw Error("'" + request.type + "' bulk packet has no destination.");
- }
- if (!request.length) {
- throw Error("'" + request.type + "' bulk packet has no length.");
- }
-
- request = new Request(request);
- request.format = "bulk";
-
- this._sendOrQueueRequest(request);
-
- return request;
- },
-
- /**
- * If a new request can be sent immediately, do so. Otherwise, queue it.
- */
- _sendOrQueueRequest(request) {
- let actor = request.actor;
- if (!this._activeRequests.has(actor)) {
- this._sendRequest(request);
- } else {
- this._queueRequest(request);
- }
- },
-
- /**
- * Send a request.
- * @throws Error if there is already an active request in flight for the same
- * actor.
- */
- _sendRequest(request) {
- let actor = request.actor;
- this.expectReply(actor, request);
-
- if (request.format === "json") {
- this._transport.send(request.request);
- return;
- }
-
- this._transport.startBulkSend(request.request).then((...args) => {
- request.emit("bulk-send-ready", ...args);
- });
- },
-
- /**
- * Queue a request to be sent later. Queues are only drained when an in
- * flight request to a given actor completes.
- */
- _queueRequest(request) {
- let actor = request.actor;
- let queue = this._pendingRequests.get(actor) || [];
- queue.push(request);
- this._pendingRequests.set(actor, queue);
- },
-
- /**
- * Attempt the next request to a given actor (if any).
- */
- _attemptNextRequest(actor) {
- if (this._activeRequests.has(actor)) {
- return;
- }
- let queue = this._pendingRequests.get(actor);
- if (!queue) {
- return;
- }
- let request = queue.shift();
- if (queue.length === 0) {
- this._pendingRequests.delete(actor);
- }
- this._sendRequest(request);
- },
-
- /**
- * Arrange to hand the next reply from |actor| to the handler bound to
- * |request|.
- *
- * DebuggerClient.prototype.request / startBulkRequest usually takes care of
- * establishing the handler for a given request, but in rare cases (well,
- * greetings from new root actors, is the only case at the moment) we must be
- * prepared for a "reply" that doesn't correspond to any request we sent.
- */
- expectReply: function (actor, request) {
- if (this._activeRequests.has(actor)) {
- throw Error("clashing handlers for next reply from " + actor);
- }
-
- // If a handler is passed directly (as it is with the handler for the root
- // actor greeting), create a dummy request to bind this to.
- if (typeof request === "function") {
- let handler = request;
- request = new Request();
- request.on("json-reply", handler);
- }
-
- this._activeRequests.set(actor, request);
- },
-
- // Transport hooks.
-
- /**
- * Called by DebuggerTransport to dispatch incoming packets as appropriate.
- *
- * @param packet object
- * The incoming packet.
- */
- onPacket: function (packet) {
- if (!packet.from) {
- DevToolsUtils.reportException(
- "onPacket",
- new Error("Server did not specify an actor, dropping packet: " +
- JSON.stringify(packet)));
- return;
- }
-
- // If we have a registered Front for this actor, let it handle the packet
- // and skip all the rest of this unpleasantness.
- let front = this.getActor(packet.from);
- if (front) {
- front.onPacket(packet);
- return;
- }
-
- // Check for "forwardingCancelled" here instead of using a client to handle it.
- // This is necessary because we might receive this event while the client is closing,
- // and the clients have already been removed by that point.
- if (this.mainRoot &&
- packet.from == this.mainRoot.actor &&
- packet.type == "forwardingCancelled") {
- this.purgeRequests(packet.prefix);
- return;
- }
-
- if (this._clients.has(packet.from) && packet.type) {
- let client = this._clients.get(packet.from);
- let type = packet.type;
- if (client.events.indexOf(type) != -1) {
- client.emit(type, packet);
- // we ignore the rest, as the client is expected to handle this packet.
- return;
- }
- }
-
- let activeRequest;
- // See if we have a handler function waiting for a reply from this
- // actor. (Don't count unsolicited notifications or pauses as
- // replies.)
- if (this._activeRequests.has(packet.from) &&
- !(packet.type in UnsolicitedNotifications) &&
- !(packet.type == ThreadStateTypes.paused &&
- packet.why.type in UnsolicitedPauses)) {
- activeRequest = this._activeRequests.get(packet.from);
- this._activeRequests.delete(packet.from);
- }
-
- // If there is a subsequent request for the same actor, hand it off to the
- // transport. Delivery of packets on the other end is always async, even
- // in the local transport case.
- this._attemptNextRequest(packet.from);
-
- // Packets that indicate thread state changes get special treatment.
- if (packet.type in ThreadStateTypes &&
- this._clients.has(packet.from) &&
- typeof this._clients.get(packet.from)._onThreadState == "function") {
- this._clients.get(packet.from)._onThreadState(packet);
- }
-
- // TODO: Bug 1151156 - Remove once Gecko 40 is on b2g-stable.
- if (!this.traits.noNeedToFakeResumptionOnNavigation) {
- // On navigation the server resumes, so the client must resume as well.
- // We achieve that by generating a fake resumption packet that triggers
- // the client's thread state change listeners.
- if (packet.type == UnsolicitedNotifications.tabNavigated &&
- this._clients.has(packet.from) &&
- this._clients.get(packet.from).thread) {
- let thread = this._clients.get(packet.from).thread;
- let resumption = { from: thread._actor, type: "resumed" };
- thread._onThreadState(resumption);
- }
- }
-
- // Only try to notify listeners on events, not responses to requests
- // that lack a packet type.
- if (packet.type) {
- this.emit(packet.type, packet);
- }
-
- if (activeRequest) {
- let emitReply = () => activeRequest.emit("json-reply", packet);
- if (activeRequest.stack) {
- callFunctionWithAsyncStack(emitReply, activeRequest.stack,
- "DevTools RDP");
- } else {
- emitReply();
- }
- }
- },
-
- /**
- * Called by the DebuggerTransport to dispatch incoming bulk packets as
- * appropriate.
- *
- * @param packet object
- * The incoming packet, which contains:
- * * actor: Name of actor that will receive the packet
- * * type: Name of actor's method that should be called on receipt
- * * length: Size of the data to be read
- * * stream: This input stream should only be used directly if you can
- * ensure that you will read exactly |length| bytes and will
- * not close the stream when reading is complete
- * * done: If you use the stream directly (instead of |copyTo|
- * below), you must signal completion by resolving /
- * rejecting this deferred. If it's rejected, the transport
- * will be closed. If an Error is supplied as a rejection
- * value, it will be logged via |dumpn|. If you do use
- * |copyTo|, resolving is taken care of for you when copying
- * completes.
- * * copyTo: A helper function for getting your data out of the stream
- * that meets the stream handling requirements above, and has
- * the following signature:
- * @param output nsIAsyncOutputStream
- * The stream to copy to.
- * @return Promise
- * The promise is resolved when copying completes or rejected
- * if any (unexpected) errors occur.
- * This object also emits "progress" events for each chunk
- * that is copied. See stream-utils.js.
- */
- onBulkPacket: function (packet) {
- let { actor } = packet;
-
- if (!actor) {
- DevToolsUtils.reportException(
- "onBulkPacket",
- new Error("Server did not specify an actor, dropping bulk packet: " +
- JSON.stringify(packet)));
- return;
- }
-
- // See if we have a handler function waiting for a reply from this
- // actor.
- if (!this._activeRequests.has(actor)) {
- return;
- }
-
- let activeRequest = this._activeRequests.get(actor);
- this._activeRequests.delete(actor);
-
- // If there is a subsequent request for the same actor, hand it off to the
- // transport. Delivery of packets on the other end is always async, even
- // in the local transport case.
- this._attemptNextRequest(actor);
-
- activeRequest.emit("bulk-reply", packet);
- },
-
- /**
- * Called by DebuggerTransport when the underlying stream is closed.
- *
- * @param status nsresult
- * The status code that corresponds to the reason for closing
- * the stream.
- */
- onClosed: function () {
- this._closed = true;
- this.emit("closed");
-
- this.purgeRequests();
-
- // The |_pools| array on the client-side currently is used only by
- // protocol.js to store active fronts, mirroring the actor pools found in
- // the server. So, read all usages of "pool" as "protocol.js front".
- //
- // In the normal case where we shutdown cleanly, the toolbox tells each tool
- // to close, and they each call |destroy| on any fronts they were using.
- // When |destroy| or |cleanup| is called on a protocol.js front, it also
- // removes itself from the |_pools| array. Once the toolbox has shutdown,
- // the connection is closed, and we reach here. All fronts (should have
- // been) |destroy|ed, so |_pools| should empty.
- //
- // If the connection instead aborts unexpectedly, we may end up here with
- // all fronts used during the life of the connection. So, we call |cleanup|
- // on them clear their state, reject pending requests, and remove themselves
- // from |_pools|. This saves the toolbox from hanging indefinitely, in case
- // it waits for some server response before shutdown that will now never
- // arrive.
- for (let pool of this._pools) {
- pool.cleanup();
- }
- },
-
- /**
- * Purge pending and active requests in this client.
- *
- * @param prefix string (optional)
- * If a prefix is given, only requests for actor IDs that start with the prefix
- * will be cleaned up. This is useful when forwarding of a portion of requests
- * is cancelled on the server.
- */
- purgeRequests(prefix = "") {
- let reject = function (type, request) {
- // Server can send packets on its own and client only pass a callback
- // to expectReply, so that there is no request object.
- let msg;
- if (request.request) {
- msg = "'" + request.request.type + "' " + type + " request packet" +
- " to '" + request.actor + "' " +
- "can't be sent as the connection just closed.";
- } else {
- msg = "server side packet can't be received as the connection just closed.";
- }
- let packet = { error: "connectionClosed", message: msg };
- request.emit("json-reply", packet);
- };
-
- let pendingRequestsToReject = [];
- this._pendingRequests.forEach((requests, actor) => {
- if (!actor.startsWith(prefix)) {
- return;
- }
- this._pendingRequests.delete(actor);
- pendingRequestsToReject = pendingRequestsToReject.concat(requests);
- });
- pendingRequestsToReject.forEach(request => reject("pending", request));
-
- let activeRequestsToReject = [];
- this._activeRequests.forEach((request, actor) => {
- if (!actor.startsWith(prefix)) {
- return;
- }
- this._activeRequests.delete(actor);
- activeRequestsToReject = activeRequestsToReject.concat(request);
- });
- activeRequestsToReject.forEach(request => reject("active", request));
- },
-
- /**
- * Search for all requests in process for this client, including those made via
- * protocol.js and wait all of them to complete. Since the requests seen when this is
- * first called may in turn trigger more requests, we keep recursing through this
- * function until there is no more activity.
- *
- * This is a fairly heavy weight process, so it's only meant to be used in tests.
- *
- * @return Promise
- * Resolved when all requests have settled.
- */
- waitForRequestsToSettle() {
- let requests = [];
-
- // Gather all pending and active requests in this client
- // The request object supports a Promise API for completion (it has .then())
- this._pendingRequests.forEach(requestsForActor => {
- // Each value is an array of pending requests
- requests = requests.concat(requestsForActor);
- });
- this._activeRequests.forEach(requestForActor => {
- // Each value is a single active request
- requests = requests.concat(requestForActor);
- });
-
- // protocol.js
- // Use a Set because some fronts (like domwalker) seem to have multiple parents.
- let fronts = new Set();
- let poolsToVisit = [...this._pools];
-
- // With protocol.js, each front can potentially have it's own pools containing child
- // fronts, forming a tree. Descend through all the pools to locate all child fronts.
- while (poolsToVisit.length) {
- let pool = poolsToVisit.shift();
- fronts.add(pool);
- for (let child of pool.poolChildren()) {
- poolsToVisit.push(child);
- }
- }
-
- // For each front, wait for its requests to settle
- for (let front of fronts) {
- if (front.hasRequests()) {
- requests.push(front.waitForRequestsToSettle());
- }
- }
-
- // Abort early if there are no requests
- if (!requests.length) {
- return Promise.resolve();
- }
-
- return DevToolsUtils.settleAll(requests).catch(() => {
- // One of the requests might have failed, but ignore that situation here and pipe
- // both success and failure through the same path. The important part is just that
- // we waited.
- }).then(() => {
- // Repeat, more requests may have started in response to those we just waited for
- return this.waitForRequestsToSettle();
- });
- },
-
- registerClient: function (client) {
- let actorID = client.actor;
- if (!actorID) {
- throw new Error("DebuggerServer.registerClient expects " +
- "a client instance with an `actor` attribute.");
- }
- if (!Array.isArray(client.events)) {
- throw new Error("DebuggerServer.registerClient expects " +
- "a client instance with an `events` attribute " +
- "that is an array.");
- }
- if (client.events.length > 0 && typeof (client.emit) != "function") {
- throw new Error("DebuggerServer.registerClient expects " +
- "a client instance with non-empty `events` array to" +
- "have an `emit` function.");
- }
- if (this._clients.has(actorID)) {
- throw new Error("DebuggerServer.registerClient already registered " +
- "a client for this actor.");
- }
- this._clients.set(actorID, client);
- },
-
- unregisterClient: function (client) {
- let actorID = client.actor;
- if (!actorID) {
- throw new Error("DebuggerServer.unregisterClient expects " +
- "a Client instance with a `actor` attribute.");
- }
- this._clients.delete(actorID);
- },
-
- /**
- * Actor lifetime management, echos the server's actor pools.
- */
- __pools: null,
- get _pools() {
- if (this.__pools) {
- return this.__pools;
- }
- this.__pools = new Set();
- return this.__pools;
- },
-
- addActorPool: function (pool) {
- this._pools.add(pool);
- },
- removeActorPool: function (pool) {
- this._pools.delete(pool);
- },
- getActor: function (actorID) {
- let pool = this.poolFor(actorID);
- return pool ? pool.get(actorID) : null;
- },
-
- poolFor: function (actorID) {
- for (let pool of this._pools) {
- if (pool.has(actorID)) {
- return pool;
- }
- }
- return null;
- },
-
- /**
- * Currently attached addon.
- */
- activeAddon: null
-};
-
-eventSource(DebuggerClient.prototype);
-
-class Request extends EventEmitter {
- constructor(request) {
- super();
- this.request = request;
- }
-
- get actor() {
- return this.request.to || this.request.actor;
- }
-}
-
-/**
- * Creates a tab client for the remote debugging protocol server. This client
- * is a front to the tab actor created in the server side, hiding the protocol
- * details in a traditional JavaScript API.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param form object
- * The protocol form for this tab.
- */
-function TabClient(client, form) {
- this.client = client;
- this._actor = form.from;
- this._threadActor = form.threadActor;
- this.javascriptEnabled = form.javascriptEnabled;
- this.cacheDisabled = form.cacheDisabled;
- this.thread = null;
- this.request = this.client.request;
- this.traits = form.traits || {};
- this.events = ["workerListChanged"];
-}
-
-TabClient.prototype = {
- get actor() {
- return this._actor;
- },
- get _transport() {
- return this.client._transport;
- },
-
- /**
- * Attach to a thread actor.
- *
- * @param object options
- * Configuration options.
- * - useSourceMaps: whether to use source maps or not.
- * @param function onResponse
- * Called with the response packet and a ThreadClient
- * (which will be undefined on error).
- */
- attachThread: function (options = {}, onResponse = noop) {
- if (this.thread) {
- DevToolsUtils.executeSoon(() => onResponse({}, this.thread));
- return promise.resolve([{}, this.thread]);
- }
-
- let packet = {
- to: this._threadActor,
- type: "attach",
- options,
- };
- return this.request(packet).then(response => {
- if (!response.error) {
- this.thread = new ThreadClient(this, this._threadActor);
- this.client.registerClient(this.thread);
- }
- onResponse(response, this.thread);
- return [response, this.thread];
- });
- },
-
- /**
- * Detach the client from the tab actor.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- detach: DebuggerClient.requester({
- type: "detach"
- }, {
- before: function (packet) {
- if (this.thread) {
- this.thread.detach();
- }
- return packet;
- },
- after: function (response) {
- this.client.unregisterClient(this);
- return response;
- },
- }),
-
- /**
- * Bring the window to the front.
- */
- focus: DebuggerClient.requester({
- type: "focus"
- }, {}),
-
- /**
- * Reload the page in this tab.
- *
- * @param [optional] object options
- * An object with a `force` property indicating whether or not
- * this reload should skip the cache
- */
- reload: function (options = { force: false }) {
- return this._reload(options);
- },
- _reload: DebuggerClient.requester({
- type: "reload",
- options: arg(0)
- }),
-
- /**
- * Navigate to another URL.
- *
- * @param string url
- * The URL to navigate to.
- */
- navigateTo: DebuggerClient.requester({
- type: "navigateTo",
- url: arg(0)
- }),
-
- /**
- * Reconfigure the tab actor.
- *
- * @param object options
- * A dictionary object of the new options to use in the tab actor.
- * @param function onResponse
- * Called with the response packet.
- */
- reconfigure: DebuggerClient.requester({
- type: "reconfigure",
- options: arg(0)
- }),
-
- listWorkers: DebuggerClient.requester({
- type: "listWorkers"
- }),
-
- attachWorker: function (workerActor, onResponse) {
- return this.client.attachWorker(workerActor, onResponse);
- },
-};
-
-eventSource(TabClient.prototype);
-
-function WorkerClient(client, form) {
- this.client = client;
- this._actor = form.from;
- this._isClosed = false;
- this._url = form.url;
-
- this._onClose = this._onClose.bind(this);
-
- this.addListener("close", this._onClose);
-
- this.traits = {};
-}
-
-WorkerClient.prototype = {
- get _transport() {
- return this.client._transport;
- },
-
- get request() {
- return this.client.request;
- },
-
- get actor() {
- return this._actor;
- },
-
- get url() {
- return this._url;
- },
-
- get isClosed() {
- return this._isClosed;
- },
-
- detach: DebuggerClient.requester({ type: "detach" }, {
- after: function (response) {
- if (this.thread) {
- this.client.unregisterClient(this.thread);
- }
- this.client.unregisterClient(this);
- return response;
- },
- }),
-
- attachThread: function (options = {}, onResponse = noop) {
- if (this.thread) {
- let response = [{
- type: "connected",
- threadActor: this.thread._actor,
- consoleActor: this.consoleActor,
- }, this.thread];
- DevToolsUtils.executeSoon(() => onResponse(response));
- return response;
- }
-
- // The connect call on server doesn't attach the thread as of version 44.
- return this.request({
- to: this._actor,
- type: "connect",
- options,
- }).then(connectResponse => {
- if (connectResponse.error) {
- onResponse(connectResponse, null);
- return [connectResponse, null];
- }
-
- return this.request({
- to: connectResponse.threadActor,
- type: "attach",
- options,
- }).then(attachResponse => {
- if (attachResponse.error) {
- onResponse(attachResponse, null);
- }
-
- this.thread = new ThreadClient(this, connectResponse.threadActor);
- this.consoleActor = connectResponse.consoleActor;
- this.client.registerClient(this.thread);
-
- onResponse(connectResponse, this.thread);
- return [connectResponse, this.thread];
- });
- }, error => {
- onResponse(error, null);
- });
- },
-
- _onClose: function () {
- this.removeListener("close", this._onClose);
-
- if (this.thread) {
- this.client.unregisterClient(this.thread);
- }
- this.client.unregisterClient(this);
- this._isClosed = true;
- },
-
- reconfigure: function () {
- return Promise.resolve();
- },
-
- events: ["close"]
-};
-
-eventSource(WorkerClient.prototype);
-
-function AddonClient(client, actor) {
- this._client = client;
- this._actor = actor;
- this.request = this._client.request;
- this.events = [];
-}
-
-AddonClient.prototype = {
- get actor() {
- return this._actor;
- },
- get _transport() {
- return this._client._transport;
- },
-
- /**
- * Detach the client from the addon actor.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- detach: DebuggerClient.requester({
- type: "detach"
- }, {
- after: function (response) {
- if (this._client.activeAddon === this) {
- this._client.activeAddon = null;
- }
- this._client.unregisterClient(this);
- return response;
- },
- })
-};
-
-/**
- * A RootClient object represents a root actor on the server. Each
- * DebuggerClient keeps a RootClient instance representing the root actor
- * for the initial connection; DebuggerClient's 'listTabs' and
- * 'listChildProcesses' methods forward to that root actor.
- *
- * @param client object
- * The client connection to which this actor belongs.
- * @param greeting string
- * The greeting packet from the root actor we're to represent.
- *
- * Properties of a RootClient instance:
- *
- * @property actor string
- * The name of this child's root actor.
- * @property applicationType string
- * The application type, as given in the root actor's greeting packet.
- * @property traits object
- * The traits object, as given in the root actor's greeting packet.
- */
-function RootClient(client, greeting) {
- this._client = client;
- this.actor = greeting.from;
- this.applicationType = greeting.applicationType;
- this.traits = greeting.traits;
-}
-exports.RootClient = RootClient;
-
-RootClient.prototype = {
- constructor: RootClient,
-
- /**
- * Gets the "root" form, which lists all the global actors that affect the entire
- * browser. This can replace usages of `listTabs` that only wanted the global actors
- * and didn't actually care about tabs.
- */
- getRoot: DebuggerClient.requester({ type: "getRoot" }),
-
- /**
- * List the open tabs.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- listTabs: DebuggerClient.requester({ type: "listTabs" }),
-
- /**
- * List the installed addons.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- listAddons: DebuggerClient.requester({ type: "listAddons" }),
-
- /**
- * List the registered workers.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- listWorkers: DebuggerClient.requester({ type: "listWorkers" }),
-
- /**
- * List the registered service workers.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- listServiceWorkerRegistrations: DebuggerClient.requester({
- type: "listServiceWorkerRegistrations"
- }),
-
- /**
- * List the running processes.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- listProcesses: DebuggerClient.requester({ type: "listProcesses" }),
-
- /**
- * Fetch the TabActor for the currently selected tab, or for a specific
- * tab given as first parameter.
- *
- * @param [optional] object filter
- * A dictionary object with following optional attributes:
- * - outerWindowID: used to match tabs in parent process
- * - tabId: used to match tabs in child processes
- * - tab: a reference to xul:tab element
- * If nothing is specified, returns the actor for the currently
- * selected tab.
- */
- getTab: function (filter) {
- let packet = {
- to: this.actor,
- type: "getTab"
- };
-
- if (filter) {
- if (typeof (filter.outerWindowID) == "number") {
- packet.outerWindowID = filter.outerWindowID;
- } else if (typeof (filter.tabId) == "number") {
- packet.tabId = filter.tabId;
- } else if ("tab" in filter) {
- let browser = filter.tab.linkedBrowser;
- if (browser.frameLoader.tabParent) {
- // Tabs in child process
- packet.tabId = browser.frameLoader.tabParent.tabId;
- } else if (browser.outerWindowID) {
- // <xul:browser> tabs in parent process
- packet.outerWindowID = browser.outerWindowID;
- } else {
- // <iframe mozbrowser> tabs in parent process
- let windowUtils = browser.contentWindow
- .QueryInterface(Ci.nsIInterfaceRequestor)
- .getInterface(Ci.nsIDOMWindowUtils);
- packet.outerWindowID = windowUtils.outerWindowID;
- }
- } else {
- // Throw if a filter object have been passed but without
- // any clearly idenfified filter.
- throw new Error("Unsupported argument given to getTab request");
- }
- }
-
- return this.request(packet);
- },
-
- /**
- * Fetch the WindowActor for a specific window, like a browser window in
- * Firefox, but it can be used to reach any window in the process.
- *
- * @param number outerWindowID
- * The outerWindowID of the top level window you are looking for.
- */
- getWindow: function ({ outerWindowID }) {
- if (!outerWindowID) {
- throw new Error("Must specify outerWindowID");
- }
-
- let packet = {
- to: this.actor,
- type: "getWindow",
- outerWindowID,
- };
-
- return this.request(packet);
- },
-
- /**
- * Description of protocol's actors and methods.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- protocolDescription: DebuggerClient.requester({ type: "protocolDescription" }),
-
- /*
- * Methods constructed by DebuggerClient.requester require these forwards
- * on their 'this'.
- */
- get _transport() {
- return this._client._transport;
- },
- get request() {
- return this._client.request;
- }
-};
-
-/**
* Creates a thread client for the remote debugging protocol server. This client
* is a front to the thread actor created in the server side, hiding the
* protocol details in a traditional JavaScript API.
*
* @param client DebuggerClient|TabClient
* The parent of the thread (tab for tab-scoped debuggers, DebuggerClient
* for chrome debuggers).
* @param actor string
@@ -2404,1023 +655,9 @@ ThreadClient.prototype = {
actors: arg(0)
}),
events: ["newSource"]
};
eventSource(ThreadClient.prototype);
-/**
- * Creates a tracing profiler client for the remote debugging protocol
- * server. This client is a front to the trace actor created on the
- * server side, hiding the protocol details in a traditional
- * JavaScript API.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param actor string
- * The actor ID for this thread.
- */
-function TraceClient(client, actor) {
- this._client = client;
- this._actor = actor;
- this._activeTraces = new Set();
- this._waitingPackets = new Map();
- this._expectedPacket = 0;
- this.request = this._client.request;
- this.events = [];
-}
-
-TraceClient.prototype = {
- get actor() {
- return this._actor;
- },
- get tracing() {
- return this._activeTraces.size > 0;
- },
-
- get _transport() {
- return this._client._transport;
- },
-
- /**
- * Detach from the trace actor.
- */
- detach: DebuggerClient.requester({
- type: "detach"
- }, {
- after: function (response) {
- this._client.unregisterClient(this);
- return response;
- },
- }),
-
- /**
- * Start a new trace.
- *
- * @param trace [string]
- * An array of trace types to be recorded by the new trace.
- *
- * @param name string
- * The name of the new trace.
- *
- * @param onResponse function
- * Called with the request's response.
- */
- startTrace: DebuggerClient.requester({
- type: "startTrace",
- name: arg(1),
- trace: arg(0)
- }, {
- after: function (response) {
- if (response.error) {
- return response;
- }
-
- if (!this.tracing) {
- this._waitingPackets.clear();
- this._expectedPacket = 0;
- }
- this._activeTraces.add(response.name);
-
- return response;
- },
- }),
-
- /**
- * End a trace. If a name is provided, stop the named
- * trace. Otherwise, stop the most recently started trace.
- *
- * @param name string
- * The name of the trace to stop.
- *
- * @param onResponse function
- * Called with the request's response.
- */
- stopTrace: DebuggerClient.requester({
- type: "stopTrace",
- name: arg(0)
- }, {
- after: function (response) {
- if (response.error) {
- return response;
- }
-
- this._activeTraces.delete(response.name);
-
- return response;
- },
- })
-};
-
-/**
- * Grip clients are used to retrieve information about the relevant object.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param grip object
- * A pause-lifetime object grip returned by the protocol.
- */
-function ObjectClient(client, grip) {
- this._grip = grip;
- this._client = client;
- this.request = this._client.request;
-}
-exports.ObjectClient = ObjectClient;
-
-ObjectClient.prototype = {
- get actor() {
- return this._grip.actor;
- },
- get _transport() {
- return this._client._transport;
- },
-
- valid: true,
-
- get isFrozen() {
- return this._grip.frozen;
- },
- get isSealed() {
- return this._grip.sealed;
- },
- get isExtensible() {
- return this._grip.extensible;
- },
-
- getDefinitionSite: DebuggerClient.requester({
- type: "definitionSite"
- }, {
- before: function (packet) {
- if (this._grip.class != "Function") {
- throw new Error("getDefinitionSite is only valid for function grips.");
- }
- return packet;
- }
- }),
-
- /**
- * Request the names of a function's formal parameters.
- *
- * @param onResponse function
- * Called with an object of the form:
- * { parameterNames:[<parameterName>, ...] }
- * where each <parameterName> is the name of a parameter.
- */
- getParameterNames: DebuggerClient.requester({
- type: "parameterNames"
- }, {
- before: function (packet) {
- if (this._grip.class !== "Function") {
- throw new Error("getParameterNames is only valid for function grips.");
- }
- return packet;
- },
- }),
-
- /**
- * Request the names of the properties defined on the object and not its
- * prototype.
- *
- * @param onResponse function Called with the request's response.
- */
- getOwnPropertyNames: DebuggerClient.requester({
- type: "ownPropertyNames"
- }),
-
- /**
- * Request the prototype and own properties of the object.
- *
- * @param onResponse function Called with the request's response.
- */
- getPrototypeAndProperties: DebuggerClient.requester({
- type: "prototypeAndProperties"
- }),
-
- /**
- * Request a PropertyIteratorClient instance to ease listing
- * properties for this object.
- *
- * @param options Object
- * A dictionary object with various boolean attributes:
- * - ignoreIndexedProperties Boolean
- * If true, filters out Array items.
- * e.g. properties names between `0` and `object.length`.
- * - ignoreNonIndexedProperties Boolean
- * If true, filters out items that aren't array items
- * e.g. properties names that are not a number between `0`
- * and `object.length`.
- * - sort Boolean
- * If true, the iterator will sort the properties by name
- * before dispatching them.
- * @param onResponse function Called with the client instance.
- */
- enumProperties: DebuggerClient.requester({
- type: "enumProperties",
- options: arg(0)
- }, {
- after: function (response) {
- if (response.iterator) {
- return { iterator: new PropertyIteratorClient(this._client, response.iterator) };
- }
- return response;
- },
- }),
-
- /**
- * Request a PropertyIteratorClient instance to enumerate entries in a
- * Map/Set-like object.
- *
- * @param onResponse function Called with the request's response.
- */
- enumEntries: DebuggerClient.requester({
- type: "enumEntries"
- }, {
- before: function (packet) {
- if (!["Map", "WeakMap", "Set", "WeakSet"].includes(this._grip.class)) {
- throw new Error("enumEntries is only valid for Map/Set-like grips.");
- }
- return packet;
- },
- after: function (response) {
- if (response.iterator) {
- return {
- iterator: new PropertyIteratorClient(this._client, response.iterator)
- };
- }
- return response;
- }
- }),
-
- /**
- * Request a SymbolIteratorClient instance to enumerate symbols in an object.
- *
- * @param onResponse function Called with the request's response.
- */
- enumSymbols: DebuggerClient.requester({
- type: "enumSymbols"
- }, {
- before: function (packet) {
- if (this._grip.type !== "object") {
- throw new Error("enumSymbols is only valid for objects grips.");
- }
- return packet;
- },
- after: function (response) {
- if (response.iterator) {
- return {
- iterator: new SymbolIteratorClient(this._client, response.iterator)
- };
- }
- return response;
- }
- }),
-
- /**
- * Request the property descriptor of the object's specified property.
- *
- * @param name string The name of the requested property.
- * @param onResponse function Called with the request's response.
- */
- getProperty: DebuggerClient.requester({
- type: "property",
- name: arg(0)
- }),
-
- /**
- * Request the prototype of the object.
- *
- * @param onResponse function Called with the request's response.
- */
- getPrototype: DebuggerClient.requester({
- type: "prototype"
- }),
-
- /**
- * Request the display string of the object.
- *
- * @param onResponse function Called with the request's response.
- */
- getDisplayString: DebuggerClient.requester({
- type: "displayString"
- }),
-
- /**
- * Request the scope of the object.
- *
- * @param onResponse function Called with the request's response.
- */
- getScope: DebuggerClient.requester({
- type: "scope"
- }, {
- before: function (packet) {
- if (this._grip.class !== "Function") {
- throw new Error("scope is only valid for function grips.");
- }
- return packet;
- },
- }),
-
- /**
- * Request the promises directly depending on the current promise.
- */
- getDependentPromises: DebuggerClient.requester({
- type: "dependentPromises"
- }, {
- before: function (packet) {
- if (this._grip.class !== "Promise") {
- throw new Error("getDependentPromises is only valid for promise " +
- "grips.");
- }
- return packet;
- }
- }),
-
- /**
- * Request the stack to the promise's allocation point.
- */
- getPromiseAllocationStack: DebuggerClient.requester({
- type: "allocationStack"
- }, {
- before: function (packet) {
- if (this._grip.class !== "Promise") {
- throw new Error("getAllocationStack is only valid for promise grips.");
- }
- return packet;
- }
- }),
-
- /**
- * Request the stack to the promise's fulfillment point.
- */
- getPromiseFulfillmentStack: DebuggerClient.requester({
- type: "fulfillmentStack"
- }, {
- before: function (packet) {
- if (this._grip.class !== "Promise") {
- throw new Error("getPromiseFulfillmentStack is only valid for " +
- "promise grips.");
- }
- return packet;
- }
- }),
-
- /**
- * Request the stack to the promise's rejection point.
- */
- getPromiseRejectionStack: DebuggerClient.requester({
- type: "rejectionStack"
- }, {
- before: function (packet) {
- if (this._grip.class !== "Promise") {
- throw new Error("getPromiseRejectionStack is only valid for " +
- "promise grips.");
- }
- return packet;
- }
- })
-};
-
-/**
- * A PropertyIteratorClient provides a way to access to property names and
- * values of an object efficiently, slice by slice.
- * Note that the properties can be sorted in the backend,
- * this is controled while creating the PropertyIteratorClient
- * from ObjectClient.enumProperties.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param grip Object
- * A PropertyIteratorActor grip returned by the protocol via
- * TabActor.enumProperties request.
- */
-function PropertyIteratorClient(client, grip) {
- this._grip = grip;
- this._client = client;
- this.request = this._client.request;
-}
-
-PropertyIteratorClient.prototype = {
- get actor() {
- return this._grip.actor;
- },
-
- /**
- * Get the total number of properties available in the iterator.
- */
- get count() {
- return this._grip.count;
- },
-
- /**
- * Get one or more property names that correspond to the positions in the
- * indexes parameter.
- *
- * @param indexes Array
- * An array of property indexes.
- * @param callback Function
- * The function called when we receive the property names.
- */
- names: DebuggerClient.requester({
- type: "names",
- indexes: arg(0)
- }, {}),
-
- /**
- * Get a set of following property value(s).
- *
- * @param start Number
- * The index of the first property to fetch.
- * @param count Number
- * The number of properties to fetch.
- * @param callback Function
- * The function called when we receive the property values.
- */
- slice: DebuggerClient.requester({
- type: "slice",
- start: arg(0),
- count: arg(1)
- }, {}),
-
- /**
- * Get all the property values.
- *
- * @param callback Function
- * The function called when we receive the property values.
- */
- all: DebuggerClient.requester({
- type: "all"
- }, {}),
-};
-
-/**
- * A SymbolIteratorClient provides a way to access to symbols
- * of an object efficiently, slice by slice.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param grip Object
- * A SymbolIteratorActor grip returned by the protocol via
- * TabActor.enumSymbols request.
- */
-function SymbolIteratorClient(client, grip) {
- this._grip = grip;
- this._client = client;
- this.request = this._client.request;
-}
-
-SymbolIteratorClient.prototype = {
- get actor() {
- return this._grip.actor;
- },
-
- /**
- * Get the total number of symbols available in the iterator.
- */
- get count() {
- return this._grip.count;
- },
-
- /**
- * Get a set of following symbols.
- *
- * @param start Number
- * The index of the first symbol to fetch.
- * @param count Number
- * The number of symbols to fetch.
- * @param callback Function
- * The function called when we receive the symbols.
- */
- slice: DebuggerClient.requester({
- type: "slice",
- start: arg(0),
- count: arg(1)
- }, {}),
-
- /**
- * Get all the symbols.
- *
- * @param callback Function
- * The function called when we receive the symbols.
- */
- all: DebuggerClient.requester({
- type: "all"
- }, {}),
-};
-
-/**
- * A ArrayBufferClient provides a way to access ArrayBuffer from the
- * debugger server.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param grip Object
- * A pause-lifetime ArrayBuffer grip returned by the protocol.
- */
-function ArrayBufferClient(client, grip) {
- this._grip = grip;
- this._client = client;
- this.request = this._client.request;
-}
-ArrayBufferClient.prototype = {
- get actor() {
- return this._grip.actor;
- },
- get length() {
- return this._grip.length;
- },
- get _transport() {
- return this._client._transport;
- },
-
- valid: true,
-
- slice: DebuggerClient.requester({
- type: "slice",
- start: arg(0),
- count: arg(1)
- }),
-};
-
-/**
- * A LongStringClient provides a way to access "very long" strings from the
- * debugger server.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param grip Object
- * A pause-lifetime long string grip returned by the protocol.
- */
-function LongStringClient(client, grip) {
- this._grip = grip;
- this._client = client;
- this.request = this._client.request;
-}
-exports.LongStringClient = LongStringClient;
-
-LongStringClient.prototype = {
- get actor() {
- return this._grip.actor;
- },
- get length() {
- return this._grip.length;
- },
- get initial() {
- return this._grip.initial;
- },
- get _transport() {
- return this._client._transport;
- },
-
- valid: true,
-
- /**
- * Get the substring of this LongString from start to end.
- *
- * @param start Number
- * The starting index.
- * @param end Number
- * The ending index.
- * @param callback Function
- * The function called when we receive the substring.
- */
- substring: DebuggerClient.requester({
- type: "substring",
- start: arg(0),
- end: arg(1)
- }),
-};
-
-/**
- * A SourceClient provides a way to access the source text of a script.
- *
- * @param client ThreadClient
- * The thread client parent.
- * @param form Object
- * The form sent across the remote debugging protocol.
- */
-function SourceClient(client, form) {
- this._form = form;
- this._isBlackBoxed = form.isBlackBoxed;
- this._isPrettyPrinted = form.isPrettyPrinted;
- this._activeThread = client;
- this._client = client.client;
-}
-
-SourceClient.prototype = {
- get _transport() {
- return this._client._transport;
- },
- get isBlackBoxed() {
- return this._isBlackBoxed;
- },
- get isPrettyPrinted() {
- return this._isPrettyPrinted;
- },
- get actor() {
- return this._form.actor;
- },
- get request() {
- return this._client.request;
- },
- get url() {
- return this._form.url;
- },
-
- /**
- * Black box this SourceClient's source.
- *
- * @param callback Function
- * The callback function called when we receive the response from the server.
- */
- blackBox: DebuggerClient.requester({
- type: "blackbox"
- }, {
- after: function (response) {
- if (!response.error) {
- this._isBlackBoxed = true;
- if (this._activeThread) {
- this._activeThread.emit("blackboxchange", this);
- }
- }
- return response;
- }
- }),
-
- /**
- * Un-black box this SourceClient's source.
- *
- * @param callback Function
- * The callback function called when we receive the response from the server.
- */
- unblackBox: DebuggerClient.requester({
- type: "unblackbox"
- }, {
- after: function (response) {
- if (!response.error) {
- this._isBlackBoxed = false;
- if (this._activeThread) {
- this._activeThread.emit("blackboxchange", this);
- }
- }
- return response;
- }
- }),
-
- /**
- * Get Executable Lines from a source
- *
- * @param callback Function
- * The callback function called when we receive the response from the server.
- */
- getExecutableLines: function (cb = noop) {
- let packet = {
- to: this._form.actor,
- type: "getExecutableLines"
- };
-
- return this._client.request(packet).then(res => {
- cb(res.lines);
- return res.lines;
- });
- },
-
- /**
- * Get a long string grip for this SourceClient's source.
- */
- source: function (callback = noop) {
- let packet = {
- to: this._form.actor,
- type: "source"
- };
- return this._client.request(packet).then(response => {
- return this._onSourceResponse(response, callback);
- });
- },
-
- /**
- * Pretty print this source's text.
- */
- prettyPrint: function (indent, callback = noop) {
- const packet = {
- to: this._form.actor,
- type: "prettyPrint",
- indent
- };
- return this._client.request(packet).then(response => {
- if (!response.error) {
- this._isPrettyPrinted = true;
- this._activeThread._clearFrames();
- this._activeThread.emit("prettyprintchange", this);
- }
- return this._onSourceResponse(response, callback);
- });
- },
-
- /**
- * Stop pretty printing this source's text.
- */
- disablePrettyPrint: function (callback = noop) {
- const packet = {
- to: this._form.actor,
- type: "disablePrettyPrint"
- };
- return this._client.request(packet).then(response => {
- if (!response.error) {
- this._isPrettyPrinted = false;
- this._activeThread._clearFrames();
- this._activeThread.emit("prettyprintchange", this);
- }
- return this._onSourceResponse(response, callback);
- });
- },
-
- _onSourceResponse: function (response, callback) {
- if (response.error) {
- callback(response);
- return response;
- }
-
- if (typeof response.source === "string") {
- callback(response);
- return response;
- }
-
- let { contentType, source } = response;
- if (source.type === "arrayBuffer") {
- let arrayBuffer = this._activeThread.threadArrayBuffer(source);
- return arrayBuffer.slice(0, arrayBuffer.length).then(function (resp) {
- if (resp.error) {
- callback(resp);
- return resp;
- }
- // Keeping str as a string, ArrayBuffer/Uint8Array will not survive
- // setIn/mergeIn operations.
- const str = atob(resp.encoded);
- let newResponse = {
- source: {
- binary: str,
- toString: () => "[wasm]",
- },
- contentType,
- };
- callback(newResponse);
- return newResponse;
- });
- }
-
- let longString = this._activeThread.threadLongString(source);
- return longString.substring(0, longString.length).then(function (resp) {
- if (resp.error) {
- callback(resp);
- return resp;
- }
-
- let newResponse = {
- source: resp.substring,
- contentType: contentType
- };
- callback(newResponse);
- return newResponse;
- });
- },
-
- /**
- * Request to set a breakpoint in the specified location.
- *
- * @param object location
- * The location and condition of the breakpoint in
- * the form of { line[, column, condition] }.
- * @param function onResponse
- * Called with the thread's response.
- */
- setBreakpoint: function ({ line, column, condition, noSliding }, onResponse = noop) {
- // A helper function that sets the breakpoint.
- let doSetBreakpoint = callback => {
- let root = this._client.mainRoot;
- let location = {
- line,
- column,
- };
-
- let packet = {
- to: this.actor,
- type: "setBreakpoint",
- location,
- condition,
- noSliding,
- };
-
- // Backwards compatibility: send the breakpoint request to the
- // thread if the server doesn't support Debugger.Source actors.
- if (!root.traits.debuggerSourceActors) {
- packet.to = this._activeThread.actor;
- packet.location.url = this.url;
- }
-
- return this._client.request(packet).then(response => {
- // Ignoring errors, since the user may be setting a breakpoint in a
- // dead script that will reappear on a page reload.
- let bpClient;
- if (response.actor) {
- bpClient = new BreakpointClient(
- this._client,
- this,
- response.actor,
- location,
- root.traits.conditionalBreakpoints ? condition : undefined
- );
- }
- onResponse(response, bpClient);
- if (callback) {
- callback();
- }
- return [response, bpClient];
- });
- };
-
- // If the debuggee is paused, just set the breakpoint.
- if (this._activeThread.paused) {
- return doSetBreakpoint();
- }
- // Otherwise, force a pause in order to set the breakpoint.
- return this._activeThread.interrupt().then(response => {
- if (response.error) {
- // Can't set the breakpoint if pausing failed.
- onResponse(response);
- return response;
- }
-
- const { type, why } = response;
- const cleanUp = type == "paused" && why.type == "interrupted"
- ? () => this._activeThread.resume()
- : noop;
-
- return doSetBreakpoint(cleanUp);
- });
- }
-};
-
-/**
- * Breakpoint clients are used to remove breakpoints that are no longer used.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param sourceClient SourceClient
- * The source where this breakpoint exists
- * @param actor string
- * The actor ID for this breakpoint.
- * @param location object
- * The location of the breakpoint. This is an object with two properties:
- * url and line.
- * @param condition string
- * The conditional expression of the breakpoint
- */
-function BreakpointClient(client, sourceClient, actor, location, condition) {
- this._client = client;
- this._actor = actor;
- this.location = location;
- this.location.actor = sourceClient.actor;
- this.location.url = sourceClient.url;
- this.source = sourceClient;
- this.request = this._client.request;
-
- // The condition property should only exist if it's a truthy value
- if (condition) {
- this.condition = condition;
- }
-}
-
-BreakpointClient.prototype = {
-
- _actor: null,
- get actor() {
- return this._actor;
- },
- get _transport() {
- return this._client._transport;
- },
-
- /**
- * Remove the breakpoint from the server.
- */
- remove: DebuggerClient.requester({
- type: "delete"
- }),
-
- /**
- * Determines if this breakpoint has a condition
- */
- hasCondition: function () {
- let root = this._client.mainRoot;
- // XXX bug 990137: We will remove support for client-side handling of
- // conditional breakpoints
- if (root.traits.conditionalBreakpoints) {
- return "condition" in this;
- }
- return "conditionalExpression" in this;
- },
-
- /**
- * Get the condition of this breakpoint. Currently we have to
- * support locally emulated conditional breakpoints until the
- * debugger servers are updated (see bug 990137). We used a
- * different property when moving it server-side to ensure that we
- * are testing the right code.
- */
- getCondition: function () {
- let root = this._client.mainRoot;
- if (root.traits.conditionalBreakpoints) {
- return this.condition;
- }
- return this.conditionalExpression;
- },
-
- /**
- * Set the condition of this breakpoint
- */
- setCondition: function (gThreadClient, condition) {
- let root = this._client.mainRoot;
- let deferred = promise.defer();
-
- if (root.traits.conditionalBreakpoints) {
- let info = {
- line: this.location.line,
- column: this.location.column,
- condition: condition
- };
-
- // Remove the current breakpoint and add a new one with the
- // condition.
- this.remove(response => {
- if (response && response.error) {
- deferred.reject(response);
- return;
- }
-
- this.source.setBreakpoint(info, (resp, newBreakpoint) => {
- if (resp && resp.error) {
- deferred.reject(resp);
- } else {
- deferred.resolve(newBreakpoint);
- }
- });
- });
- } else {
- // The property shouldn't even exist if the condition is blank
- if (condition === "") {
- delete this.conditionalExpression;
- } else {
- this.conditionalExpression = condition;
- }
- deferred.resolve(this);
- }
-
- return deferred.promise;
- }
-};
-
-eventSource(BreakpointClient.prototype);
-
-/**
- * Environment clients are used to manipulate the lexical environment actors.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param form Object
- * The form sent across the remote debugging protocol.
- */
-function EnvironmentClient(client, form) {
- this._client = client;
- this._form = form;
- this.request = this._client.request;
-}
-exports.EnvironmentClient = EnvironmentClient;
-
-EnvironmentClient.prototype = {
-
- get actor() {
- return this._form.actor;
- },
- get _transport() {
- return this._client._transport;
- },
-
- /**
- * Fetches the bindings introduced by this lexical environment.
- */
- getBindings: DebuggerClient.requester({
- type: "bindings"
- }),
-
- /**
- * Changes the value of the identifier whose name is name (a string) to that
- * represented by value (a grip).
- */
- assign: DebuggerClient.requester({
- type: "assign",
- name: arg(0),
- value: arg(1)
- })
-};
-
-eventSource(EnvironmentClient.prototype);
+module.exports = ThreadClient;
copy from devtools/shared/client/main.js
copy to devtools/shared/client/trace-client.js
--- a/devtools/shared/client/main.js
+++ b/devtools/shared/client/trace-client.js
@@ -1,2418 +1,15 @@
-/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
-/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
-const { Ci, Cu } = require("chrome");
-const DevToolsUtils = require("devtools/shared/DevToolsUtils");
-const { getStack, callFunctionWithAsyncStack } = require("devtools/shared/platform/stack");
-
-const promise = Cu.import("resource://devtools/shared/deprecated-sync-thenables.js", {}).Promise;
-
-loader.lazyRequireGetter(this, "EventEmitter", "devtools/shared/event-emitter");
-loader.lazyRequireGetter(this, "WebConsoleClient", "devtools/shared/webconsole/client", true);
-loader.lazyRequireGetter(this, "DebuggerSocket", "devtools/shared/security/socket", true);
-loader.lazyRequireGetter(this, "Authentication", "devtools/shared/security/auth");
-
-const noop = () => {};
-
-/**
- * TODO: Get rid of this API in favor of EventTarget (bug 1042642)
- *
- * Add simple event notification to a prototype object. Any object that has
- * some use for event notifications or the observer pattern in general can be
- * augmented with the necessary facilities by passing its prototype to this
- * function.
- *
- * @param proto object
- * The prototype object that will be modified.
- */
-function eventSource(proto) {
- /**
- * Add a listener to the event source for a given event.
- *
- * @param name string
- * The event to listen for.
- * @param listener function
- * Called when the event is fired. If the same listener
- * is added more than once, it will be called once per
- * addListener call.
- */
- proto.addListener = function (name, listener) {
- if (typeof listener != "function") {
- throw TypeError("Listeners must be functions.");
- }
-
- if (!this._listeners) {
- this._listeners = {};
- }
-
- this._getListeners(name).push(listener);
- };
-
- /**
- * Add a listener to the event source for a given event. The
- * listener will be removed after it is called for the first time.
- *
- * @param name string
- * The event to listen for.
- * @param listener function
- * Called when the event is fired.
- */
- proto.addOneTimeListener = function (name, listener) {
- let l = (...args) => {
- this.removeListener(name, l);
- listener.apply(null, args);
- };
- this.addListener(name, l);
- };
-
- /**
- * Remove a listener from the event source previously added with
- * addListener().
- *
- * @param name string
- * The event name used during addListener to add the listener.
- * @param listener function
- * The callback to remove. If addListener was called multiple
- * times, all instances will be removed.
- */
- proto.removeListener = function (name, listener) {
- if (!this._listeners || (listener && !this._listeners[name])) {
- return;
- }
-
- if (!listener) {
- this._listeners[name] = [];
- } else {
- this._listeners[name] =
- this._listeners[name].filter(l => l != listener);
- }
- };
-
- /**
- * Returns the listeners for the specified event name. If none are defined it
- * initializes an empty list and returns that.
- *
- * @param name string
- * The event name.
- */
- proto._getListeners = function (name) {
- if (name in this._listeners) {
- return this._listeners[name];
- }
- this._listeners[name] = [];
- return this._listeners[name];
- };
-
- /**
- * Notify listeners of an event.
- *
- * @param name string
- * The event to fire.
- * @param arguments
- * All arguments will be passed along to the listeners,
- * including the name argument.
- */
- proto.emit = function () {
- if (!this._listeners) {
- return;
- }
-
- let name = arguments[0];
- let listeners = this._getListeners(name).slice(0);
-
- for (let listener of listeners) {
- try {
- listener.apply(null, arguments);
- } catch (e) {
- // Prevent a bad listener from interfering with the others.
- DevToolsUtils.reportException("notify event '" + name + "'", e);
- }
- }
- };
-}
-
-/**
- * Set of protocol messages that affect thread state, and the
- * state the actor is in after each message.
- */
-const ThreadStateTypes = {
- "paused": "paused",
- "resumed": "attached",
- "detached": "detached",
- "running": "attached"
-};
-
-/**
- * Set of protocol messages that are sent by the server without a prior request
- * by the client.
- */
-const UnsolicitedNotifications = {
- "consoleAPICall": "consoleAPICall",
- "eventNotification": "eventNotification",
- "fileActivity": "fileActivity",
- "lastPrivateContextExited": "lastPrivateContextExited",
- "logMessage": "logMessage",
- "networkEvent": "networkEvent",
- "networkEventUpdate": "networkEventUpdate",
- "newGlobal": "newGlobal",
- "newScript": "newScript",
- "tabDetached": "tabDetached",
- "tabListChanged": "tabListChanged",
- "reflowActivity": "reflowActivity",
- "addonListChanged": "addonListChanged",
- "workerListChanged": "workerListChanged",
- "serviceWorkerRegistrationListChanged": "serviceWorkerRegistrationList",
- "tabNavigated": "tabNavigated",
- "frameUpdate": "frameUpdate",
- "pageError": "pageError",
- "documentLoad": "documentLoad",
- "enteredFrame": "enteredFrame",
- "exitedFrame": "exitedFrame",
- "appOpen": "appOpen",
- "appClose": "appClose",
- "appInstall": "appInstall",
- "appUninstall": "appUninstall",
- "evaluationResult": "evaluationResult",
- "newSource": "newSource",
- "updatedSource": "updatedSource",
- "inspectObject": "inspectObject"
-};
-
-/**
- * Set of pause types that are sent by the server and not as an immediate
- * response to a client request.
- */
-const UnsolicitedPauses = {
- "resumeLimit": "resumeLimit",
- "debuggerStatement": "debuggerStatement",
- "breakpoint": "breakpoint",
- "DOMEvent": "DOMEvent",
- "watchpoint": "watchpoint",
- "exception": "exception"
-};
-
-/**
- * Creates a client for the remote debugging protocol server. This client
- * provides the means to communicate with the server and exchange the messages
- * required by the protocol in a traditional JavaScript API.
- */
-const DebuggerClient = exports.DebuggerClient = function (transport) {
- this._transport = transport;
- this._transport.hooks = this;
-
- // Map actor ID to client instance for each actor type.
- this._clients = new Map();
-
- this._pendingRequests = new Map();
- this._activeRequests = new Map();
- this._eventsEnabled = true;
-
- this.traits = {};
-
- this.request = this.request.bind(this);
- this.localTransport = this._transport.onOutputStreamReady === undefined;
-
- /*
- * As the first thing on the connection, expect a greeting packet from
- * the connection's root actor.
- */
- this.mainRoot = null;
- this.expectReply("root", (packet) => {
- this.mainRoot = new RootClient(this, packet);
- this.emit("connected", packet.applicationType, packet.traits);
- });
-};
-
-/**
- * A declarative helper for defining methods that send requests to the server.
- *
- * @param packetSkeleton
- * The form of the packet to send. Can specify fields to be filled from
- * the parameters by using the |arg| function.
- * @param before
- * The function to call before sending the packet. Is passed the packet,
- * and the return value is used as the new packet. The |this| context is
- * the instance of the client object we are defining a method for.
- * @param after
- * The function to call after the response is received. It is passed the
- * response, and the return value is considered the new response that
- * will be passed to the callback. The |this| context is the instance of
- * the client object we are defining a method for.
- * @return Request
- * The `Request` object that is a Promise object and resolves once
- * we receive the response. (See request method for more details)
- */
-DebuggerClient.requester = function (packetSkeleton, config = {}) {
- let { before, after } = config;
- return DevToolsUtils.makeInfallible(function (...args) {
- let outgoingPacket = {
- to: packetSkeleton.to || this.actor
- };
-
- let maxPosition = -1;
- for (let k of Object.keys(packetSkeleton)) {
- if (packetSkeleton[k] instanceof DebuggerClient.Argument) {
- let { position } = packetSkeleton[k];
- outgoingPacket[k] = packetSkeleton[k].getArgument(args);
- maxPosition = Math.max(position, maxPosition);
- } else {
- outgoingPacket[k] = packetSkeleton[k];
- }
- }
-
- if (before) {
- outgoingPacket = before.call(this, outgoingPacket);
- }
-
- return this.request(outgoingPacket, DevToolsUtils.makeInfallible((response) => {
- if (after) {
- let { from } = response;
- response = after.call(this, response);
- if (!response.from) {
- response.from = from;
- }
- }
-
- // The callback is always the last parameter.
- let thisCallback = args[maxPosition + 1];
- if (thisCallback) {
- thisCallback(response);
- }
- return response;
- }, "DebuggerClient.requester request callback"));
- }, "DebuggerClient.requester");
-};
-
-function arg(pos) {
- return new DebuggerClient.Argument(pos);
-}
-
-DebuggerClient.Argument = function (position) {
- this.position = position;
-};
-
-DebuggerClient.Argument.prototype.getArgument = function (params) {
- if (!(this.position in params)) {
- throw new Error("Bad index into params: " + this.position);
- }
- return params[this.position];
-};
-
-// Expose these to save callers the trouble of importing DebuggerSocket
-DebuggerClient.socketConnect = function (options) {
- // Defined here instead of just copying the function to allow lazy-load
- return DebuggerSocket.connect(options);
-};
-DevToolsUtils.defineLazyGetter(DebuggerClient, "Authenticators", () => {
- return Authentication.Authenticators;
-});
-DevToolsUtils.defineLazyGetter(DebuggerClient, "AuthenticationResult", () => {
- return Authentication.AuthenticationResult;
-});
-
-DebuggerClient.prototype = {
- /**
- * Connect to the server and start exchanging protocol messages.
- *
- * @param onConnected function
- * If specified, will be called when the greeting packet is
- * received from the debugging server.
- *
- * @return Promise
- * Resolves once connected with an array whose first element
- * is the application type, by default "browser", and the second
- * element is the traits object (help figure out the features
- * and behaviors of the server we connect to. See RootActor).
- */
- connect: function (onConnected) {
- let deferred = promise.defer();
- this.emit("connect");
-
- // Also emit the event on the |DebuggerClient| object (not on the instance),
- // so it's possible to track all instances.
- EventEmitter.emit(DebuggerClient, "connect", this);
-
- this.addOneTimeListener("connected", (name, applicationType, traits) => {
- this.traits = traits;
- if (onConnected) {
- onConnected(applicationType, traits);
- }
- deferred.resolve([applicationType, traits]);
- });
-
- this._transport.ready();
- return deferred.promise;
- },
-
- /**
- * Shut down communication with the debugging server.
- *
- * @param onClosed function
- * If specified, will be called when the debugging connection
- * has been closed. This parameter is deprecated - please use
- * the returned Promise.
- * @return Promise
- * Resolves after the underlying transport is closed.
- */
- close: function (onClosed) {
- let deferred = promise.defer();
- if (onClosed) {
- deferred.promise.then(onClosed);
- }
-
- // Disable detach event notifications, because event handlers will be in a
- // cleared scope by the time they run.
- this._eventsEnabled = false;
-
- let cleanup = () => {
- this._transport.close();
- this._transport = null;
- };
-
- // If the connection is already closed,
- // there is no need to detach client
- // as we won't be able to send any message.
- if (this._closed) {
- cleanup();
- deferred.resolve();
- return deferred.promise;
- }
-
- this.addOneTimeListener("closed", deferred.resolve);
-
- // Call each client's `detach` method by calling
- // lastly registered ones first to give a chance
- // to detach child clients first.
- let clients = [...this._clients.values()];
- this._clients.clear();
- const detachClients = () => {
- let client = clients.pop();
- if (!client) {
- // All clients detached.
- cleanup();
- return;
- }
- if (client.detach) {
- client.detach(detachClients);
- return;
- }
- detachClients();
- };
- detachClients();
-
- return deferred.promise;
- },
-
- /*
- * This function exists only to preserve DebuggerClient's interface;
- * new code should say 'client.mainRoot.listTabs()'.
- */
- listTabs: function (onResponse) {
- return this.mainRoot.listTabs(onResponse);
- },
-
- /*
- * This function exists only to preserve DebuggerClient's interface;
- * new code should say 'client.mainRoot.listAddons()'.
- */
- listAddons: function (onResponse) {
- return this.mainRoot.listAddons(onResponse);
- },
-
- getTab: function (filter) {
- return this.mainRoot.getTab(filter);
- },
-
- /**
- * Attach to a tab actor.
- *
- * @param string tabActor
- * The actor ID for the tab to attach.
- * @param function onResponse
- * Called with the response packet and a TabClient
- * (which will be undefined on error).
- */
- attachTab: function (tabActor, onResponse = noop) {
- if (this._clients.has(tabActor)) {
- let cachedTab = this._clients.get(tabActor);
- let cachedResponse = {
- cacheDisabled: cachedTab.cacheDisabled,
- javascriptEnabled: cachedTab.javascriptEnabled,
- traits: cachedTab.traits,
- };
- DevToolsUtils.executeSoon(() => onResponse(cachedResponse, cachedTab));
- return promise.resolve([cachedResponse, cachedTab]);
- }
-
- let packet = {
- to: tabActor,
- type: "attach"
- };
- return this.request(packet).then(response => {
- let tabClient;
- if (!response.error) {
- tabClient = new TabClient(this, response);
- this.registerClient(tabClient);
- }
- onResponse(response, tabClient);
- return [response, tabClient];
- });
- },
-
- attachWorker: function (workerActor, onResponse = noop) {
- let workerClient = this._clients.get(workerActor);
- if (workerClient !== undefined) {
- let response = {
- from: workerClient.actor,
- type: "attached",
- url: workerClient.url
- };
- DevToolsUtils.executeSoon(() => onResponse(response, workerClient));
- return promise.resolve([response, workerClient]);
- }
-
- return this.request({ to: workerActor, type: "attach" }).then(response => {
- if (response.error) {
- onResponse(response, null);
- return [response, null];
- }
-
- workerClient = new WorkerClient(this, response);
- this.registerClient(workerClient);
- onResponse(response, workerClient);
- return [response, workerClient];
- });
- },
-
- /**
- * Attach to an addon actor.
- *
- * @param string addonActor
- * The actor ID for the addon to attach.
- * @param function onResponse
- * Called with the response packet and a AddonClient
- * (which will be undefined on error).
- */
- attachAddon: function (addonActor, onResponse = noop) {
- let packet = {
- to: addonActor,
- type: "attach"
- };
- return this.request(packet).then(response => {
- let addonClient;
- if (!response.error) {
- addonClient = new AddonClient(this, addonActor);
- this.registerClient(addonClient);
- this.activeAddon = addonClient;
- }
- onResponse(response, addonClient);
- return [response, addonClient];
- });
- },
-
- /**
- * Attach to a Web Console actor.
- *
- * @param string consoleActor
- * The ID for the console actor to attach to.
- * @param array listeners
- * The console listeners you want to start.
- * @param function onResponse
- * Called with the response packet and a WebConsoleClient
- * instance (which will be undefined on error).
- */
- attachConsole:
- function (consoleActor, listeners, onResponse = noop) {
- let packet = {
- to: consoleActor,
- type: "startListeners",
- listeners: listeners,
- };
-
- return this.request(packet).then(response => {
- let consoleClient;
- if (!response.error) {
- if (this._clients.has(consoleActor)) {
- consoleClient = this._clients.get(consoleActor);
- } else {
- consoleClient = new WebConsoleClient(this, response);
- this.registerClient(consoleClient);
- }
- }
- onResponse(response, consoleClient);
- return [response, consoleClient];
- });
- },
-
- /**
- * Attach to a global-scoped thread actor for chrome debugging.
- *
- * @param string threadActor
- * The actor ID for the thread to attach.
- * @param function onResponse
- * Called with the response packet and a ThreadClient
- * (which will be undefined on error).
- * @param object options
- * Configuration options.
- * - useSourceMaps: whether to use source maps or not.
- */
- attachThread: function (threadActor, onResponse = noop, options = {}) {
- if (this._clients.has(threadActor)) {
- let client = this._clients.get(threadActor);
- DevToolsUtils.executeSoon(() => onResponse({}, client));
- return promise.resolve([{}, client]);
- }
-
- let packet = {
- to: threadActor,
- type: "attach",
- options,
- };
- return this.request(packet).then(response => {
- let threadClient;
- if (!response.error) {
- threadClient = new ThreadClient(this, threadActor);
- this.registerClient(threadClient);
- }
- onResponse(response, threadClient);
- return [response, threadClient];
- });
- },
-
- /**
- * Attach to a trace actor.
- *
- * @param string traceActor
- * The actor ID for the tracer to attach.
- * @param function onResponse
- * Called with the response packet and a TraceClient
- * (which will be undefined on error).
- */
- attachTracer: function (traceActor, onResponse = noop) {
- if (this._clients.has(traceActor)) {
- let client = this._clients.get(traceActor);
- DevToolsUtils.executeSoon(() => onResponse({}, client));
- return promise.resolve([{}, client]);
- }
-
- let packet = {
- to: traceActor,
- type: "attach"
- };
- return this.request(packet).then(response => {
- let traceClient;
- if (!response.error) {
- traceClient = new TraceClient(this, traceActor);
- this.registerClient(traceClient);
- }
- onResponse(response, traceClient);
- return [response, traceClient];
- });
- },
-
- /**
- * Fetch the ChromeActor for the main process or ChildProcessActor for a
- * a given child process ID.
- *
- * @param number id
- * The ID for the process to attach (returned by `listProcesses`).
- * Connected to the main process if omitted, or is 0.
- */
- getProcess: function (id) {
- let packet = {
- to: "root",
- type: "getProcess"
- };
- if (typeof (id) == "number") {
- packet.id = id;
- }
- return this.request(packet);
- },
-
- /**
- * Release an object actor.
- *
- * @param string actor
- * The actor ID to send the request to.
- * @param onResponse function
- * If specified, will be called with the response packet when
- * debugging server responds.
- */
- release: DebuggerClient.requester({
- to: arg(0),
- type: "release"
- }),
-
- /**
- * Send a request to the debugging server.
- *
- * @param packet object
- * A JSON packet to send to the debugging server.
- * @param onResponse function
- * If specified, will be called with the JSON response packet when
- * debugging server responds.
- * @return Request
- * This object emits a number of events to allow you to respond to
- * different parts of the request lifecycle.
- * It is also a Promise object, with a `then` method, that is resolved
- * whenever a JSON or a Bulk response is received; and is rejected
- * if the response is an error.
- * Note: This return value can be ignored if you are using JSON alone,
- * because the callback provided in |onResponse| will be bound to the
- * "json-reply" event automatically.
- *
- * Events emitted:
- * * json-reply: The server replied with a JSON packet, which is
- * passed as event data.
- * * bulk-reply: The server replied with bulk data, which you can read
- * using the event data object containing:
- * * actor: Name of actor that received the packet
- * * type: Name of actor's method that was called on receipt
- * * length: Size of the data to be read
- * * stream: This input stream should only be used directly if you
- * can ensure that you will read exactly |length| bytes
- * and will not close the stream when reading is complete
- * * done: If you use the stream directly (instead of |copyTo|
- * below), you must signal completion by resolving /
- * rejecting this deferred. If it's rejected, the
- * transport will be closed. If an Error is supplied as a
- * rejection value, it will be logged via |dumpn|. If you
- * do use |copyTo|, resolving is taken care of for you
- * when copying completes.
- * * copyTo: A helper function for getting your data out of the
- * stream that meets the stream handling requirements
- * above, and has the following signature:
- * @param output nsIAsyncOutputStream
- * The stream to copy to.
- * @return Promise
- * The promise is resolved when copying completes or
- * rejected if any (unexpected) errors occur.
- * This object also emits "progress" events for each chunk
- * that is copied. See stream-utils.js.
- */
- request: function (packet, onResponse) {
- if (!this.mainRoot) {
- throw Error("Have not yet received a hello packet from the server.");
- }
- let type = packet.type || "";
- if (!packet.to) {
- throw Error("'" + type + "' request packet has no destination.");
- }
-
- // The onResponse callback might modify the response, so we need to call
- // it and resolve the promise with its result if it's truthy.
- const safeOnResponse = response => {
- if (!onResponse) {
- return response;
- }
- return onResponse(response) || response;
- };
-
- if (this._closed) {
- let msg = "'" + type + "' request packet to " +
- "'" + packet.to + "' " +
- "can't be sent as the connection is closed.";
- let resp = { error: "connectionClosed", message: msg };
- return promise.reject(safeOnResponse(resp));
- }
-
- let request = new Request(packet);
- request.format = "json";
- request.stack = getStack();
-
- // Implement a Promise like API on the returned object
- // that resolves/rejects on request response
- let deferred = promise.defer();
- function listenerJson(resp) {
- removeRequestListeners();
- if (resp.error) {
- deferred.reject(safeOnResponse(resp));
- } else {
- deferred.resolve(safeOnResponse(resp));
- }
- }
- function listenerBulk(resp) {
- removeRequestListeners();
- deferred.resolve(safeOnResponse(resp));
- }
-
- const removeRequestListeners = () => {
- request.off("json-reply", listenerJson);
- request.off("bulk-reply", listenerBulk);
- };
-
- request.on("json-reply", listenerJson);
- request.on("bulk-reply", listenerBulk);
-
- this._sendOrQueueRequest(request);
- request.then = deferred.promise.then.bind(deferred.promise);
-
- return request;
- },
-
- /**
- * Transmit streaming data via a bulk request.
- *
- * This method initiates the bulk send process by queuing up the header data.
- * The caller receives eventual access to a stream for writing.
- *
- * Since this opens up more options for how the server might respond (it could
- * send back either JSON or bulk data), and the returned Request object emits
- * events for different stages of the request process that you may want to
- * react to.
- *
- * @param request Object
- * This is modeled after the format of JSON packets above, but does not
- * actually contain the data, but is instead just a routing header:
- * * actor: Name of actor that will receive the packet
- * * type: Name of actor's method that should be called on receipt
- * * length: Size of the data to be sent
- * @return Request
- * This object emits a number of events to allow you to respond to
- * different parts of the request lifecycle.
- *
- * Events emitted:
- * * bulk-send-ready: Ready to send bulk data to the server, using the
- * event data object containing:
- * * stream: This output stream should only be used directly if
- * you can ensure that you will write exactly |length|
- * bytes and will not close the stream when writing is
- * complete
- * * done: If you use the stream directly (instead of |copyFrom|
- * below), you must signal completion by resolving /
- * rejecting this deferred. If it's rejected, the
- * transport will be closed. If an Error is supplied as
- * a rejection value, it will be logged via |dumpn|. If
- * you do use |copyFrom|, resolving is taken care of for
- * you when copying completes.
- * * copyFrom: A helper function for getting your data onto the
- * stream that meets the stream handling requirements
- * above, and has the following signature:
- * @param input nsIAsyncInputStream
- * The stream to copy from.
- * @return Promise
- * The promise is resolved when copying completes or
- * rejected if any (unexpected) errors occur.
- * This object also emits "progress" events for each chunk
- * that is copied. See stream-utils.js.
- * * json-reply: The server replied with a JSON packet, which is
- * passed as event data.
- * * bulk-reply: The server replied with bulk data, which you can read
- * using the event data object containing:
- * * actor: Name of actor that received the packet
- * * type: Name of actor's method that was called on receipt
- * * length: Size of the data to be read
- * * stream: This input stream should only be used directly if you
- * can ensure that you will read exactly |length| bytes
- * and will not close the stream when reading is complete
- * * done: If you use the stream directly (instead of |copyTo|
- * below), you must signal completion by resolving /
- * rejecting this deferred. If it's rejected, the
- * transport will be closed. If an Error is supplied as a
- * rejection value, it will be logged via |dumpn|. If you
- * do use |copyTo|, resolving is taken care of for you
- * when copying completes.
- * * copyTo: A helper function for getting your data out of the
- * stream that meets the stream handling requirements
- * above, and has the following signature:
- * @param output nsIAsyncOutputStream
- * The stream to copy to.
- * @return Promise
- * The promise is resolved when copying completes or
- * rejected if any (unexpected) errors occur.
- * This object also emits "progress" events for each chunk
- * that is copied. See stream-utils.js.
- */
- startBulkRequest: function (request) {
- if (!this.traits.bulk) {
- throw Error("Server doesn't support bulk transfers");
- }
- if (!this.mainRoot) {
- throw Error("Have not yet received a hello packet from the server.");
- }
- if (!request.type) {
- throw Error("Bulk packet is missing the required 'type' field.");
- }
- if (!request.actor) {
- throw Error("'" + request.type + "' bulk packet has no destination.");
- }
- if (!request.length) {
- throw Error("'" + request.type + "' bulk packet has no length.");
- }
-
- request = new Request(request);
- request.format = "bulk";
-
- this._sendOrQueueRequest(request);
-
- return request;
- },
-
- /**
- * If a new request can be sent immediately, do so. Otherwise, queue it.
- */
- _sendOrQueueRequest(request) {
- let actor = request.actor;
- if (!this._activeRequests.has(actor)) {
- this._sendRequest(request);
- } else {
- this._queueRequest(request);
- }
- },
-
- /**
- * Send a request.
- * @throws Error if there is already an active request in flight for the same
- * actor.
- */
- _sendRequest(request) {
- let actor = request.actor;
- this.expectReply(actor, request);
-
- if (request.format === "json") {
- this._transport.send(request.request);
- return;
- }
-
- this._transport.startBulkSend(request.request).then((...args) => {
- request.emit("bulk-send-ready", ...args);
- });
- },
-
- /**
- * Queue a request to be sent later. Queues are only drained when an in
- * flight request to a given actor completes.
- */
- _queueRequest(request) {
- let actor = request.actor;
- let queue = this._pendingRequests.get(actor) || [];
- queue.push(request);
- this._pendingRequests.set(actor, queue);
- },
-
- /**
- * Attempt the next request to a given actor (if any).
- */
- _attemptNextRequest(actor) {
- if (this._activeRequests.has(actor)) {
- return;
- }
- let queue = this._pendingRequests.get(actor);
- if (!queue) {
- return;
- }
- let request = queue.shift();
- if (queue.length === 0) {
- this._pendingRequests.delete(actor);
- }
- this._sendRequest(request);
- },
-
- /**
- * Arrange to hand the next reply from |actor| to the handler bound to
- * |request|.
- *
- * DebuggerClient.prototype.request / startBulkRequest usually takes care of
- * establishing the handler for a given request, but in rare cases (well,
- * greetings from new root actors, is the only case at the moment) we must be
- * prepared for a "reply" that doesn't correspond to any request we sent.
- */
- expectReply: function (actor, request) {
- if (this._activeRequests.has(actor)) {
- throw Error("clashing handlers for next reply from " + actor);
- }
-
- // If a handler is passed directly (as it is with the handler for the root
- // actor greeting), create a dummy request to bind this to.
- if (typeof request === "function") {
- let handler = request;
- request = new Request();
- request.on("json-reply", handler);
- }
-
- this._activeRequests.set(actor, request);
- },
-
- // Transport hooks.
-
- /**
- * Called by DebuggerTransport to dispatch incoming packets as appropriate.
- *
- * @param packet object
- * The incoming packet.
- */
- onPacket: function (packet) {
- if (!packet.from) {
- DevToolsUtils.reportException(
- "onPacket",
- new Error("Server did not specify an actor, dropping packet: " +
- JSON.stringify(packet)));
- return;
- }
-
- // If we have a registered Front for this actor, let it handle the packet
- // and skip all the rest of this unpleasantness.
- let front = this.getActor(packet.from);
- if (front) {
- front.onPacket(packet);
- return;
- }
-
- // Check for "forwardingCancelled" here instead of using a client to handle it.
- // This is necessary because we might receive this event while the client is closing,
- // and the clients have already been removed by that point.
- if (this.mainRoot &&
- packet.from == this.mainRoot.actor &&
- packet.type == "forwardingCancelled") {
- this.purgeRequests(packet.prefix);
- return;
- }
-
- if (this._clients.has(packet.from) && packet.type) {
- let client = this._clients.get(packet.from);
- let type = packet.type;
- if (client.events.indexOf(type) != -1) {
- client.emit(type, packet);
- // we ignore the rest, as the client is expected to handle this packet.
- return;
- }
- }
-
- let activeRequest;
- // See if we have a handler function waiting for a reply from this
- // actor. (Don't count unsolicited notifications or pauses as
- // replies.)
- if (this._activeRequests.has(packet.from) &&
- !(packet.type in UnsolicitedNotifications) &&
- !(packet.type == ThreadStateTypes.paused &&
- packet.why.type in UnsolicitedPauses)) {
- activeRequest = this._activeRequests.get(packet.from);
- this._activeRequests.delete(packet.from);
- }
-
- // If there is a subsequent request for the same actor, hand it off to the
- // transport. Delivery of packets on the other end is always async, even
- // in the local transport case.
- this._attemptNextRequest(packet.from);
-
- // Packets that indicate thread state changes get special treatment.
- if (packet.type in ThreadStateTypes &&
- this._clients.has(packet.from) &&
- typeof this._clients.get(packet.from)._onThreadState == "function") {
- this._clients.get(packet.from)._onThreadState(packet);
- }
-
- // TODO: Bug 1151156 - Remove once Gecko 40 is on b2g-stable.
- if (!this.traits.noNeedToFakeResumptionOnNavigation) {
- // On navigation the server resumes, so the client must resume as well.
- // We achieve that by generating a fake resumption packet that triggers
- // the client's thread state change listeners.
- if (packet.type == UnsolicitedNotifications.tabNavigated &&
- this._clients.has(packet.from) &&
- this._clients.get(packet.from).thread) {
- let thread = this._clients.get(packet.from).thread;
- let resumption = { from: thread._actor, type: "resumed" };
- thread._onThreadState(resumption);
- }
- }
-
- // Only try to notify listeners on events, not responses to requests
- // that lack a packet type.
- if (packet.type) {
- this.emit(packet.type, packet);
- }
-
- if (activeRequest) {
- let emitReply = () => activeRequest.emit("json-reply", packet);
- if (activeRequest.stack) {
- callFunctionWithAsyncStack(emitReply, activeRequest.stack,
- "DevTools RDP");
- } else {
- emitReply();
- }
- }
- },
-
- /**
- * Called by the DebuggerTransport to dispatch incoming bulk packets as
- * appropriate.
- *
- * @param packet object
- * The incoming packet, which contains:
- * * actor: Name of actor that will receive the packet
- * * type: Name of actor's method that should be called on receipt
- * * length: Size of the data to be read
- * * stream: This input stream should only be used directly if you can
- * ensure that you will read exactly |length| bytes and will
- * not close the stream when reading is complete
- * * done: If you use the stream directly (instead of |copyTo|
- * below), you must signal completion by resolving /
- * rejecting this deferred. If it's rejected, the transport
- * will be closed. If an Error is supplied as a rejection
- * value, it will be logged via |dumpn|. If you do use
- * |copyTo|, resolving is taken care of for you when copying
- * completes.
- * * copyTo: A helper function for getting your data out of the stream
- * that meets the stream handling requirements above, and has
- * the following signature:
- * @param output nsIAsyncOutputStream
- * The stream to copy to.
- * @return Promise
- * The promise is resolved when copying completes or rejected
- * if any (unexpected) errors occur.
- * This object also emits "progress" events for each chunk
- * that is copied. See stream-utils.js.
- */
- onBulkPacket: function (packet) {
- let { actor } = packet;
-
- if (!actor) {
- DevToolsUtils.reportException(
- "onBulkPacket",
- new Error("Server did not specify an actor, dropping bulk packet: " +
- JSON.stringify(packet)));
- return;
- }
-
- // See if we have a handler function waiting for a reply from this
- // actor.
- if (!this._activeRequests.has(actor)) {
- return;
- }
-
- let activeRequest = this._activeRequests.get(actor);
- this._activeRequests.delete(actor);
-
- // If there is a subsequent request for the same actor, hand it off to the
- // transport. Delivery of packets on the other end is always async, even
- // in the local transport case.
- this._attemptNextRequest(actor);
-
- activeRequest.emit("bulk-reply", packet);
- },
-
- /**
- * Called by DebuggerTransport when the underlying stream is closed.
- *
- * @param status nsresult
- * The status code that corresponds to the reason for closing
- * the stream.
- */
- onClosed: function () {
- this._closed = true;
- this.emit("closed");
-
- this.purgeRequests();
-
- // The |_pools| array on the client-side currently is used only by
- // protocol.js to store active fronts, mirroring the actor pools found in
- // the server. So, read all usages of "pool" as "protocol.js front".
- //
- // In the normal case where we shutdown cleanly, the toolbox tells each tool
- // to close, and they each call |destroy| on any fronts they were using.
- // When |destroy| or |cleanup| is called on a protocol.js front, it also
- // removes itself from the |_pools| array. Once the toolbox has shutdown,
- // the connection is closed, and we reach here. All fronts (should have
- // been) |destroy|ed, so |_pools| should empty.
- //
- // If the connection instead aborts unexpectedly, we may end up here with
- // all fronts used during the life of the connection. So, we call |cleanup|
- // on them clear their state, reject pending requests, and remove themselves
- // from |_pools|. This saves the toolbox from hanging indefinitely, in case
- // it waits for some server response before shutdown that will now never
- // arrive.
- for (let pool of this._pools) {
- pool.cleanup();
- }
- },
-
- /**
- * Purge pending and active requests in this client.
- *
- * @param prefix string (optional)
- * If a prefix is given, only requests for actor IDs that start with the prefix
- * will be cleaned up. This is useful when forwarding of a portion of requests
- * is cancelled on the server.
- */
- purgeRequests(prefix = "") {
- let reject = function (type, request) {
- // Server can send packets on its own and client only pass a callback
- // to expectReply, so that there is no request object.
- let msg;
- if (request.request) {
- msg = "'" + request.request.type + "' " + type + " request packet" +
- " to '" + request.actor + "' " +
- "can't be sent as the connection just closed.";
- } else {
- msg = "server side packet can't be received as the connection just closed.";
- }
- let packet = { error: "connectionClosed", message: msg };
- request.emit("json-reply", packet);
- };
-
- let pendingRequestsToReject = [];
- this._pendingRequests.forEach((requests, actor) => {
- if (!actor.startsWith(prefix)) {
- return;
- }
- this._pendingRequests.delete(actor);
- pendingRequestsToReject = pendingRequestsToReject.concat(requests);
- });
- pendingRequestsToReject.forEach(request => reject("pending", request));
-
- let activeRequestsToReject = [];
- this._activeRequests.forEach((request, actor) => {
- if (!actor.startsWith(prefix)) {
- return;
- }
- this._activeRequests.delete(actor);
- activeRequestsToReject = activeRequestsToReject.concat(request);
- });
- activeRequestsToReject.forEach(request => reject("active", request));
- },
-
- /**
- * Search for all requests in process for this client, including those made via
- * protocol.js and wait all of them to complete. Since the requests seen when this is
- * first called may in turn trigger more requests, we keep recursing through this
- * function until there is no more activity.
- *
- * This is a fairly heavy weight process, so it's only meant to be used in tests.
- *
- * @return Promise
- * Resolved when all requests have settled.
- */
- waitForRequestsToSettle() {
- let requests = [];
-
- // Gather all pending and active requests in this client
- // The request object supports a Promise API for completion (it has .then())
- this._pendingRequests.forEach(requestsForActor => {
- // Each value is an array of pending requests
- requests = requests.concat(requestsForActor);
- });
- this._activeRequests.forEach(requestForActor => {
- // Each value is a single active request
- requests = requests.concat(requestForActor);
- });
-
- // protocol.js
- // Use a Set because some fronts (like domwalker) seem to have multiple parents.
- let fronts = new Set();
- let poolsToVisit = [...this._pools];
-
- // With protocol.js, each front can potentially have it's own pools containing child
- // fronts, forming a tree. Descend through all the pools to locate all child fronts.
- while (poolsToVisit.length) {
- let pool = poolsToVisit.shift();
- fronts.add(pool);
- for (let child of pool.poolChildren()) {
- poolsToVisit.push(child);
- }
- }
-
- // For each front, wait for its requests to settle
- for (let front of fronts) {
- if (front.hasRequests()) {
- requests.push(front.waitForRequestsToSettle());
- }
- }
-
- // Abort early if there are no requests
- if (!requests.length) {
- return Promise.resolve();
- }
-
- return DevToolsUtils.settleAll(requests).catch(() => {
- // One of the requests might have failed, but ignore that situation here and pipe
- // both success and failure through the same path. The important part is just that
- // we waited.
- }).then(() => {
- // Repeat, more requests may have started in response to those we just waited for
- return this.waitForRequestsToSettle();
- });
- },
-
- registerClient: function (client) {
- let actorID = client.actor;
- if (!actorID) {
- throw new Error("DebuggerServer.registerClient expects " +
- "a client instance with an `actor` attribute.");
- }
- if (!Array.isArray(client.events)) {
- throw new Error("DebuggerServer.registerClient expects " +
- "a client instance with an `events` attribute " +
- "that is an array.");
- }
- if (client.events.length > 0 && typeof (client.emit) != "function") {
- throw new Error("DebuggerServer.registerClient expects " +
- "a client instance with non-empty `events` array to" +
- "have an `emit` function.");
- }
- if (this._clients.has(actorID)) {
- throw new Error("DebuggerServer.registerClient already registered " +
- "a client for this actor.");
- }
- this._clients.set(actorID, client);
- },
-
- unregisterClient: function (client) {
- let actorID = client.actor;
- if (!actorID) {
- throw new Error("DebuggerServer.unregisterClient expects " +
- "a Client instance with a `actor` attribute.");
- }
- this._clients.delete(actorID);
- },
-
- /**
- * Actor lifetime management, echos the server's actor pools.
- */
- __pools: null,
- get _pools() {
- if (this.__pools) {
- return this.__pools;
- }
- this.__pools = new Set();
- return this.__pools;
- },
-
- addActorPool: function (pool) {
- this._pools.add(pool);
- },
- removeActorPool: function (pool) {
- this._pools.delete(pool);
- },
- getActor: function (actorID) {
- let pool = this.poolFor(actorID);
- return pool ? pool.get(actorID) : null;
- },
-
- poolFor: function (actorID) {
- for (let pool of this._pools) {
- if (pool.has(actorID)) {
- return pool;
- }
- }
- return null;
- },
-
- /**
- * Currently attached addon.
- */
- activeAddon: null
-};
-
-eventSource(DebuggerClient.prototype);
-
-class Request extends EventEmitter {
- constructor(request) {
- super();
- this.request = request;
- }
-
- get actor() {
- return this.request.to || this.request.actor;
- }
-}
-
-/**
- * Creates a tab client for the remote debugging protocol server. This client
- * is a front to the tab actor created in the server side, hiding the protocol
- * details in a traditional JavaScript API.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param form object
- * The protocol form for this tab.
- */
-function TabClient(client, form) {
- this.client = client;
- this._actor = form.from;
- this._threadActor = form.threadActor;
- this.javascriptEnabled = form.javascriptEnabled;
- this.cacheDisabled = form.cacheDisabled;
- this.thread = null;
- this.request = this.client.request;
- this.traits = form.traits || {};
- this.events = ["workerListChanged"];
-}
-
-TabClient.prototype = {
- get actor() {
- return this._actor;
- },
- get _transport() {
- return this.client._transport;
- },
-
- /**
- * Attach to a thread actor.
- *
- * @param object options
- * Configuration options.
- * - useSourceMaps: whether to use source maps or not.
- * @param function onResponse
- * Called with the response packet and a ThreadClient
- * (which will be undefined on error).
- */
- attachThread: function (options = {}, onResponse = noop) {
- if (this.thread) {
- DevToolsUtils.executeSoon(() => onResponse({}, this.thread));
- return promise.resolve([{}, this.thread]);
- }
-
- let packet = {
- to: this._threadActor,
- type: "attach",
- options,
- };
- return this.request(packet).then(response => {
- if (!response.error) {
- this.thread = new ThreadClient(this, this._threadActor);
- this.client.registerClient(this.thread);
- }
- onResponse(response, this.thread);
- return [response, this.thread];
- });
- },
-
- /**
- * Detach the client from the tab actor.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- detach: DebuggerClient.requester({
- type: "detach"
- }, {
- before: function (packet) {
- if (this.thread) {
- this.thread.detach();
- }
- return packet;
- },
- after: function (response) {
- this.client.unregisterClient(this);
- return response;
- },
- }),
-
- /**
- * Bring the window to the front.
- */
- focus: DebuggerClient.requester({
- type: "focus"
- }, {}),
-
- /**
- * Reload the page in this tab.
- *
- * @param [optional] object options
- * An object with a `force` property indicating whether or not
- * this reload should skip the cache
- */
- reload: function (options = { force: false }) {
- return this._reload(options);
- },
- _reload: DebuggerClient.requester({
- type: "reload",
- options: arg(0)
- }),
-
- /**
- * Navigate to another URL.
- *
- * @param string url
- * The URL to navigate to.
- */
- navigateTo: DebuggerClient.requester({
- type: "navigateTo",
- url: arg(0)
- }),
-
- /**
- * Reconfigure the tab actor.
- *
- * @param object options
- * A dictionary object of the new options to use in the tab actor.
- * @param function onResponse
- * Called with the response packet.
- */
- reconfigure: DebuggerClient.requester({
- type: "reconfigure",
- options: arg(0)
- }),
-
- listWorkers: DebuggerClient.requester({
- type: "listWorkers"
- }),
-
- attachWorker: function (workerActor, onResponse) {
- return this.client.attachWorker(workerActor, onResponse);
- },
-};
-
-eventSource(TabClient.prototype);
-
-function WorkerClient(client, form) {
- this.client = client;
- this._actor = form.from;
- this._isClosed = false;
- this._url = form.url;
-
- this._onClose = this._onClose.bind(this);
-
- this.addListener("close", this._onClose);
-
- this.traits = {};
-}
-
-WorkerClient.prototype = {
- get _transport() {
- return this.client._transport;
- },
-
- get request() {
- return this.client.request;
- },
-
- get actor() {
- return this._actor;
- },
-
- get url() {
- return this._url;
- },
-
- get isClosed() {
- return this._isClosed;
- },
-
- detach: DebuggerClient.requester({ type: "detach" }, {
- after: function (response) {
- if (this.thread) {
- this.client.unregisterClient(this.thread);
- }
- this.client.unregisterClient(this);
- return response;
- },
- }),
-
- attachThread: function (options = {}, onResponse = noop) {
- if (this.thread) {
- let response = [{
- type: "connected",
- threadActor: this.thread._actor,
- consoleActor: this.consoleActor,
- }, this.thread];
- DevToolsUtils.executeSoon(() => onResponse(response));
- return response;
- }
-
- // The connect call on server doesn't attach the thread as of version 44.
- return this.request({
- to: this._actor,
- type: "connect",
- options,
- }).then(connectResponse => {
- if (connectResponse.error) {
- onResponse(connectResponse, null);
- return [connectResponse, null];
- }
-
- return this.request({
- to: connectResponse.threadActor,
- type: "attach",
- options,
- }).then(attachResponse => {
- if (attachResponse.error) {
- onResponse(attachResponse, null);
- }
-
- this.thread = new ThreadClient(this, connectResponse.threadActor);
- this.consoleActor = connectResponse.consoleActor;
- this.client.registerClient(this.thread);
-
- onResponse(connectResponse, this.thread);
- return [connectResponse, this.thread];
- });
- }, error => {
- onResponse(error, null);
- });
- },
-
- _onClose: function () {
- this.removeListener("close", this._onClose);
-
- if (this.thread) {
- this.client.unregisterClient(this.thread);
- }
- this.client.unregisterClient(this);
- this._isClosed = true;
- },
-
- reconfigure: function () {
- return Promise.resolve();
- },
-
- events: ["close"]
-};
-
-eventSource(WorkerClient.prototype);
-
-function AddonClient(client, actor) {
- this._client = client;
- this._actor = actor;
- this.request = this._client.request;
- this.events = [];
-}
-
-AddonClient.prototype = {
- get actor() {
- return this._actor;
- },
- get _transport() {
- return this._client._transport;
- },
-
- /**
- * Detach the client from the addon actor.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- detach: DebuggerClient.requester({
- type: "detach"
- }, {
- after: function (response) {
- if (this._client.activeAddon === this) {
- this._client.activeAddon = null;
- }
- this._client.unregisterClient(this);
- return response;
- },
- })
-};
-
-/**
- * A RootClient object represents a root actor on the server. Each
- * DebuggerClient keeps a RootClient instance representing the root actor
- * for the initial connection; DebuggerClient's 'listTabs' and
- * 'listChildProcesses' methods forward to that root actor.
- *
- * @param client object
- * The client connection to which this actor belongs.
- * @param greeting string
- * The greeting packet from the root actor we're to represent.
- *
- * Properties of a RootClient instance:
- *
- * @property actor string
- * The name of this child's root actor.
- * @property applicationType string
- * The application type, as given in the root actor's greeting packet.
- * @property traits object
- * The traits object, as given in the root actor's greeting packet.
- */
-function RootClient(client, greeting) {
- this._client = client;
- this.actor = greeting.from;
- this.applicationType = greeting.applicationType;
- this.traits = greeting.traits;
-}
-exports.RootClient = RootClient;
-
-RootClient.prototype = {
- constructor: RootClient,
-
- /**
- * Gets the "root" form, which lists all the global actors that affect the entire
- * browser. This can replace usages of `listTabs` that only wanted the global actors
- * and didn't actually care about tabs.
- */
- getRoot: DebuggerClient.requester({ type: "getRoot" }),
-
- /**
- * List the open tabs.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- listTabs: DebuggerClient.requester({ type: "listTabs" }),
-
- /**
- * List the installed addons.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- listAddons: DebuggerClient.requester({ type: "listAddons" }),
-
- /**
- * List the registered workers.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- listWorkers: DebuggerClient.requester({ type: "listWorkers" }),
-
- /**
- * List the registered service workers.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- listServiceWorkerRegistrations: DebuggerClient.requester({
- type: "listServiceWorkerRegistrations"
- }),
-
- /**
- * List the running processes.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- listProcesses: DebuggerClient.requester({ type: "listProcesses" }),
-
- /**
- * Fetch the TabActor for the currently selected tab, or for a specific
- * tab given as first parameter.
- *
- * @param [optional] object filter
- * A dictionary object with following optional attributes:
- * - outerWindowID: used to match tabs in parent process
- * - tabId: used to match tabs in child processes
- * - tab: a reference to xul:tab element
- * If nothing is specified, returns the actor for the currently
- * selected tab.
- */
- getTab: function (filter) {
- let packet = {
- to: this.actor,
- type: "getTab"
- };
-
- if (filter) {
- if (typeof (filter.outerWindowID) == "number") {
- packet.outerWindowID = filter.outerWindowID;
- } else if (typeof (filter.tabId) == "number") {
- packet.tabId = filter.tabId;
- } else if ("tab" in filter) {
- let browser = filter.tab.linkedBrowser;
- if (browser.frameLoader.tabParent) {
- // Tabs in child process
- packet.tabId = browser.frameLoader.tabParent.tabId;
- } else if (browser.outerWindowID) {
- // <xul:browser> tabs in parent process
- packet.outerWindowID = browser.outerWindowID;
- } else {
- // <iframe mozbrowser> tabs in parent process
- let windowUtils = browser.contentWindow
- .QueryInterface(Ci.nsIInterfaceRequestor)
- .getInterface(Ci.nsIDOMWindowUtils);
- packet.outerWindowID = windowUtils.outerWindowID;
- }
- } else {
- // Throw if a filter object have been passed but without
- // any clearly idenfified filter.
- throw new Error("Unsupported argument given to getTab request");
- }
- }
-
- return this.request(packet);
- },
-
- /**
- * Fetch the WindowActor for a specific window, like a browser window in
- * Firefox, but it can be used to reach any window in the process.
- *
- * @param number outerWindowID
- * The outerWindowID of the top level window you are looking for.
- */
- getWindow: function ({ outerWindowID }) {
- if (!outerWindowID) {
- throw new Error("Must specify outerWindowID");
- }
-
- let packet = {
- to: this.actor,
- type: "getWindow",
- outerWindowID,
- };
-
- return this.request(packet);
- },
-
- /**
- * Description of protocol's actors and methods.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- protocolDescription: DebuggerClient.requester({ type: "protocolDescription" }),
-
- /*
- * Methods constructed by DebuggerClient.requester require these forwards
- * on their 'this'.
- */
- get _transport() {
- return this._client._transport;
- },
- get request() {
- return this._client.request;
- }
-};
-
-/**
- * Creates a thread client for the remote debugging protocol server. This client
- * is a front to the thread actor created in the server side, hiding the
- * protocol details in a traditional JavaScript API.
- *
- * @param client DebuggerClient|TabClient
- * The parent of the thread (tab for tab-scoped debuggers, DebuggerClient
- * for chrome debuggers).
- * @param actor string
- * The actor ID for this thread.
- */
-function ThreadClient(client, actor) {
- this._parent = client;
- this.client = client instanceof DebuggerClient ? client : client.client;
- this._actor = actor;
- this._frameCache = [];
- this._scriptCache = {};
- this._pauseGrips = {};
- this._threadGrips = {};
- this.request = this.client.request;
-}
-
-ThreadClient.prototype = {
- _state: "paused",
- get state() {
- return this._state;
- },
- get paused() {
- return this._state === "paused";
- },
-
- _pauseOnExceptions: false,
- _ignoreCaughtExceptions: false,
- _pauseOnDOMEvents: null,
-
- _actor: null,
- get actor() {
- return this._actor;
- },
-
- get _transport() {
- return this.client._transport;
- },
-
- _assertPaused: function (command) {
- if (!this.paused) {
- throw Error(command + " command sent while not paused. Currently " + this._state);
- }
- },
-
- /**
- * Resume a paused thread. If the optional limit parameter is present, then
- * the thread will also pause when that limit is reached.
- *
- * @param [optional] object limit
- * An object with a type property set to the appropriate limit (next,
- * step, or finish) per the remote debugging protocol specification.
- * Use null to specify no limit.
- * @param function onResponse
- * Called with the response packet.
- */
- _doResume: DebuggerClient.requester({
- type: "resume",
- resumeLimit: arg(0)
- }, {
- before: function (packet) {
- this._assertPaused("resume");
-
- // Put the client in a tentative "resuming" state so we can prevent
- // further requests that should only be sent in the paused state.
- this._previousState = this._state;
- this._state = "resuming";
-
- if (this._pauseOnExceptions) {
- packet.pauseOnExceptions = this._pauseOnExceptions;
- }
- if (this._ignoreCaughtExceptions) {
- packet.ignoreCaughtExceptions = this._ignoreCaughtExceptions;
- }
- if (this._pauseOnDOMEvents) {
- packet.pauseOnDOMEvents = this._pauseOnDOMEvents;
- }
- return packet;
- },
- after: function (response) {
- if (response.error && this._state == "resuming") {
- // There was an error resuming, update the state to the new one
- // reported by the server, if given (only on wrongState), otherwise
- // reset back to the previous state.
- if (response.state) {
- this._state = ThreadStateTypes[response.state];
- } else {
- this._state = this._previousState;
- }
- }
- delete this._previousState;
- return response;
- },
- }),
-
- /**
- * Reconfigure the thread actor.
- *
- * @param object options
- * A dictionary object of the new options to use in the thread actor.
- * @param function onResponse
- * Called with the response packet.
- */
- reconfigure: DebuggerClient.requester({
- type: "reconfigure",
- options: arg(0)
- }),
-
- /**
- * Resume a paused thread.
- */
- resume: function (onResponse) {
- return this._doResume(null, onResponse);
- },
-
- /**
- * Resume then pause without stepping.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- resumeThenPause: function (onResponse) {
- return this._doResume({ type: "break" }, onResponse);
- },
-
- /**
- * Step over a function call.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- stepOver: function (onResponse) {
- return this._doResume({ type: "next" }, onResponse);
- },
-
- /**
- * Step into a function call.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- stepIn: function (onResponse) {
- return this._doResume({ type: "step" }, onResponse);
- },
-
- /**
- * Step out of a function call.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- stepOut: function (onResponse) {
- return this._doResume({ type: "finish" }, onResponse);
- },
-
- /**
- * Immediately interrupt a running thread.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- interrupt: function (onResponse) {
- return this._doInterrupt(null, onResponse);
- },
-
- /**
- * Pause execution right before the next JavaScript bytecode is executed.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- breakOnNext: function (onResponse) {
- return this._doInterrupt("onNext", onResponse);
- },
-
- /**
- * Interrupt a running thread.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- _doInterrupt: DebuggerClient.requester({
- type: "interrupt",
- when: arg(0)
- }),
-
- /**
- * Enable or disable pausing when an exception is thrown.
- *
- * @param boolean pauseOnExceptions
- * Enables pausing if true, disables otherwise.
- * @param boolean ignoreCaughtExceptions
- * Whether to ignore caught exceptions
- * @param function onResponse
- * Called with the response packet.
- */
- pauseOnExceptions: function (pauseOnExceptions,
- ignoreCaughtExceptions,
- onResponse = noop) {
- this._pauseOnExceptions = pauseOnExceptions;
- this._ignoreCaughtExceptions = ignoreCaughtExceptions;
-
- // Otherwise send the flag using a standard resume request.
- if (!this.paused) {
- return this.interrupt(response => {
- if (response.error) {
- // Can't continue if pausing failed.
- onResponse(response);
- return response;
- }
- return this.resume(onResponse);
- });
- }
-
- onResponse();
- return promise.resolve();
- },
-
- /**
- * Enable pausing when the specified DOM events are triggered. Disabling
- * pausing on an event can be realized by calling this method with the updated
- * array of events that doesn't contain it.
- *
- * @param array|string events
- * An array of strings, representing the DOM event types to pause on,
- * or "*" to pause on all DOM events. Pass an empty array to
- * completely disable pausing on DOM events.
- * @param function onResponse
- * Called with the response packet in a future turn of the event loop.
- */
- pauseOnDOMEvents: function (events, onResponse = noop) {
- this._pauseOnDOMEvents = events;
- // If the debuggee is paused, the value of the array will be communicated in
- // the next resumption. Otherwise we have to force a pause in order to send
- // the array.
- if (this.paused) {
- DevToolsUtils.executeSoon(() => onResponse({}));
- return {};
- }
- return this.interrupt(response => {
- // Can't continue if pausing failed.
- if (response.error) {
- onResponse(response);
- return response;
- }
- return this.resume(onResponse);
- });
- },
-
- /**
- * Send a clientEvaluate packet to the debuggee. Response
- * will be a resume packet.
- *
- * @param string frame
- * The actor ID of the frame where the evaluation should take place.
- * @param string expression
- * The expression that will be evaluated in the scope of the frame
- * above.
- * @param function onResponse
- * Called with the response packet.
- */
- eval: DebuggerClient.requester({
- type: "clientEvaluate",
- frame: arg(0),
- expression: arg(1)
- }, {
- before: function (packet) {
- this._assertPaused("eval");
- // Put the client in a tentative "resuming" state so we can prevent
- // further requests that should only be sent in the paused state.
- this._state = "resuming";
- return packet;
- },
- after: function (response) {
- if (response.error) {
- // There was an error resuming, back to paused state.
- this._state = "paused";
- }
- return response;
- },
- }),
-
- /**
- * Detach from the thread actor.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- detach: DebuggerClient.requester({
- type: "detach"
- }, {
- after: function (response) {
- this.client.unregisterClient(this);
- this._parent.thread = null;
- return response;
- },
- }),
-
- /**
- * Release multiple thread-lifetime object actors. If any pause-lifetime
- * actors are included in the request, a |notReleasable| error will return,
- * but all the thread-lifetime ones will have been released.
- *
- * @param array actors
- * An array with actor IDs to release.
- */
- releaseMany: DebuggerClient.requester({
- type: "releaseMany",
- actors: arg(0),
- }),
-
- /**
- * Promote multiple pause-lifetime object actors to thread-lifetime ones.
- *
- * @param array actors
- * An array with actor IDs to promote.
- */
- threadGrips: DebuggerClient.requester({
- type: "threadGrips",
- actors: arg(0)
- }),
-
- /**
- * Return the event listeners defined on the page.
- *
- * @param onResponse Function
- * Called with the thread's response.
- */
- eventListeners: DebuggerClient.requester({
- type: "eventListeners"
- }),
-
- /**
- * Request the loaded sources for the current thread.
- *
- * @param onResponse Function
- * Called with the thread's response.
- */
- getSources: DebuggerClient.requester({
- type: "sources"
- }),
-
- /**
- * Clear the thread's source script cache. A scriptscleared event
- * will be sent.
- */
- _clearScripts: function () {
- if (Object.keys(this._scriptCache).length > 0) {
- this._scriptCache = {};
- this.emit("scriptscleared");
- }
- },
-
- /**
- * Request frames from the callstack for the current thread.
- *
- * @param start integer
- * The number of the youngest stack frame to return (the youngest
- * frame is 0).
- * @param count integer
- * The maximum number of frames to return, or null to return all
- * frames.
- * @param onResponse function
- * Called with the thread's response.
- */
- getFrames: DebuggerClient.requester({
- type: "frames",
- start: arg(0),
- count: arg(1)
- }),
-
- /**
- * An array of cached frames. Clients can observe the framesadded and
- * framescleared event to keep up to date on changes to this cache,
- * and can fill it using the fillFrames method.
- */
- get cachedFrames() {
- return this._frameCache;
- },
-
- /**
- * true if there are more stack frames available on the server.
- */
- get moreFrames() {
- return this.paused && (!this._frameCache || this._frameCache.length == 0
- || !this._frameCache[this._frameCache.length - 1].oldest);
- },
-
- /**
- * Request the frame environment.
- *
- * @param frameId string
- */
- getEnvironment: function (frameId) {
- return this.request({ to: frameId, type: "getEnvironment" });
- },
-
- /**
- * Ensure that at least total stack frames have been loaded in the
- * ThreadClient's stack frame cache. A framesadded event will be
- * sent when the stack frame cache is updated.
- *
- * @param total number
- * The minimum number of stack frames to be included.
- * @param callback function
- * Optional callback function called when frames have been loaded
- * @returns true if a framesadded notification should be expected.
- */
- fillFrames: function (total, callback = noop) {
- this._assertPaused("fillFrames");
- if (this._frameCache.length >= total) {
- return false;
- }
-
- let numFrames = this._frameCache.length;
-
- this.getFrames(numFrames, total - numFrames, (response) => {
- if (response.error) {
- callback(response);
- return;
- }
-
- let threadGrips = DevToolsUtils.values(this._threadGrips);
-
- for (let i in response.frames) {
- let frame = response.frames[i];
- if (!frame.where.source) {
- // Older servers use urls instead, so we need to resolve
- // them to source actors
- for (let grip of threadGrips) {
- if (grip instanceof SourceClient && grip.url === frame.url) {
- frame.where.source = grip._form;
- }
- }
- }
-
- this._frameCache[frame.depth] = frame;
- }
-
- // If we got as many frames as we asked for, there might be more
- // frames available.
- this.emit("framesadded");
-
- callback(response);
- });
-
- return true;
- },
-
- /**
- * Clear the thread's stack frame cache. A framescleared event
- * will be sent.
- */
- _clearFrames: function () {
- if (this._frameCache.length > 0) {
- this._frameCache = [];
- this.emit("framescleared");
- }
- },
-
- /**
- * Return a ObjectClient object for the given object grip.
- *
- * @param grip object
- * A pause-lifetime object grip returned by the protocol.
- */
- pauseGrip: function (grip) {
- if (grip.actor in this._pauseGrips) {
- return this._pauseGrips[grip.actor];
- }
-
- let client = new ObjectClient(this.client, grip);
- this._pauseGrips[grip.actor] = client;
- return client;
- },
-
- /**
- * Get or create a long string client, checking the grip client cache if it
- * already exists.
- *
- * @param grip Object
- * The long string grip returned by the protocol.
- * @param gripCacheName String
- * The property name of the grip client cache to check for existing
- * clients in.
- */
- _longString: function (grip, gripCacheName) {
- if (grip.actor in this[gripCacheName]) {
- return this[gripCacheName][grip.actor];
- }
-
- let client = new LongStringClient(this.client, grip);
- this[gripCacheName][grip.actor] = client;
- return client;
- },
-
- /**
- * Return an instance of LongStringClient for the given long string grip that
- * is scoped to the current pause.
- *
- * @param grip Object
- * The long string grip returned by the protocol.
- */
- pauseLongString: function (grip) {
- return this._longString(grip, "_pauseGrips");
- },
-
- /**
- * Return an instance of LongStringClient for the given long string grip that
- * is scoped to the thread lifetime.
- *
- * @param grip Object
- * The long string grip returned by the protocol.
- */
- threadLongString: function (grip) {
- return this._longString(grip, "_threadGrips");
- },
-
- /**
- * Get or create an ArrayBuffer client, checking the grip client cache if it
- * already exists.
- *
- * @param grip Object
- * The ArrayBuffer grip returned by the protocol.
- * @param gripCacheName String
- * The property name of the grip client cache to check for existing
- * clients in.
- */
- _arrayBuffer: function (grip, gripCacheName) {
- if (grip.actor in this[gripCacheName]) {
- return this[gripCacheName][grip.actor];
- }
-
- let client = new ArrayBufferClient(this.client, grip);
- this[gripCacheName][grip.actor] = client;
- return client;
- },
-
- /**
- * Return an instance of ArrayBufferClient for the given ArrayBuffer grip that
- * is scoped to the thread lifetime.
- *
- * @param grip Object
- * The ArrayBuffer grip returned by the protocol.
- */
- threadArrayBuffer: function (grip) {
- return this._arrayBuffer(grip, "_threadGrips");
- },
-
- /**
- * Clear and invalidate all the grip clients from the given cache.
- *
- * @param gripCacheName
- * The property name of the grip cache we want to clear.
- */
- _clearObjectClients: function (gripCacheName) {
- for (let id in this[gripCacheName]) {
- this[gripCacheName][id].valid = false;
- }
- this[gripCacheName] = {};
- },
-
- /**
- * Invalidate pause-lifetime grip clients and clear the list of current grip
- * clients.
- */
- _clearPauseGrips: function () {
- this._clearObjectClients("_pauseGrips");
- },
-
- /**
- * Invalidate thread-lifetime grip clients and clear the list of current grip
- * clients.
- */
- _clearThreadGrips: function () {
- this._clearObjectClients("_threadGrips");
- },
-
- /**
- * Handle thread state change by doing necessary cleanup and notifying all
- * registered listeners.
- */
- _onThreadState: function (packet) {
- this._state = ThreadStateTypes[packet.type];
- // The debugger UI may not be initialized yet so we want to keep
- // the packet around so it knows what to pause state to display
- // when it's initialized
- this._lastPausePacket = packet.type === "resumed" ? null : packet;
- this._clearFrames();
- this._clearPauseGrips();
- packet.type === ThreadStateTypes.detached && this._clearThreadGrips();
- this.client._eventsEnabled && this.emit(packet.type, packet);
- },
-
- getLastPausePacket: function () {
- return this._lastPausePacket;
- },
-
- /**
- * Return an EnvironmentClient instance for the given environment actor form.
- */
- environment: function (form) {
- return new EnvironmentClient(this.client, form);
- },
-
- /**
- * Return an instance of SourceClient for the given source actor form.
- */
- source: function (form) {
- if (form.actor in this._threadGrips) {
- return this._threadGrips[form.actor];
- }
-
- this._threadGrips[form.actor] = new SourceClient(this, form);
- return this._threadGrips[form.actor];
- },
-
- /**
- * Request the prototype and own properties of mutlipleObjects.
- *
- * @param onResponse function
- * Called with the request's response.
- * @param actors [string]
- * List of actor ID of the queried objects.
- */
- getPrototypesAndProperties: DebuggerClient.requester({
- type: "prototypesAndProperties",
- actors: arg(0)
- }),
-
- events: ["newSource"]
-};
-
-eventSource(ThreadClient.prototype);
+const {arg, DebuggerClient} = require("./debugger-client");
/**
* Creates a tracing profiler client for the remote debugging protocol
* server. This client is a front to the trace actor created on the
* server side, hiding the protocol details in a traditional
* JavaScript API.
*
* @param client DebuggerClient
@@ -2507,920 +104,9 @@ TraceClient.prototype = {
this._activeTraces.delete(response.name);
return response;
},
})
};
-/**
- * Grip clients are used to retrieve information about the relevant object.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param grip object
- * A pause-lifetime object grip returned by the protocol.
- */
-function ObjectClient(client, grip) {
- this._grip = grip;
- this._client = client;
- this.request = this._client.request;
-}
-exports.ObjectClient = ObjectClient;
-
-ObjectClient.prototype = {
- get actor() {
- return this._grip.actor;
- },
- get _transport() {
- return this._client._transport;
- },
-
- valid: true,
-
- get isFrozen() {
- return this._grip.frozen;
- },
- get isSealed() {
- return this._grip.sealed;
- },
- get isExtensible() {
- return this._grip.extensible;
- },
-
- getDefinitionSite: DebuggerClient.requester({
- type: "definitionSite"
- }, {
- before: function (packet) {
- if (this._grip.class != "Function") {
- throw new Error("getDefinitionSite is only valid for function grips.");
- }
- return packet;
- }
- }),
-
- /**
- * Request the names of a function's formal parameters.
- *
- * @param onResponse function
- * Called with an object of the form:
- * { parameterNames:[<parameterName>, ...] }
- * where each <parameterName> is the name of a parameter.
- */
- getParameterNames: DebuggerClient.requester({
- type: "parameterNames"
- }, {
- before: function (packet) {
- if (this._grip.class !== "Function") {
- throw new Error("getParameterNames is only valid for function grips.");
- }
- return packet;
- },
- }),
-
- /**
- * Request the names of the properties defined on the object and not its
- * prototype.
- *
- * @param onResponse function Called with the request's response.
- */
- getOwnPropertyNames: DebuggerClient.requester({
- type: "ownPropertyNames"
- }),
-
- /**
- * Request the prototype and own properties of the object.
- *
- * @param onResponse function Called with the request's response.
- */
- getPrototypeAndProperties: DebuggerClient.requester({
- type: "prototypeAndProperties"
- }),
-
- /**
- * Request a PropertyIteratorClient instance to ease listing
- * properties for this object.
- *
- * @param options Object
- * A dictionary object with various boolean attributes:
- * - ignoreIndexedProperties Boolean
- * If true, filters out Array items.
- * e.g. properties names between `0` and `object.length`.
- * - ignoreNonIndexedProperties Boolean
- * If true, filters out items that aren't array items
- * e.g. properties names that are not a number between `0`
- * and `object.length`.
- * - sort Boolean
- * If true, the iterator will sort the properties by name
- * before dispatching them.
- * @param onResponse function Called with the client instance.
- */
- enumProperties: DebuggerClient.requester({
- type: "enumProperties",
- options: arg(0)
- }, {
- after: function (response) {
- if (response.iterator) {
- return { iterator: new PropertyIteratorClient(this._client, response.iterator) };
- }
- return response;
- },
- }),
-
- /**
- * Request a PropertyIteratorClient instance to enumerate entries in a
- * Map/Set-like object.
- *
- * @param onResponse function Called with the request's response.
- */
- enumEntries: DebuggerClient.requester({
- type: "enumEntries"
- }, {
- before: function (packet) {
- if (!["Map", "WeakMap", "Set", "WeakSet"].includes(this._grip.class)) {
- throw new Error("enumEntries is only valid for Map/Set-like grips.");
- }
- return packet;
- },
- after: function (response) {
- if (response.iterator) {
- return {
- iterator: new PropertyIteratorClient(this._client, response.iterator)
- };
- }
- return response;
- }
- }),
-
- /**
- * Request a SymbolIteratorClient instance to enumerate symbols in an object.
- *
- * @param onResponse function Called with the request's response.
- */
- enumSymbols: DebuggerClient.requester({
- type: "enumSymbols"
- }, {
- before: function (packet) {
- if (this._grip.type !== "object") {
- throw new Error("enumSymbols is only valid for objects grips.");
- }
- return packet;
- },
- after: function (response) {
- if (response.iterator) {
- return {
- iterator: new SymbolIteratorClient(this._client, response.iterator)
- };
- }
- return response;
- }
- }),
-
- /**
- * Request the property descriptor of the object's specified property.
- *
- * @param name string The name of the requested property.
- * @param onResponse function Called with the request's response.
- */
- getProperty: DebuggerClient.requester({
- type: "property",
- name: arg(0)
- }),
-
- /**
- * Request the prototype of the object.
- *
- * @param onResponse function Called with the request's response.
- */
- getPrototype: DebuggerClient.requester({
- type: "prototype"
- }),
-
- /**
- * Request the display string of the object.
- *
- * @param onResponse function Called with the request's response.
- */
- getDisplayString: DebuggerClient.requester({
- type: "displayString"
- }),
-
- /**
- * Request the scope of the object.
- *
- * @param onResponse function Called with the request's response.
- */
- getScope: DebuggerClient.requester({
- type: "scope"
- }, {
- before: function (packet) {
- if (this._grip.class !== "Function") {
- throw new Error("scope is only valid for function grips.");
- }
- return packet;
- },
- }),
-
- /**
- * Request the promises directly depending on the current promise.
- */
- getDependentPromises: DebuggerClient.requester({
- type: "dependentPromises"
- }, {
- before: function (packet) {
- if (this._grip.class !== "Promise") {
- throw new Error("getDependentPromises is only valid for promise " +
- "grips.");
- }
- return packet;
- }
- }),
-
- /**
- * Request the stack to the promise's allocation point.
- */
- getPromiseAllocationStack: DebuggerClient.requester({
- type: "allocationStack"
- }, {
- before: function (packet) {
- if (this._grip.class !== "Promise") {
- throw new Error("getAllocationStack is only valid for promise grips.");
- }
- return packet;
- }
- }),
-
- /**
- * Request the stack to the promise's fulfillment point.
- */
- getPromiseFulfillmentStack: DebuggerClient.requester({
- type: "fulfillmentStack"
- }, {
- before: function (packet) {
- if (this._grip.class !== "Promise") {
- throw new Error("getPromiseFulfillmentStack is only valid for " +
- "promise grips.");
- }
- return packet;
- }
- }),
-
- /**
- * Request the stack to the promise's rejection point.
- */
- getPromiseRejectionStack: DebuggerClient.requester({
- type: "rejectionStack"
- }, {
- before: function (packet) {
- if (this._grip.class !== "Promise") {
- throw new Error("getPromiseRejectionStack is only valid for " +
- "promise grips.");
- }
- return packet;
- }
- })
-};
-
-/**
- * A PropertyIteratorClient provides a way to access to property names and
- * values of an object efficiently, slice by slice.
- * Note that the properties can be sorted in the backend,
- * this is controled while creating the PropertyIteratorClient
- * from ObjectClient.enumProperties.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param grip Object
- * A PropertyIteratorActor grip returned by the protocol via
- * TabActor.enumProperties request.
- */
-function PropertyIteratorClient(client, grip) {
- this._grip = grip;
- this._client = client;
- this.request = this._client.request;
-}
-
-PropertyIteratorClient.prototype = {
- get actor() {
- return this._grip.actor;
- },
-
- /**
- * Get the total number of properties available in the iterator.
- */
- get count() {
- return this._grip.count;
- },
-
- /**
- * Get one or more property names that correspond to the positions in the
- * indexes parameter.
- *
- * @param indexes Array
- * An array of property indexes.
- * @param callback Function
- * The function called when we receive the property names.
- */
- names: DebuggerClient.requester({
- type: "names",
- indexes: arg(0)
- }, {}),
-
- /**
- * Get a set of following property value(s).
- *
- * @param start Number
- * The index of the first property to fetch.
- * @param count Number
- * The number of properties to fetch.
- * @param callback Function
- * The function called when we receive the property values.
- */
- slice: DebuggerClient.requester({
- type: "slice",
- start: arg(0),
- count: arg(1)
- }, {}),
-
- /**
- * Get all the property values.
- *
- * @param callback Function
- * The function called when we receive the property values.
- */
- all: DebuggerClient.requester({
- type: "all"
- }, {}),
-};
-
-/**
- * A SymbolIteratorClient provides a way to access to symbols
- * of an object efficiently, slice by slice.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param grip Object
- * A SymbolIteratorActor grip returned by the protocol via
- * TabActor.enumSymbols request.
- */
-function SymbolIteratorClient(client, grip) {
- this._grip = grip;
- this._client = client;
- this.request = this._client.request;
-}
-
-SymbolIteratorClient.prototype = {
- get actor() {
- return this._grip.actor;
- },
-
- /**
- * Get the total number of symbols available in the iterator.
- */
- get count() {
- return this._grip.count;
- },
-
- /**
- * Get a set of following symbols.
- *
- * @param start Number
- * The index of the first symbol to fetch.
- * @param count Number
- * The number of symbols to fetch.
- * @param callback Function
- * The function called when we receive the symbols.
- */
- slice: DebuggerClient.requester({
- type: "slice",
- start: arg(0),
- count: arg(1)
- }, {}),
-
- /**
- * Get all the symbols.
- *
- * @param callback Function
- * The function called when we receive the symbols.
- */
- all: DebuggerClient.requester({
- type: "all"
- }, {}),
-};
-
-/**
- * A ArrayBufferClient provides a way to access ArrayBuffer from the
- * debugger server.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param grip Object
- * A pause-lifetime ArrayBuffer grip returned by the protocol.
- */
-function ArrayBufferClient(client, grip) {
- this._grip = grip;
- this._client = client;
- this.request = this._client.request;
-}
-ArrayBufferClient.prototype = {
- get actor() {
- return this._grip.actor;
- },
- get length() {
- return this._grip.length;
- },
- get _transport() {
- return this._client._transport;
- },
-
- valid: true,
-
- slice: DebuggerClient.requester({
- type: "slice",
- start: arg(0),
- count: arg(1)
- }),
-};
-
-/**
- * A LongStringClient provides a way to access "very long" strings from the
- * debugger server.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param grip Object
- * A pause-lifetime long string grip returned by the protocol.
- */
-function LongStringClient(client, grip) {
- this._grip = grip;
- this._client = client;
- this.request = this._client.request;
-}
-exports.LongStringClient = LongStringClient;
-
-LongStringClient.prototype = {
- get actor() {
- return this._grip.actor;
- },
- get length() {
- return this._grip.length;
- },
- get initial() {
- return this._grip.initial;
- },
- get _transport() {
- return this._client._transport;
- },
-
- valid: true,
-
- /**
- * Get the substring of this LongString from start to end.
- *
- * @param start Number
- * The starting index.
- * @param end Number
- * The ending index.
- * @param callback Function
- * The function called when we receive the substring.
- */
- substring: DebuggerClient.requester({
- type: "substring",
- start: arg(0),
- end: arg(1)
- }),
-};
-
-/**
- * A SourceClient provides a way to access the source text of a script.
- *
- * @param client ThreadClient
- * The thread client parent.
- * @param form Object
- * The form sent across the remote debugging protocol.
- */
-function SourceClient(client, form) {
- this._form = form;
- this._isBlackBoxed = form.isBlackBoxed;
- this._isPrettyPrinted = form.isPrettyPrinted;
- this._activeThread = client;
- this._client = client.client;
-}
-
-SourceClient.prototype = {
- get _transport() {
- return this._client._transport;
- },
- get isBlackBoxed() {
- return this._isBlackBoxed;
- },
- get isPrettyPrinted() {
- return this._isPrettyPrinted;
- },
- get actor() {
- return this._form.actor;
- },
- get request() {
- return this._client.request;
- },
- get url() {
- return this._form.url;
- },
-
- /**
- * Black box this SourceClient's source.
- *
- * @param callback Function
- * The callback function called when we receive the response from the server.
- */
- blackBox: DebuggerClient.requester({
- type: "blackbox"
- }, {
- after: function (response) {
- if (!response.error) {
- this._isBlackBoxed = true;
- if (this._activeThread) {
- this._activeThread.emit("blackboxchange", this);
- }
- }
- return response;
- }
- }),
-
- /**
- * Un-black box this SourceClient's source.
- *
- * @param callback Function
- * The callback function called when we receive the response from the server.
- */
- unblackBox: DebuggerClient.requester({
- type: "unblackbox"
- }, {
- after: function (response) {
- if (!response.error) {
- this._isBlackBoxed = false;
- if (this._activeThread) {
- this._activeThread.emit("blackboxchange", this);
- }
- }
- return response;
- }
- }),
-
- /**
- * Get Executable Lines from a source
- *
- * @param callback Function
- * The callback function called when we receive the response from the server.
- */
- getExecutableLines: function (cb = noop) {
- let packet = {
- to: this._form.actor,
- type: "getExecutableLines"
- };
-
- return this._client.request(packet).then(res => {
- cb(res.lines);
- return res.lines;
- });
- },
-
- /**
- * Get a long string grip for this SourceClient's source.
- */
- source: function (callback = noop) {
- let packet = {
- to: this._form.actor,
- type: "source"
- };
- return this._client.request(packet).then(response => {
- return this._onSourceResponse(response, callback);
- });
- },
-
- /**
- * Pretty print this source's text.
- */
- prettyPrint: function (indent, callback = noop) {
- const packet = {
- to: this._form.actor,
- type: "prettyPrint",
- indent
- };
- return this._client.request(packet).then(response => {
- if (!response.error) {
- this._isPrettyPrinted = true;
- this._activeThread._clearFrames();
- this._activeThread.emit("prettyprintchange", this);
- }
- return this._onSourceResponse(response, callback);
- });
- },
-
- /**
- * Stop pretty printing this source's text.
- */
- disablePrettyPrint: function (callback = noop) {
- const packet = {
- to: this._form.actor,
- type: "disablePrettyPrint"
- };
- return this._client.request(packet).then(response => {
- if (!response.error) {
- this._isPrettyPrinted = false;
- this._activeThread._clearFrames();
- this._activeThread.emit("prettyprintchange", this);
- }
- return this._onSourceResponse(response, callback);
- });
- },
-
- _onSourceResponse: function (response, callback) {
- if (response.error) {
- callback(response);
- return response;
- }
-
- if (typeof response.source === "string") {
- callback(response);
- return response;
- }
-
- let { contentType, source } = response;
- if (source.type === "arrayBuffer") {
- let arrayBuffer = this._activeThread.threadArrayBuffer(source);
- return arrayBuffer.slice(0, arrayBuffer.length).then(function (resp) {
- if (resp.error) {
- callback(resp);
- return resp;
- }
- // Keeping str as a string, ArrayBuffer/Uint8Array will not survive
- // setIn/mergeIn operations.
- const str = atob(resp.encoded);
- let newResponse = {
- source: {
- binary: str,
- toString: () => "[wasm]",
- },
- contentType,
- };
- callback(newResponse);
- return newResponse;
- });
- }
-
- let longString = this._activeThread.threadLongString(source);
- return longString.substring(0, longString.length).then(function (resp) {
- if (resp.error) {
- callback(resp);
- return resp;
- }
-
- let newResponse = {
- source: resp.substring,
- contentType: contentType
- };
- callback(newResponse);
- return newResponse;
- });
- },
-
- /**
- * Request to set a breakpoint in the specified location.
- *
- * @param object location
- * The location and condition of the breakpoint in
- * the form of { line[, column, condition] }.
- * @param function onResponse
- * Called with the thread's response.
- */
- setBreakpoint: function ({ line, column, condition, noSliding }, onResponse = noop) {
- // A helper function that sets the breakpoint.
- let doSetBreakpoint = callback => {
- let root = this._client.mainRoot;
- let location = {
- line,
- column,
- };
-
- let packet = {
- to: this.actor,
- type: "setBreakpoint",
- location,
- condition,
- noSliding,
- };
-
- // Backwards compatibility: send the breakpoint request to the
- // thread if the server doesn't support Debugger.Source actors.
- if (!root.traits.debuggerSourceActors) {
- packet.to = this._activeThread.actor;
- packet.location.url = this.url;
- }
-
- return this._client.request(packet).then(response => {
- // Ignoring errors, since the user may be setting a breakpoint in a
- // dead script that will reappear on a page reload.
- let bpClient;
- if (response.actor) {
- bpClient = new BreakpointClient(
- this._client,
- this,
- response.actor,
- location,
- root.traits.conditionalBreakpoints ? condition : undefined
- );
- }
- onResponse(response, bpClient);
- if (callback) {
- callback();
- }
- return [response, bpClient];
- });
- };
-
- // If the debuggee is paused, just set the breakpoint.
- if (this._activeThread.paused) {
- return doSetBreakpoint();
- }
- // Otherwise, force a pause in order to set the breakpoint.
- return this._activeThread.interrupt().then(response => {
- if (response.error) {
- // Can't set the breakpoint if pausing failed.
- onResponse(response);
- return response;
- }
-
- const { type, why } = response;
- const cleanUp = type == "paused" && why.type == "interrupted"
- ? () => this._activeThread.resume()
- : noop;
-
- return doSetBreakpoint(cleanUp);
- });
- }
-};
-
-/**
- * Breakpoint clients are used to remove breakpoints that are no longer used.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param sourceClient SourceClient
- * The source where this breakpoint exists
- * @param actor string
- * The actor ID for this breakpoint.
- * @param location object
- * The location of the breakpoint. This is an object with two properties:
- * url and line.
- * @param condition string
- * The conditional expression of the breakpoint
- */
-function BreakpointClient(client, sourceClient, actor, location, condition) {
- this._client = client;
- this._actor = actor;
- this.location = location;
- this.location.actor = sourceClient.actor;
- this.location.url = sourceClient.url;
- this.source = sourceClient;
- this.request = this._client.request;
-
- // The condition property should only exist if it's a truthy value
- if (condition) {
- this.condition = condition;
- }
-}
-
-BreakpointClient.prototype = {
-
- _actor: null,
- get actor() {
- return this._actor;
- },
- get _transport() {
- return this._client._transport;
- },
-
- /**
- * Remove the breakpoint from the server.
- */
- remove: DebuggerClient.requester({
- type: "delete"
- }),
-
- /**
- * Determines if this breakpoint has a condition
- */
- hasCondition: function () {
- let root = this._client.mainRoot;
- // XXX bug 990137: We will remove support for client-side handling of
- // conditional breakpoints
- if (root.traits.conditionalBreakpoints) {
- return "condition" in this;
- }
- return "conditionalExpression" in this;
- },
-
- /**
- * Get the condition of this breakpoint. Currently we have to
- * support locally emulated conditional breakpoints until the
- * debugger servers are updated (see bug 990137). We used a
- * different property when moving it server-side to ensure that we
- * are testing the right code.
- */
- getCondition: function () {
- let root = this._client.mainRoot;
- if (root.traits.conditionalBreakpoints) {
- return this.condition;
- }
- return this.conditionalExpression;
- },
-
- /**
- * Set the condition of this breakpoint
- */
- setCondition: function (gThreadClient, condition) {
- let root = this._client.mainRoot;
- let deferred = promise.defer();
-
- if (root.traits.conditionalBreakpoints) {
- let info = {
- line: this.location.line,
- column: this.location.column,
- condition: condition
- };
-
- // Remove the current breakpoint and add a new one with the
- // condition.
- this.remove(response => {
- if (response && response.error) {
- deferred.reject(response);
- return;
- }
-
- this.source.setBreakpoint(info, (resp, newBreakpoint) => {
- if (resp && resp.error) {
- deferred.reject(resp);
- } else {
- deferred.resolve(newBreakpoint);
- }
- });
- });
- } else {
- // The property shouldn't even exist if the condition is blank
- if (condition === "") {
- delete this.conditionalExpression;
- } else {
- this.conditionalExpression = condition;
- }
- deferred.resolve(this);
- }
-
- return deferred.promise;
- }
-};
-
-eventSource(BreakpointClient.prototype);
-
-/**
- * Environment clients are used to manipulate the lexical environment actors.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param form Object
- * The form sent across the remote debugging protocol.
- */
-function EnvironmentClient(client, form) {
- this._client = client;
- this._form = form;
- this.request = this._client.request;
-}
-exports.EnvironmentClient = EnvironmentClient;
-
-EnvironmentClient.prototype = {
-
- get actor() {
- return this._form.actor;
- },
- get _transport() {
- return this._client._transport;
- },
-
- /**
- * Fetches the bindings introduced by this lexical environment.
- */
- getBindings: DebuggerClient.requester({
- type: "bindings"
- }),
-
- /**
- * Changes the value of the identifier whose name is name (a string) to that
- * represented by value (a grip).
- */
- assign: DebuggerClient.requester({
- type: "assign",
- name: arg(0),
- value: arg(1)
- })
-};
-
-eventSource(EnvironmentClient.prototype);
+module.exports = TraceClient;
copy from devtools/shared/client/main.js
copy to devtools/shared/client/worker-client.js
--- a/devtools/shared/client/main.js
+++ b/devtools/shared/client/worker-client.js
@@ -1,1470 +1,21 @@
-/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
-/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
-const { Ci, Cu } = require("chrome");
+const {DebuggerClient} = require("./debugger-client");
const DevToolsUtils = require("devtools/shared/DevToolsUtils");
-const { getStack, callFunctionWithAsyncStack } = require("devtools/shared/platform/stack");
-
-const promise = Cu.import("resource://devtools/shared/deprecated-sync-thenables.js", {}).Promise;
-
-loader.lazyRequireGetter(this, "EventEmitter", "devtools/shared/event-emitter");
-loader.lazyRequireGetter(this, "WebConsoleClient", "devtools/shared/webconsole/client", true);
-loader.lazyRequireGetter(this, "DebuggerSocket", "devtools/shared/security/socket", true);
-loader.lazyRequireGetter(this, "Authentication", "devtools/shared/security/auth");
+const eventSource = require("./event-source");
+loader.lazyRequireGetter(this, "ThreadClient", "devtools/shared/client/thread-client");
const noop = () => {};
-/**
- * TODO: Get rid of this API in favor of EventTarget (bug 1042642)
- *
- * Add simple event notification to a prototype object. Any object that has
- * some use for event notifications or the observer pattern in general can be
- * augmented with the necessary facilities by passing its prototype to this
- * function.
- *
- * @param proto object
- * The prototype object that will be modified.
- */
-function eventSource(proto) {
- /**
- * Add a listener to the event source for a given event.
- *
- * @param name string
- * The event to listen for.
- * @param listener function
- * Called when the event is fired. If the same listener
- * is added more than once, it will be called once per
- * addListener call.
- */
- proto.addListener = function (name, listener) {
- if (typeof listener != "function") {
- throw TypeError("Listeners must be functions.");
- }
-
- if (!this._listeners) {
- this._listeners = {};
- }
-
- this._getListeners(name).push(listener);
- };
-
- /**
- * Add a listener to the event source for a given event. The
- * listener will be removed after it is called for the first time.
- *
- * @param name string
- * The event to listen for.
- * @param listener function
- * Called when the event is fired.
- */
- proto.addOneTimeListener = function (name, listener) {
- let l = (...args) => {
- this.removeListener(name, l);
- listener.apply(null, args);
- };
- this.addListener(name, l);
- };
-
- /**
- * Remove a listener from the event source previously added with
- * addListener().
- *
- * @param name string
- * The event name used during addListener to add the listener.
- * @param listener function
- * The callback to remove. If addListener was called multiple
- * times, all instances will be removed.
- */
- proto.removeListener = function (name, listener) {
- if (!this._listeners || (listener && !this._listeners[name])) {
- return;
- }
-
- if (!listener) {
- this._listeners[name] = [];
- } else {
- this._listeners[name] =
- this._listeners[name].filter(l => l != listener);
- }
- };
-
- /**
- * Returns the listeners for the specified event name. If none are defined it
- * initializes an empty list and returns that.
- *
- * @param name string
- * The event name.
- */
- proto._getListeners = function (name) {
- if (name in this._listeners) {
- return this._listeners[name];
- }
- this._listeners[name] = [];
- return this._listeners[name];
- };
-
- /**
- * Notify listeners of an event.
- *
- * @param name string
- * The event to fire.
- * @param arguments
- * All arguments will be passed along to the listeners,
- * including the name argument.
- */
- proto.emit = function () {
- if (!this._listeners) {
- return;
- }
-
- let name = arguments[0];
- let listeners = this._getListeners(name).slice(0);
-
- for (let listener of listeners) {
- try {
- listener.apply(null, arguments);
- } catch (e) {
- // Prevent a bad listener from interfering with the others.
- DevToolsUtils.reportException("notify event '" + name + "'", e);
- }
- }
- };
-}
-
-/**
- * Set of protocol messages that affect thread state, and the
- * state the actor is in after each message.
- */
-const ThreadStateTypes = {
- "paused": "paused",
- "resumed": "attached",
- "detached": "detached",
- "running": "attached"
-};
-
-/**
- * Set of protocol messages that are sent by the server without a prior request
- * by the client.
- */
-const UnsolicitedNotifications = {
- "consoleAPICall": "consoleAPICall",
- "eventNotification": "eventNotification",
- "fileActivity": "fileActivity",
- "lastPrivateContextExited": "lastPrivateContextExited",
- "logMessage": "logMessage",
- "networkEvent": "networkEvent",
- "networkEventUpdate": "networkEventUpdate",
- "newGlobal": "newGlobal",
- "newScript": "newScript",
- "tabDetached": "tabDetached",
- "tabListChanged": "tabListChanged",
- "reflowActivity": "reflowActivity",
- "addonListChanged": "addonListChanged",
- "workerListChanged": "workerListChanged",
- "serviceWorkerRegistrationListChanged": "serviceWorkerRegistrationList",
- "tabNavigated": "tabNavigated",
- "frameUpdate": "frameUpdate",
- "pageError": "pageError",
- "documentLoad": "documentLoad",
- "enteredFrame": "enteredFrame",
- "exitedFrame": "exitedFrame",
- "appOpen": "appOpen",
- "appClose": "appClose",
- "appInstall": "appInstall",
- "appUninstall": "appUninstall",
- "evaluationResult": "evaluationResult",
- "newSource": "newSource",
- "updatedSource": "updatedSource",
- "inspectObject": "inspectObject"
-};
-
-/**
- * Set of pause types that are sent by the server and not as an immediate
- * response to a client request.
- */
-const UnsolicitedPauses = {
- "resumeLimit": "resumeLimit",
- "debuggerStatement": "debuggerStatement",
- "breakpoint": "breakpoint",
- "DOMEvent": "DOMEvent",
- "watchpoint": "watchpoint",
- "exception": "exception"
-};
-
-/**
- * Creates a client for the remote debugging protocol server. This client
- * provides the means to communicate with the server and exchange the messages
- * required by the protocol in a traditional JavaScript API.
- */
-const DebuggerClient = exports.DebuggerClient = function (transport) {
- this._transport = transport;
- this._transport.hooks = this;
-
- // Map actor ID to client instance for each actor type.
- this._clients = new Map();
-
- this._pendingRequests = new Map();
- this._activeRequests = new Map();
- this._eventsEnabled = true;
-
- this.traits = {};
-
- this.request = this.request.bind(this);
- this.localTransport = this._transport.onOutputStreamReady === undefined;
-
- /*
- * As the first thing on the connection, expect a greeting packet from
- * the connection's root actor.
- */
- this.mainRoot = null;
- this.expectReply("root", (packet) => {
- this.mainRoot = new RootClient(this, packet);
- this.emit("connected", packet.applicationType, packet.traits);
- });
-};
-
-/**
- * A declarative helper for defining methods that send requests to the server.
- *
- * @param packetSkeleton
- * The form of the packet to send. Can specify fields to be filled from
- * the parameters by using the |arg| function.
- * @param before
- * The function to call before sending the packet. Is passed the packet,
- * and the return value is used as the new packet. The |this| context is
- * the instance of the client object we are defining a method for.
- * @param after
- * The function to call after the response is received. It is passed the
- * response, and the return value is considered the new response that
- * will be passed to the callback. The |this| context is the instance of
- * the client object we are defining a method for.
- * @return Request
- * The `Request` object that is a Promise object and resolves once
- * we receive the response. (See request method for more details)
- */
-DebuggerClient.requester = function (packetSkeleton, config = {}) {
- let { before, after } = config;
- return DevToolsUtils.makeInfallible(function (...args) {
- let outgoingPacket = {
- to: packetSkeleton.to || this.actor
- };
-
- let maxPosition = -1;
- for (let k of Object.keys(packetSkeleton)) {
- if (packetSkeleton[k] instanceof DebuggerClient.Argument) {
- let { position } = packetSkeleton[k];
- outgoingPacket[k] = packetSkeleton[k].getArgument(args);
- maxPosition = Math.max(position, maxPosition);
- } else {
- outgoingPacket[k] = packetSkeleton[k];
- }
- }
-
- if (before) {
- outgoingPacket = before.call(this, outgoingPacket);
- }
-
- return this.request(outgoingPacket, DevToolsUtils.makeInfallible((response) => {
- if (after) {
- let { from } = response;
- response = after.call(this, response);
- if (!response.from) {
- response.from = from;
- }
- }
-
- // The callback is always the last parameter.
- let thisCallback = args[maxPosition + 1];
- if (thisCallback) {
- thisCallback(response);
- }
- return response;
- }, "DebuggerClient.requester request callback"));
- }, "DebuggerClient.requester");
-};
-
-function arg(pos) {
- return new DebuggerClient.Argument(pos);
-}
-
-DebuggerClient.Argument = function (position) {
- this.position = position;
-};
-
-DebuggerClient.Argument.prototype.getArgument = function (params) {
- if (!(this.position in params)) {
- throw new Error("Bad index into params: " + this.position);
- }
- return params[this.position];
-};
-
-// Expose these to save callers the trouble of importing DebuggerSocket
-DebuggerClient.socketConnect = function (options) {
- // Defined here instead of just copying the function to allow lazy-load
- return DebuggerSocket.connect(options);
-};
-DevToolsUtils.defineLazyGetter(DebuggerClient, "Authenticators", () => {
- return Authentication.Authenticators;
-});
-DevToolsUtils.defineLazyGetter(DebuggerClient, "AuthenticationResult", () => {
- return Authentication.AuthenticationResult;
-});
-
-DebuggerClient.prototype = {
- /**
- * Connect to the server and start exchanging protocol messages.
- *
- * @param onConnected function
- * If specified, will be called when the greeting packet is
- * received from the debugging server.
- *
- * @return Promise
- * Resolves once connected with an array whose first element
- * is the application type, by default "browser", and the second
- * element is the traits object (help figure out the features
- * and behaviors of the server we connect to. See RootActor).
- */
- connect: function (onConnected) {
- let deferred = promise.defer();
- this.emit("connect");
-
- // Also emit the event on the |DebuggerClient| object (not on the instance),
- // so it's possible to track all instances.
- EventEmitter.emit(DebuggerClient, "connect", this);
-
- this.addOneTimeListener("connected", (name, applicationType, traits) => {
- this.traits = traits;
- if (onConnected) {
- onConnected(applicationType, traits);
- }
- deferred.resolve([applicationType, traits]);
- });
-
- this._transport.ready();
- return deferred.promise;
- },
-
- /**
- * Shut down communication with the debugging server.
- *
- * @param onClosed function
- * If specified, will be called when the debugging connection
- * has been closed. This parameter is deprecated - please use
- * the returned Promise.
- * @return Promise
- * Resolves after the underlying transport is closed.
- */
- close: function (onClosed) {
- let deferred = promise.defer();
- if (onClosed) {
- deferred.promise.then(onClosed);
- }
-
- // Disable detach event notifications, because event handlers will be in a
- // cleared scope by the time they run.
- this._eventsEnabled = false;
-
- let cleanup = () => {
- this._transport.close();
- this._transport = null;
- };
-
- // If the connection is already closed,
- // there is no need to detach client
- // as we won't be able to send any message.
- if (this._closed) {
- cleanup();
- deferred.resolve();
- return deferred.promise;
- }
-
- this.addOneTimeListener("closed", deferred.resolve);
-
- // Call each client's `detach` method by calling
- // lastly registered ones first to give a chance
- // to detach child clients first.
- let clients = [...this._clients.values()];
- this._clients.clear();
- const detachClients = () => {
- let client = clients.pop();
- if (!client) {
- // All clients detached.
- cleanup();
- return;
- }
- if (client.detach) {
- client.detach(detachClients);
- return;
- }
- detachClients();
- };
- detachClients();
-
- return deferred.promise;
- },
-
- /*
- * This function exists only to preserve DebuggerClient's interface;
- * new code should say 'client.mainRoot.listTabs()'.
- */
- listTabs: function (onResponse) {
- return this.mainRoot.listTabs(onResponse);
- },
-
- /*
- * This function exists only to preserve DebuggerClient's interface;
- * new code should say 'client.mainRoot.listAddons()'.
- */
- listAddons: function (onResponse) {
- return this.mainRoot.listAddons(onResponse);
- },
-
- getTab: function (filter) {
- return this.mainRoot.getTab(filter);
- },
-
- /**
- * Attach to a tab actor.
- *
- * @param string tabActor
- * The actor ID for the tab to attach.
- * @param function onResponse
- * Called with the response packet and a TabClient
- * (which will be undefined on error).
- */
- attachTab: function (tabActor, onResponse = noop) {
- if (this._clients.has(tabActor)) {
- let cachedTab = this._clients.get(tabActor);
- let cachedResponse = {
- cacheDisabled: cachedTab.cacheDisabled,
- javascriptEnabled: cachedTab.javascriptEnabled,
- traits: cachedTab.traits,
- };
- DevToolsUtils.executeSoon(() => onResponse(cachedResponse, cachedTab));
- return promise.resolve([cachedResponse, cachedTab]);
- }
-
- let packet = {
- to: tabActor,
- type: "attach"
- };
- return this.request(packet).then(response => {
- let tabClient;
- if (!response.error) {
- tabClient = new TabClient(this, response);
- this.registerClient(tabClient);
- }
- onResponse(response, tabClient);
- return [response, tabClient];
- });
- },
-
- attachWorker: function (workerActor, onResponse = noop) {
- let workerClient = this._clients.get(workerActor);
- if (workerClient !== undefined) {
- let response = {
- from: workerClient.actor,
- type: "attached",
- url: workerClient.url
- };
- DevToolsUtils.executeSoon(() => onResponse(response, workerClient));
- return promise.resolve([response, workerClient]);
- }
-
- return this.request({ to: workerActor, type: "attach" }).then(response => {
- if (response.error) {
- onResponse(response, null);
- return [response, null];
- }
-
- workerClient = new WorkerClient(this, response);
- this.registerClient(workerClient);
- onResponse(response, workerClient);
- return [response, workerClient];
- });
- },
-
- /**
- * Attach to an addon actor.
- *
- * @param string addonActor
- * The actor ID for the addon to attach.
- * @param function onResponse
- * Called with the response packet and a AddonClient
- * (which will be undefined on error).
- */
- attachAddon: function (addonActor, onResponse = noop) {
- let packet = {
- to: addonActor,
- type: "attach"
- };
- return this.request(packet).then(response => {
- let addonClient;
- if (!response.error) {
- addonClient = new AddonClient(this, addonActor);
- this.registerClient(addonClient);
- this.activeAddon = addonClient;
- }
- onResponse(response, addonClient);
- return [response, addonClient];
- });
- },
-
- /**
- * Attach to a Web Console actor.
- *
- * @param string consoleActor
- * The ID for the console actor to attach to.
- * @param array listeners
- * The console listeners you want to start.
- * @param function onResponse
- * Called with the response packet and a WebConsoleClient
- * instance (which will be undefined on error).
- */
- attachConsole:
- function (consoleActor, listeners, onResponse = noop) {
- let packet = {
- to: consoleActor,
- type: "startListeners",
- listeners: listeners,
- };
-
- return this.request(packet).then(response => {
- let consoleClient;
- if (!response.error) {
- if (this._clients.has(consoleActor)) {
- consoleClient = this._clients.get(consoleActor);
- } else {
- consoleClient = new WebConsoleClient(this, response);
- this.registerClient(consoleClient);
- }
- }
- onResponse(response, consoleClient);
- return [response, consoleClient];
- });
- },
-
- /**
- * Attach to a global-scoped thread actor for chrome debugging.
- *
- * @param string threadActor
- * The actor ID for the thread to attach.
- * @param function onResponse
- * Called with the response packet and a ThreadClient
- * (which will be undefined on error).
- * @param object options
- * Configuration options.
- * - useSourceMaps: whether to use source maps or not.
- */
- attachThread: function (threadActor, onResponse = noop, options = {}) {
- if (this._clients.has(threadActor)) {
- let client = this._clients.get(threadActor);
- DevToolsUtils.executeSoon(() => onResponse({}, client));
- return promise.resolve([{}, client]);
- }
-
- let packet = {
- to: threadActor,
- type: "attach",
- options,
- };
- return this.request(packet).then(response => {
- let threadClient;
- if (!response.error) {
- threadClient = new ThreadClient(this, threadActor);
- this.registerClient(threadClient);
- }
- onResponse(response, threadClient);
- return [response, threadClient];
- });
- },
-
- /**
- * Attach to a trace actor.
- *
- * @param string traceActor
- * The actor ID for the tracer to attach.
- * @param function onResponse
- * Called with the response packet and a TraceClient
- * (which will be undefined on error).
- */
- attachTracer: function (traceActor, onResponse = noop) {
- if (this._clients.has(traceActor)) {
- let client = this._clients.get(traceActor);
- DevToolsUtils.executeSoon(() => onResponse({}, client));
- return promise.resolve([{}, client]);
- }
-
- let packet = {
- to: traceActor,
- type: "attach"
- };
- return this.request(packet).then(response => {
- let traceClient;
- if (!response.error) {
- traceClient = new TraceClient(this, traceActor);
- this.registerClient(traceClient);
- }
- onResponse(response, traceClient);
- return [response, traceClient];
- });
- },
-
- /**
- * Fetch the ChromeActor for the main process or ChildProcessActor for a
- * a given child process ID.
- *
- * @param number id
- * The ID for the process to attach (returned by `listProcesses`).
- * Connected to the main process if omitted, or is 0.
- */
- getProcess: function (id) {
- let packet = {
- to: "root",
- type: "getProcess"
- };
- if (typeof (id) == "number") {
- packet.id = id;
- }
- return this.request(packet);
- },
-
- /**
- * Release an object actor.
- *
- * @param string actor
- * The actor ID to send the request to.
- * @param onResponse function
- * If specified, will be called with the response packet when
- * debugging server responds.
- */
- release: DebuggerClient.requester({
- to: arg(0),
- type: "release"
- }),
-
- /**
- * Send a request to the debugging server.
- *
- * @param packet object
- * A JSON packet to send to the debugging server.
- * @param onResponse function
- * If specified, will be called with the JSON response packet when
- * debugging server responds.
- * @return Request
- * This object emits a number of events to allow you to respond to
- * different parts of the request lifecycle.
- * It is also a Promise object, with a `then` method, that is resolved
- * whenever a JSON or a Bulk response is received; and is rejected
- * if the response is an error.
- * Note: This return value can be ignored if you are using JSON alone,
- * because the callback provided in |onResponse| will be bound to the
- * "json-reply" event automatically.
- *
- * Events emitted:
- * * json-reply: The server replied with a JSON packet, which is
- * passed as event data.
- * * bulk-reply: The server replied with bulk data, which you can read
- * using the event data object containing:
- * * actor: Name of actor that received the packet
- * * type: Name of actor's method that was called on receipt
- * * length: Size of the data to be read
- * * stream: This input stream should only be used directly if you
- * can ensure that you will read exactly |length| bytes
- * and will not close the stream when reading is complete
- * * done: If you use the stream directly (instead of |copyTo|
- * below), you must signal completion by resolving /
- * rejecting this deferred. If it's rejected, the
- * transport will be closed. If an Error is supplied as a
- * rejection value, it will be logged via |dumpn|. If you
- * do use |copyTo|, resolving is taken care of for you
- * when copying completes.
- * * copyTo: A helper function for getting your data out of the
- * stream that meets the stream handling requirements
- * above, and has the following signature:
- * @param output nsIAsyncOutputStream
- * The stream to copy to.
- * @return Promise
- * The promise is resolved when copying completes or
- * rejected if any (unexpected) errors occur.
- * This object also emits "progress" events for each chunk
- * that is copied. See stream-utils.js.
- */
- request: function (packet, onResponse) {
- if (!this.mainRoot) {
- throw Error("Have not yet received a hello packet from the server.");
- }
- let type = packet.type || "";
- if (!packet.to) {
- throw Error("'" + type + "' request packet has no destination.");
- }
-
- // The onResponse callback might modify the response, so we need to call
- // it and resolve the promise with its result if it's truthy.
- const safeOnResponse = response => {
- if (!onResponse) {
- return response;
- }
- return onResponse(response) || response;
- };
-
- if (this._closed) {
- let msg = "'" + type + "' request packet to " +
- "'" + packet.to + "' " +
- "can't be sent as the connection is closed.";
- let resp = { error: "connectionClosed", message: msg };
- return promise.reject(safeOnResponse(resp));
- }
-
- let request = new Request(packet);
- request.format = "json";
- request.stack = getStack();
-
- // Implement a Promise like API on the returned object
- // that resolves/rejects on request response
- let deferred = promise.defer();
- function listenerJson(resp) {
- removeRequestListeners();
- if (resp.error) {
- deferred.reject(safeOnResponse(resp));
- } else {
- deferred.resolve(safeOnResponse(resp));
- }
- }
- function listenerBulk(resp) {
- removeRequestListeners();
- deferred.resolve(safeOnResponse(resp));
- }
-
- const removeRequestListeners = () => {
- request.off("json-reply", listenerJson);
- request.off("bulk-reply", listenerBulk);
- };
-
- request.on("json-reply", listenerJson);
- request.on("bulk-reply", listenerBulk);
-
- this._sendOrQueueRequest(request);
- request.then = deferred.promise.then.bind(deferred.promise);
-
- return request;
- },
-
- /**
- * Transmit streaming data via a bulk request.
- *
- * This method initiates the bulk send process by queuing up the header data.
- * The caller receives eventual access to a stream for writing.
- *
- * Since this opens up more options for how the server might respond (it could
- * send back either JSON or bulk data), and the returned Request object emits
- * events for different stages of the request process that you may want to
- * react to.
- *
- * @param request Object
- * This is modeled after the format of JSON packets above, but does not
- * actually contain the data, but is instead just a routing header:
- * * actor: Name of actor that will receive the packet
- * * type: Name of actor's method that should be called on receipt
- * * length: Size of the data to be sent
- * @return Request
- * This object emits a number of events to allow you to respond to
- * different parts of the request lifecycle.
- *
- * Events emitted:
- * * bulk-send-ready: Ready to send bulk data to the server, using the
- * event data object containing:
- * * stream: This output stream should only be used directly if
- * you can ensure that you will write exactly |length|
- * bytes and will not close the stream when writing is
- * complete
- * * done: If you use the stream directly (instead of |copyFrom|
- * below), you must signal completion by resolving /
- * rejecting this deferred. If it's rejected, the
- * transport will be closed. If an Error is supplied as
- * a rejection value, it will be logged via |dumpn|. If
- * you do use |copyFrom|, resolving is taken care of for
- * you when copying completes.
- * * copyFrom: A helper function for getting your data onto the
- * stream that meets the stream handling requirements
- * above, and has the following signature:
- * @param input nsIAsyncInputStream
- * The stream to copy from.
- * @return Promise
- * The promise is resolved when copying completes or
- * rejected if any (unexpected) errors occur.
- * This object also emits "progress" events for each chunk
- * that is copied. See stream-utils.js.
- * * json-reply: The server replied with a JSON packet, which is
- * passed as event data.
- * * bulk-reply: The server replied with bulk data, which you can read
- * using the event data object containing:
- * * actor: Name of actor that received the packet
- * * type: Name of actor's method that was called on receipt
- * * length: Size of the data to be read
- * * stream: This input stream should only be used directly if you
- * can ensure that you will read exactly |length| bytes
- * and will not close the stream when reading is complete
- * * done: If you use the stream directly (instead of |copyTo|
- * below), you must signal completion by resolving /
- * rejecting this deferred. If it's rejected, the
- * transport will be closed. If an Error is supplied as a
- * rejection value, it will be logged via |dumpn|. If you
- * do use |copyTo|, resolving is taken care of for you
- * when copying completes.
- * * copyTo: A helper function for getting your data out of the
- * stream that meets the stream handling requirements
- * above, and has the following signature:
- * @param output nsIAsyncOutputStream
- * The stream to copy to.
- * @return Promise
- * The promise is resolved when copying completes or
- * rejected if any (unexpected) errors occur.
- * This object also emits "progress" events for each chunk
- * that is copied. See stream-utils.js.
- */
- startBulkRequest: function (request) {
- if (!this.traits.bulk) {
- throw Error("Server doesn't support bulk transfers");
- }
- if (!this.mainRoot) {
- throw Error("Have not yet received a hello packet from the server.");
- }
- if (!request.type) {
- throw Error("Bulk packet is missing the required 'type' field.");
- }
- if (!request.actor) {
- throw Error("'" + request.type + "' bulk packet has no destination.");
- }
- if (!request.length) {
- throw Error("'" + request.type + "' bulk packet has no length.");
- }
-
- request = new Request(request);
- request.format = "bulk";
-
- this._sendOrQueueRequest(request);
-
- return request;
- },
-
- /**
- * If a new request can be sent immediately, do so. Otherwise, queue it.
- */
- _sendOrQueueRequest(request) {
- let actor = request.actor;
- if (!this._activeRequests.has(actor)) {
- this._sendRequest(request);
- } else {
- this._queueRequest(request);
- }
- },
-
- /**
- * Send a request.
- * @throws Error if there is already an active request in flight for the same
- * actor.
- */
- _sendRequest(request) {
- let actor = request.actor;
- this.expectReply(actor, request);
-
- if (request.format === "json") {
- this._transport.send(request.request);
- return;
- }
-
- this._transport.startBulkSend(request.request).then((...args) => {
- request.emit("bulk-send-ready", ...args);
- });
- },
-
- /**
- * Queue a request to be sent later. Queues are only drained when an in
- * flight request to a given actor completes.
- */
- _queueRequest(request) {
- let actor = request.actor;
- let queue = this._pendingRequests.get(actor) || [];
- queue.push(request);
- this._pendingRequests.set(actor, queue);
- },
-
- /**
- * Attempt the next request to a given actor (if any).
- */
- _attemptNextRequest(actor) {
- if (this._activeRequests.has(actor)) {
- return;
- }
- let queue = this._pendingRequests.get(actor);
- if (!queue) {
- return;
- }
- let request = queue.shift();
- if (queue.length === 0) {
- this._pendingRequests.delete(actor);
- }
- this._sendRequest(request);
- },
-
- /**
- * Arrange to hand the next reply from |actor| to the handler bound to
- * |request|.
- *
- * DebuggerClient.prototype.request / startBulkRequest usually takes care of
- * establishing the handler for a given request, but in rare cases (well,
- * greetings from new root actors, is the only case at the moment) we must be
- * prepared for a "reply" that doesn't correspond to any request we sent.
- */
- expectReply: function (actor, request) {
- if (this._activeRequests.has(actor)) {
- throw Error("clashing handlers for next reply from " + actor);
- }
-
- // If a handler is passed directly (as it is with the handler for the root
- // actor greeting), create a dummy request to bind this to.
- if (typeof request === "function") {
- let handler = request;
- request = new Request();
- request.on("json-reply", handler);
- }
-
- this._activeRequests.set(actor, request);
- },
-
- // Transport hooks.
-
- /**
- * Called by DebuggerTransport to dispatch incoming packets as appropriate.
- *
- * @param packet object
- * The incoming packet.
- */
- onPacket: function (packet) {
- if (!packet.from) {
- DevToolsUtils.reportException(
- "onPacket",
- new Error("Server did not specify an actor, dropping packet: " +
- JSON.stringify(packet)));
- return;
- }
-
- // If we have a registered Front for this actor, let it handle the packet
- // and skip all the rest of this unpleasantness.
- let front = this.getActor(packet.from);
- if (front) {
- front.onPacket(packet);
- return;
- }
-
- // Check for "forwardingCancelled" here instead of using a client to handle it.
- // This is necessary because we might receive this event while the client is closing,
- // and the clients have already been removed by that point.
- if (this.mainRoot &&
- packet.from == this.mainRoot.actor &&
- packet.type == "forwardingCancelled") {
- this.purgeRequests(packet.prefix);
- return;
- }
-
- if (this._clients.has(packet.from) && packet.type) {
- let client = this._clients.get(packet.from);
- let type = packet.type;
- if (client.events.indexOf(type) != -1) {
- client.emit(type, packet);
- // we ignore the rest, as the client is expected to handle this packet.
- return;
- }
- }
-
- let activeRequest;
- // See if we have a handler function waiting for a reply from this
- // actor. (Don't count unsolicited notifications or pauses as
- // replies.)
- if (this._activeRequests.has(packet.from) &&
- !(packet.type in UnsolicitedNotifications) &&
- !(packet.type == ThreadStateTypes.paused &&
- packet.why.type in UnsolicitedPauses)) {
- activeRequest = this._activeRequests.get(packet.from);
- this._activeRequests.delete(packet.from);
- }
-
- // If there is a subsequent request for the same actor, hand it off to the
- // transport. Delivery of packets on the other end is always async, even
- // in the local transport case.
- this._attemptNextRequest(packet.from);
-
- // Packets that indicate thread state changes get special treatment.
- if (packet.type in ThreadStateTypes &&
- this._clients.has(packet.from) &&
- typeof this._clients.get(packet.from)._onThreadState == "function") {
- this._clients.get(packet.from)._onThreadState(packet);
- }
-
- // TODO: Bug 1151156 - Remove once Gecko 40 is on b2g-stable.
- if (!this.traits.noNeedToFakeResumptionOnNavigation) {
- // On navigation the server resumes, so the client must resume as well.
- // We achieve that by generating a fake resumption packet that triggers
- // the client's thread state change listeners.
- if (packet.type == UnsolicitedNotifications.tabNavigated &&
- this._clients.has(packet.from) &&
- this._clients.get(packet.from).thread) {
- let thread = this._clients.get(packet.from).thread;
- let resumption = { from: thread._actor, type: "resumed" };
- thread._onThreadState(resumption);
- }
- }
-
- // Only try to notify listeners on events, not responses to requests
- // that lack a packet type.
- if (packet.type) {
- this.emit(packet.type, packet);
- }
-
- if (activeRequest) {
- let emitReply = () => activeRequest.emit("json-reply", packet);
- if (activeRequest.stack) {
- callFunctionWithAsyncStack(emitReply, activeRequest.stack,
- "DevTools RDP");
- } else {
- emitReply();
- }
- }
- },
-
- /**
- * Called by the DebuggerTransport to dispatch incoming bulk packets as
- * appropriate.
- *
- * @param packet object
- * The incoming packet, which contains:
- * * actor: Name of actor that will receive the packet
- * * type: Name of actor's method that should be called on receipt
- * * length: Size of the data to be read
- * * stream: This input stream should only be used directly if you can
- * ensure that you will read exactly |length| bytes and will
- * not close the stream when reading is complete
- * * done: If you use the stream directly (instead of |copyTo|
- * below), you must signal completion by resolving /
- * rejecting this deferred. If it's rejected, the transport
- * will be closed. If an Error is supplied as a rejection
- * value, it will be logged via |dumpn|. If you do use
- * |copyTo|, resolving is taken care of for you when copying
- * completes.
- * * copyTo: A helper function for getting your data out of the stream
- * that meets the stream handling requirements above, and has
- * the following signature:
- * @param output nsIAsyncOutputStream
- * The stream to copy to.
- * @return Promise
- * The promise is resolved when copying completes or rejected
- * if any (unexpected) errors occur.
- * This object also emits "progress" events for each chunk
- * that is copied. See stream-utils.js.
- */
- onBulkPacket: function (packet) {
- let { actor } = packet;
-
- if (!actor) {
- DevToolsUtils.reportException(
- "onBulkPacket",
- new Error("Server did not specify an actor, dropping bulk packet: " +
- JSON.stringify(packet)));
- return;
- }
-
- // See if we have a handler function waiting for a reply from this
- // actor.
- if (!this._activeRequests.has(actor)) {
- return;
- }
-
- let activeRequest = this._activeRequests.get(actor);
- this._activeRequests.delete(actor);
-
- // If there is a subsequent request for the same actor, hand it off to the
- // transport. Delivery of packets on the other end is always async, even
- // in the local transport case.
- this._attemptNextRequest(actor);
-
- activeRequest.emit("bulk-reply", packet);
- },
-
- /**
- * Called by DebuggerTransport when the underlying stream is closed.
- *
- * @param status nsresult
- * The status code that corresponds to the reason for closing
- * the stream.
- */
- onClosed: function () {
- this._closed = true;
- this.emit("closed");
-
- this.purgeRequests();
-
- // The |_pools| array on the client-side currently is used only by
- // protocol.js to store active fronts, mirroring the actor pools found in
- // the server. So, read all usages of "pool" as "protocol.js front".
- //
- // In the normal case where we shutdown cleanly, the toolbox tells each tool
- // to close, and they each call |destroy| on any fronts they were using.
- // When |destroy| or |cleanup| is called on a protocol.js front, it also
- // removes itself from the |_pools| array. Once the toolbox has shutdown,
- // the connection is closed, and we reach here. All fronts (should have
- // been) |destroy|ed, so |_pools| should empty.
- //
- // If the connection instead aborts unexpectedly, we may end up here with
- // all fronts used during the life of the connection. So, we call |cleanup|
- // on them clear their state, reject pending requests, and remove themselves
- // from |_pools|. This saves the toolbox from hanging indefinitely, in case
- // it waits for some server response before shutdown that will now never
- // arrive.
- for (let pool of this._pools) {
- pool.cleanup();
- }
- },
-
- /**
- * Purge pending and active requests in this client.
- *
- * @param prefix string (optional)
- * If a prefix is given, only requests for actor IDs that start with the prefix
- * will be cleaned up. This is useful when forwarding of a portion of requests
- * is cancelled on the server.
- */
- purgeRequests(prefix = "") {
- let reject = function (type, request) {
- // Server can send packets on its own and client only pass a callback
- // to expectReply, so that there is no request object.
- let msg;
- if (request.request) {
- msg = "'" + request.request.type + "' " + type + " request packet" +
- " to '" + request.actor + "' " +
- "can't be sent as the connection just closed.";
- } else {
- msg = "server side packet can't be received as the connection just closed.";
- }
- let packet = { error: "connectionClosed", message: msg };
- request.emit("json-reply", packet);
- };
-
- let pendingRequestsToReject = [];
- this._pendingRequests.forEach((requests, actor) => {
- if (!actor.startsWith(prefix)) {
- return;
- }
- this._pendingRequests.delete(actor);
- pendingRequestsToReject = pendingRequestsToReject.concat(requests);
- });
- pendingRequestsToReject.forEach(request => reject("pending", request));
-
- let activeRequestsToReject = [];
- this._activeRequests.forEach((request, actor) => {
- if (!actor.startsWith(prefix)) {
- return;
- }
- this._activeRequests.delete(actor);
- activeRequestsToReject = activeRequestsToReject.concat(request);
- });
- activeRequestsToReject.forEach(request => reject("active", request));
- },
-
- /**
- * Search for all requests in process for this client, including those made via
- * protocol.js and wait all of them to complete. Since the requests seen when this is
- * first called may in turn trigger more requests, we keep recursing through this
- * function until there is no more activity.
- *
- * This is a fairly heavy weight process, so it's only meant to be used in tests.
- *
- * @return Promise
- * Resolved when all requests have settled.
- */
- waitForRequestsToSettle() {
- let requests = [];
-
- // Gather all pending and active requests in this client
- // The request object supports a Promise API for completion (it has .then())
- this._pendingRequests.forEach(requestsForActor => {
- // Each value is an array of pending requests
- requests = requests.concat(requestsForActor);
- });
- this._activeRequests.forEach(requestForActor => {
- // Each value is a single active request
- requests = requests.concat(requestForActor);
- });
-
- // protocol.js
- // Use a Set because some fronts (like domwalker) seem to have multiple parents.
- let fronts = new Set();
- let poolsToVisit = [...this._pools];
-
- // With protocol.js, each front can potentially have it's own pools containing child
- // fronts, forming a tree. Descend through all the pools to locate all child fronts.
- while (poolsToVisit.length) {
- let pool = poolsToVisit.shift();
- fronts.add(pool);
- for (let child of pool.poolChildren()) {
- poolsToVisit.push(child);
- }
- }
-
- // For each front, wait for its requests to settle
- for (let front of fronts) {
- if (front.hasRequests()) {
- requests.push(front.waitForRequestsToSettle());
- }
- }
-
- // Abort early if there are no requests
- if (!requests.length) {
- return Promise.resolve();
- }
-
- return DevToolsUtils.settleAll(requests).catch(() => {
- // One of the requests might have failed, but ignore that situation here and pipe
- // both success and failure through the same path. The important part is just that
- // we waited.
- }).then(() => {
- // Repeat, more requests may have started in response to those we just waited for
- return this.waitForRequestsToSettle();
- });
- },
-
- registerClient: function (client) {
- let actorID = client.actor;
- if (!actorID) {
- throw new Error("DebuggerServer.registerClient expects " +
- "a client instance with an `actor` attribute.");
- }
- if (!Array.isArray(client.events)) {
- throw new Error("DebuggerServer.registerClient expects " +
- "a client instance with an `events` attribute " +
- "that is an array.");
- }
- if (client.events.length > 0 && typeof (client.emit) != "function") {
- throw new Error("DebuggerServer.registerClient expects " +
- "a client instance with non-empty `events` array to" +
- "have an `emit` function.");
- }
- if (this._clients.has(actorID)) {
- throw new Error("DebuggerServer.registerClient already registered " +
- "a client for this actor.");
- }
- this._clients.set(actorID, client);
- },
-
- unregisterClient: function (client) {
- let actorID = client.actor;
- if (!actorID) {
- throw new Error("DebuggerServer.unregisterClient expects " +
- "a Client instance with a `actor` attribute.");
- }
- this._clients.delete(actorID);
- },
-
- /**
- * Actor lifetime management, echos the server's actor pools.
- */
- __pools: null,
- get _pools() {
- if (this.__pools) {
- return this.__pools;
- }
- this.__pools = new Set();
- return this.__pools;
- },
-
- addActorPool: function (pool) {
- this._pools.add(pool);
- },
- removeActorPool: function (pool) {
- this._pools.delete(pool);
- },
- getActor: function (actorID) {
- let pool = this.poolFor(actorID);
- return pool ? pool.get(actorID) : null;
- },
-
- poolFor: function (actorID) {
- for (let pool of this._pools) {
- if (pool.has(actorID)) {
- return pool;
- }
- }
- return null;
- },
-
- /**
- * Currently attached addon.
- */
- activeAddon: null
-};
-
-eventSource(DebuggerClient.prototype);
-
-class Request extends EventEmitter {
- constructor(request) {
- super();
- this.request = request;
- }
-
- get actor() {
- return this.request.to || this.request.actor;
- }
-}
-
-/**
- * Creates a tab client for the remote debugging protocol server. This client
- * is a front to the tab actor created in the server side, hiding the protocol
- * details in a traditional JavaScript API.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param form object
- * The protocol form for this tab.
- */
-function TabClient(client, form) {
- this.client = client;
- this._actor = form.from;
- this._threadActor = form.threadActor;
- this.javascriptEnabled = form.javascriptEnabled;
- this.cacheDisabled = form.cacheDisabled;
- this.thread = null;
- this.request = this.client.request;
- this.traits = form.traits || {};
- this.events = ["workerListChanged"];
-}
-
-TabClient.prototype = {
- get actor() {
- return this._actor;
- },
- get _transport() {
- return this.client._transport;
- },
-
- /**
- * Attach to a thread actor.
- *
- * @param object options
- * Configuration options.
- * - useSourceMaps: whether to use source maps or not.
- * @param function onResponse
- * Called with the response packet and a ThreadClient
- * (which will be undefined on error).
- */
- attachThread: function (options = {}, onResponse = noop) {
- if (this.thread) {
- DevToolsUtils.executeSoon(() => onResponse({}, this.thread));
- return promise.resolve([{}, this.thread]);
- }
-
- let packet = {
- to: this._threadActor,
- type: "attach",
- options,
- };
- return this.request(packet).then(response => {
- if (!response.error) {
- this.thread = new ThreadClient(this, this._threadActor);
- this.client.registerClient(this.thread);
- }
- onResponse(response, this.thread);
- return [response, this.thread];
- });
- },
-
- /**
- * Detach the client from the tab actor.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- detach: DebuggerClient.requester({
- type: "detach"
- }, {
- before: function (packet) {
- if (this.thread) {
- this.thread.detach();
- }
- return packet;
- },
- after: function (response) {
- this.client.unregisterClient(this);
- return response;
- },
- }),
-
- /**
- * Bring the window to the front.
- */
- focus: DebuggerClient.requester({
- type: "focus"
- }, {}),
-
- /**
- * Reload the page in this tab.
- *
- * @param [optional] object options
- * An object with a `force` property indicating whether or not
- * this reload should skip the cache
- */
- reload: function (options = { force: false }) {
- return this._reload(options);
- },
- _reload: DebuggerClient.requester({
- type: "reload",
- options: arg(0)
- }),
-
- /**
- * Navigate to another URL.
- *
- * @param string url
- * The URL to navigate to.
- */
- navigateTo: DebuggerClient.requester({
- type: "navigateTo",
- url: arg(0)
- }),
-
- /**
- * Reconfigure the tab actor.
- *
- * @param object options
- * A dictionary object of the new options to use in the tab actor.
- * @param function onResponse
- * Called with the response packet.
- */
- reconfigure: DebuggerClient.requester({
- type: "reconfigure",
- options: arg(0)
- }),
-
- listWorkers: DebuggerClient.requester({
- type: "listWorkers"
- }),
-
- attachWorker: function (workerActor, onResponse) {
- return this.client.attachWorker(workerActor, onResponse);
- },
-};
-
-eventSource(TabClient.prototype);
-
function WorkerClient(client, form) {
this.client = client;
this._actor = form.from;
this._isClosed = false;
this._url = form.url;
this._onClose = this._onClose.bind(this);
@@ -1561,1866 +112,9 @@ WorkerClient.prototype = {
return Promise.resolve();
},
events: ["close"]
};
eventSource(WorkerClient.prototype);
-function AddonClient(client, actor) {
- this._client = client;
- this._actor = actor;
- this.request = this._client.request;
- this.events = [];
-}
-
-AddonClient.prototype = {
- get actor() {
- return this._actor;
- },
- get _transport() {
- return this._client._transport;
- },
-
- /**
- * Detach the client from the addon actor.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- detach: DebuggerClient.requester({
- type: "detach"
- }, {
- after: function (response) {
- if (this._client.activeAddon === this) {
- this._client.activeAddon = null;
- }
- this._client.unregisterClient(this);
- return response;
- },
- })
-};
-
-/**
- * A RootClient object represents a root actor on the server. Each
- * DebuggerClient keeps a RootClient instance representing the root actor
- * for the initial connection; DebuggerClient's 'listTabs' and
- * 'listChildProcesses' methods forward to that root actor.
- *
- * @param client object
- * The client connection to which this actor belongs.
- * @param greeting string
- * The greeting packet from the root actor we're to represent.
- *
- * Properties of a RootClient instance:
- *
- * @property actor string
- * The name of this child's root actor.
- * @property applicationType string
- * The application type, as given in the root actor's greeting packet.
- * @property traits object
- * The traits object, as given in the root actor's greeting packet.
- */
-function RootClient(client, greeting) {
- this._client = client;
- this.actor = greeting.from;
- this.applicationType = greeting.applicationType;
- this.traits = greeting.traits;
-}
-exports.RootClient = RootClient;
-
-RootClient.prototype = {
- constructor: RootClient,
-
- /**
- * Gets the "root" form, which lists all the global actors that affect the entire
- * browser. This can replace usages of `listTabs` that only wanted the global actors
- * and didn't actually care about tabs.
- */
- getRoot: DebuggerClient.requester({ type: "getRoot" }),
-
- /**
- * List the open tabs.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- listTabs: DebuggerClient.requester({ type: "listTabs" }),
-
- /**
- * List the installed addons.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- listAddons: DebuggerClient.requester({ type: "listAddons" }),
-
- /**
- * List the registered workers.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- listWorkers: DebuggerClient.requester({ type: "listWorkers" }),
-
- /**
- * List the registered service workers.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- listServiceWorkerRegistrations: DebuggerClient.requester({
- type: "listServiceWorkerRegistrations"
- }),
-
- /**
- * List the running processes.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- listProcesses: DebuggerClient.requester({ type: "listProcesses" }),
-
- /**
- * Fetch the TabActor for the currently selected tab, or for a specific
- * tab given as first parameter.
- *
- * @param [optional] object filter
- * A dictionary object with following optional attributes:
- * - outerWindowID: used to match tabs in parent process
- * - tabId: used to match tabs in child processes
- * - tab: a reference to xul:tab element
- * If nothing is specified, returns the actor for the currently
- * selected tab.
- */
- getTab: function (filter) {
- let packet = {
- to: this.actor,
- type: "getTab"
- };
-
- if (filter) {
- if (typeof (filter.outerWindowID) == "number") {
- packet.outerWindowID = filter.outerWindowID;
- } else if (typeof (filter.tabId) == "number") {
- packet.tabId = filter.tabId;
- } else if ("tab" in filter) {
- let browser = filter.tab.linkedBrowser;
- if (browser.frameLoader.tabParent) {
- // Tabs in child process
- packet.tabId = browser.frameLoader.tabParent.tabId;
- } else if (browser.outerWindowID) {
- // <xul:browser> tabs in parent process
- packet.outerWindowID = browser.outerWindowID;
- } else {
- // <iframe mozbrowser> tabs in parent process
- let windowUtils = browser.contentWindow
- .QueryInterface(Ci.nsIInterfaceRequestor)
- .getInterface(Ci.nsIDOMWindowUtils);
- packet.outerWindowID = windowUtils.outerWindowID;
- }
- } else {
- // Throw if a filter object have been passed but without
- // any clearly idenfified filter.
- throw new Error("Unsupported argument given to getTab request");
- }
- }
-
- return this.request(packet);
- },
-
- /**
- * Fetch the WindowActor for a specific window, like a browser window in
- * Firefox, but it can be used to reach any window in the process.
- *
- * @param number outerWindowID
- * The outerWindowID of the top level window you are looking for.
- */
- getWindow: function ({ outerWindowID }) {
- if (!outerWindowID) {
- throw new Error("Must specify outerWindowID");
- }
-
- let packet = {
- to: this.actor,
- type: "getWindow",
- outerWindowID,
- };
-
- return this.request(packet);
- },
-
- /**
- * Description of protocol's actors and methods.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- protocolDescription: DebuggerClient.requester({ type: "protocolDescription" }),
-
- /*
- * Methods constructed by DebuggerClient.requester require these forwards
- * on their 'this'.
- */
- get _transport() {
- return this._client._transport;
- },
- get request() {
- return this._client.request;
- }
-};
-
-/**
- * Creates a thread client for the remote debugging protocol server. This client
- * is a front to the thread actor created in the server side, hiding the
- * protocol details in a traditional JavaScript API.
- *
- * @param client DebuggerClient|TabClient
- * The parent of the thread (tab for tab-scoped debuggers, DebuggerClient
- * for chrome debuggers).
- * @param actor string
- * The actor ID for this thread.
- */
-function ThreadClient(client, actor) {
- this._parent = client;
- this.client = client instanceof DebuggerClient ? client : client.client;
- this._actor = actor;
- this._frameCache = [];
- this._scriptCache = {};
- this._pauseGrips = {};
- this._threadGrips = {};
- this.request = this.client.request;
-}
-
-ThreadClient.prototype = {
- _state: "paused",
- get state() {
- return this._state;
- },
- get paused() {
- return this._state === "paused";
- },
-
- _pauseOnExceptions: false,
- _ignoreCaughtExceptions: false,
- _pauseOnDOMEvents: null,
-
- _actor: null,
- get actor() {
- return this._actor;
- },
-
- get _transport() {
- return this.client._transport;
- },
-
- _assertPaused: function (command) {
- if (!this.paused) {
- throw Error(command + " command sent while not paused. Currently " + this._state);
- }
- },
-
- /**
- * Resume a paused thread. If the optional limit parameter is present, then
- * the thread will also pause when that limit is reached.
- *
- * @param [optional] object limit
- * An object with a type property set to the appropriate limit (next,
- * step, or finish) per the remote debugging protocol specification.
- * Use null to specify no limit.
- * @param function onResponse
- * Called with the response packet.
- */
- _doResume: DebuggerClient.requester({
- type: "resume",
- resumeLimit: arg(0)
- }, {
- before: function (packet) {
- this._assertPaused("resume");
-
- // Put the client in a tentative "resuming" state so we can prevent
- // further requests that should only be sent in the paused state.
- this._previousState = this._state;
- this._state = "resuming";
-
- if (this._pauseOnExceptions) {
- packet.pauseOnExceptions = this._pauseOnExceptions;
- }
- if (this._ignoreCaughtExceptions) {
- packet.ignoreCaughtExceptions = this._ignoreCaughtExceptions;
- }
- if (this._pauseOnDOMEvents) {
- packet.pauseOnDOMEvents = this._pauseOnDOMEvents;
- }
- return packet;
- },
- after: function (response) {
- if (response.error && this._state == "resuming") {
- // There was an error resuming, update the state to the new one
- // reported by the server, if given (only on wrongState), otherwise
- // reset back to the previous state.
- if (response.state) {
- this._state = ThreadStateTypes[response.state];
- } else {
- this._state = this._previousState;
- }
- }
- delete this._previousState;
- return response;
- },
- }),
-
- /**
- * Reconfigure the thread actor.
- *
- * @param object options
- * A dictionary object of the new options to use in the thread actor.
- * @param function onResponse
- * Called with the response packet.
- */
- reconfigure: DebuggerClient.requester({
- type: "reconfigure",
- options: arg(0)
- }),
-
- /**
- * Resume a paused thread.
- */
- resume: function (onResponse) {
- return this._doResume(null, onResponse);
- },
-
- /**
- * Resume then pause without stepping.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- resumeThenPause: function (onResponse) {
- return this._doResume({ type: "break" }, onResponse);
- },
-
- /**
- * Step over a function call.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- stepOver: function (onResponse) {
- return this._doResume({ type: "next" }, onResponse);
- },
-
- /**
- * Step into a function call.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- stepIn: function (onResponse) {
- return this._doResume({ type: "step" }, onResponse);
- },
-
- /**
- * Step out of a function call.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- stepOut: function (onResponse) {
- return this._doResume({ type: "finish" }, onResponse);
- },
-
- /**
- * Immediately interrupt a running thread.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- interrupt: function (onResponse) {
- return this._doInterrupt(null, onResponse);
- },
-
- /**
- * Pause execution right before the next JavaScript bytecode is executed.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- breakOnNext: function (onResponse) {
- return this._doInterrupt("onNext", onResponse);
- },
-
- /**
- * Interrupt a running thread.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- _doInterrupt: DebuggerClient.requester({
- type: "interrupt",
- when: arg(0)
- }),
-
- /**
- * Enable or disable pausing when an exception is thrown.
- *
- * @param boolean pauseOnExceptions
- * Enables pausing if true, disables otherwise.
- * @param boolean ignoreCaughtExceptions
- * Whether to ignore caught exceptions
- * @param function onResponse
- * Called with the response packet.
- */
- pauseOnExceptions: function (pauseOnExceptions,
- ignoreCaughtExceptions,
- onResponse = noop) {
- this._pauseOnExceptions = pauseOnExceptions;
- this._ignoreCaughtExceptions = ignoreCaughtExceptions;
-
- // Otherwise send the flag using a standard resume request.
- if (!this.paused) {
- return this.interrupt(response => {
- if (response.error) {
- // Can't continue if pausing failed.
- onResponse(response);
- return response;
- }
- return this.resume(onResponse);
- });
- }
-
- onResponse();
- return promise.resolve();
- },
-
- /**
- * Enable pausing when the specified DOM events are triggered. Disabling
- * pausing on an event can be realized by calling this method with the updated
- * array of events that doesn't contain it.
- *
- * @param array|string events
- * An array of strings, representing the DOM event types to pause on,
- * or "*" to pause on all DOM events. Pass an empty array to
- * completely disable pausing on DOM events.
- * @param function onResponse
- * Called with the response packet in a future turn of the event loop.
- */
- pauseOnDOMEvents: function (events, onResponse = noop) {
- this._pauseOnDOMEvents = events;
- // If the debuggee is paused, the value of the array will be communicated in
- // the next resumption. Otherwise we have to force a pause in order to send
- // the array.
- if (this.paused) {
- DevToolsUtils.executeSoon(() => onResponse({}));
- return {};
- }
- return this.interrupt(response => {
- // Can't continue if pausing failed.
- if (response.error) {
- onResponse(response);
- return response;
- }
- return this.resume(onResponse);
- });
- },
-
- /**
- * Send a clientEvaluate packet to the debuggee. Response
- * will be a resume packet.
- *
- * @param string frame
- * The actor ID of the frame where the evaluation should take place.
- * @param string expression
- * The expression that will be evaluated in the scope of the frame
- * above.
- * @param function onResponse
- * Called with the response packet.
- */
- eval: DebuggerClient.requester({
- type: "clientEvaluate",
- frame: arg(0),
- expression: arg(1)
- }, {
- before: function (packet) {
- this._assertPaused("eval");
- // Put the client in a tentative "resuming" state so we can prevent
- // further requests that should only be sent in the paused state.
- this._state = "resuming";
- return packet;
- },
- after: function (response) {
- if (response.error) {
- // There was an error resuming, back to paused state.
- this._state = "paused";
- }
- return response;
- },
- }),
-
- /**
- * Detach from the thread actor.
- *
- * @param function onResponse
- * Called with the response packet.
- */
- detach: DebuggerClient.requester({
- type: "detach"
- }, {
- after: function (response) {
- this.client.unregisterClient(this);
- this._parent.thread = null;
- return response;
- },
- }),
-
- /**
- * Release multiple thread-lifetime object actors. If any pause-lifetime
- * actors are included in the request, a |notReleasable| error will return,
- * but all the thread-lifetime ones will have been released.
- *
- * @param array actors
- * An array with actor IDs to release.
- */
- releaseMany: DebuggerClient.requester({
- type: "releaseMany",
- actors: arg(0),
- }),
-
- /**
- * Promote multiple pause-lifetime object actors to thread-lifetime ones.
- *
- * @param array actors
- * An array with actor IDs to promote.
- */
- threadGrips: DebuggerClient.requester({
- type: "threadGrips",
- actors: arg(0)
- }),
-
- /**
- * Return the event listeners defined on the page.
- *
- * @param onResponse Function
- * Called with the thread's response.
- */
- eventListeners: DebuggerClient.requester({
- type: "eventListeners"
- }),
-
- /**
- * Request the loaded sources for the current thread.
- *
- * @param onResponse Function
- * Called with the thread's response.
- */
- getSources: DebuggerClient.requester({
- type: "sources"
- }),
-
- /**
- * Clear the thread's source script cache. A scriptscleared event
- * will be sent.
- */
- _clearScripts: function () {
- if (Object.keys(this._scriptCache).length > 0) {
- this._scriptCache = {};
- this.emit("scriptscleared");
- }
- },
-
- /**
- * Request frames from the callstack for the current thread.
- *
- * @param start integer
- * The number of the youngest stack frame to return (the youngest
- * frame is 0).
- * @param count integer
- * The maximum number of frames to return, or null to return all
- * frames.
- * @param onResponse function
- * Called with the thread's response.
- */
- getFrames: DebuggerClient.requester({
- type: "frames",
- start: arg(0),
- count: arg(1)
- }),
-
- /**
- * An array of cached frames. Clients can observe the framesadded and
- * framescleared event to keep up to date on changes to this cache,
- * and can fill it using the fillFrames method.
- */
- get cachedFrames() {
- return this._frameCache;
- },
-
- /**
- * true if there are more stack frames available on the server.
- */
- get moreFrames() {
- return this.paused && (!this._frameCache || this._frameCache.length == 0
- || !this._frameCache[this._frameCache.length - 1].oldest);
- },
-
- /**
- * Request the frame environment.
- *
- * @param frameId string
- */
- getEnvironment: function (frameId) {
- return this.request({ to: frameId, type: "getEnvironment" });
- },
-
- /**
- * Ensure that at least total stack frames have been loaded in the
- * ThreadClient's stack frame cache. A framesadded event will be
- * sent when the stack frame cache is updated.
- *
- * @param total number
- * The minimum number of stack frames to be included.
- * @param callback function
- * Optional callback function called when frames have been loaded
- * @returns true if a framesadded notification should be expected.
- */
- fillFrames: function (total, callback = noop) {
- this._assertPaused("fillFrames");
- if (this._frameCache.length >= total) {
- return false;
- }
-
- let numFrames = this._frameCache.length;
-
- this.getFrames(numFrames, total - numFrames, (response) => {
- if (response.error) {
- callback(response);
- return;
- }
-
- let threadGrips = DevToolsUtils.values(this._threadGrips);
-
- for (let i in response.frames) {
- let frame = response.frames[i];
- if (!frame.where.source) {
- // Older servers use urls instead, so we need to resolve
- // them to source actors
- for (let grip of threadGrips) {
- if (grip instanceof SourceClient && grip.url === frame.url) {
- frame.where.source = grip._form;
- }
- }
- }
-
- this._frameCache[frame.depth] = frame;
- }
-
- // If we got as many frames as we asked for, there might be more
- // frames available.
- this.emit("framesadded");
-
- callback(response);
- });
-
- return true;
- },
-
- /**
- * Clear the thread's stack frame cache. A framescleared event
- * will be sent.
- */
- _clearFrames: function () {
- if (this._frameCache.length > 0) {
- this._frameCache = [];
- this.emit("framescleared");
- }
- },
-
- /**
- * Return a ObjectClient object for the given object grip.
- *
- * @param grip object
- * A pause-lifetime object grip returned by the protocol.
- */
- pauseGrip: function (grip) {
- if (grip.actor in this._pauseGrips) {
- return this._pauseGrips[grip.actor];
- }
-
- let client = new ObjectClient(this.client, grip);
- this._pauseGrips[grip.actor] = client;
- return client;
- },
-
- /**
- * Get or create a long string client, checking the grip client cache if it
- * already exists.
- *
- * @param grip Object
- * The long string grip returned by the protocol.
- * @param gripCacheName String
- * The property name of the grip client cache to check for existing
- * clients in.
- */
- _longString: function (grip, gripCacheName) {
- if (grip.actor in this[gripCacheName]) {
- return this[gripCacheName][grip.actor];
- }
-
- let client = new LongStringClient(this.client, grip);
- this[gripCacheName][grip.actor] = client;
- return client;
- },
-
- /**
- * Return an instance of LongStringClient for the given long string grip that
- * is scoped to the current pause.
- *
- * @param grip Object
- * The long string grip returned by the protocol.
- */
- pauseLongString: function (grip) {
- return this._longString(grip, "_pauseGrips");
- },
-
- /**
- * Return an instance of LongStringClient for the given long string grip that
- * is scoped to the thread lifetime.
- *
- * @param grip Object
- * The long string grip returned by the protocol.
- */
- threadLongString: function (grip) {
- return this._longString(grip, "_threadGrips");
- },
-
- /**
- * Get or create an ArrayBuffer client, checking the grip client cache if it
- * already exists.
- *
- * @param grip Object
- * The ArrayBuffer grip returned by the protocol.
- * @param gripCacheName String
- * The property name of the grip client cache to check for existing
- * clients in.
- */
- _arrayBuffer: function (grip, gripCacheName) {
- if (grip.actor in this[gripCacheName]) {
- return this[gripCacheName][grip.actor];
- }
-
- let client = new ArrayBufferClient(this.client, grip);
- this[gripCacheName][grip.actor] = client;
- return client;
- },
-
- /**
- * Return an instance of ArrayBufferClient for the given ArrayBuffer grip that
- * is scoped to the thread lifetime.
- *
- * @param grip Object
- * The ArrayBuffer grip returned by the protocol.
- */
- threadArrayBuffer: function (grip) {
- return this._arrayBuffer(grip, "_threadGrips");
- },
-
- /**
- * Clear and invalidate all the grip clients from the given cache.
- *
- * @param gripCacheName
- * The property name of the grip cache we want to clear.
- */
- _clearObjectClients: function (gripCacheName) {
- for (let id in this[gripCacheName]) {
- this[gripCacheName][id].valid = false;
- }
- this[gripCacheName] = {};
- },
-
- /**
- * Invalidate pause-lifetime grip clients and clear the list of current grip
- * clients.
- */
- _clearPauseGrips: function () {
- this._clearObjectClients("_pauseGrips");
- },
-
- /**
- * Invalidate thread-lifetime grip clients and clear the list of current grip
- * clients.
- */
- _clearThreadGrips: function () {
- this._clearObjectClients("_threadGrips");
- },
-
- /**
- * Handle thread state change by doing necessary cleanup and notifying all
- * registered listeners.
- */
- _onThreadState: function (packet) {
- this._state = ThreadStateTypes[packet.type];
- // The debugger UI may not be initialized yet so we want to keep
- // the packet around so it knows what to pause state to display
- // when it's initialized
- this._lastPausePacket = packet.type === "resumed" ? null : packet;
- this._clearFrames();
- this._clearPauseGrips();
- packet.type === ThreadStateTypes.detached && this._clearThreadGrips();
- this.client._eventsEnabled && this.emit(packet.type, packet);
- },
-
- getLastPausePacket: function () {
- return this._lastPausePacket;
- },
-
- /**
- * Return an EnvironmentClient instance for the given environment actor form.
- */
- environment: function (form) {
- return new EnvironmentClient(this.client, form);
- },
-
- /**
- * Return an instance of SourceClient for the given source actor form.
- */
- source: function (form) {
- if (form.actor in this._threadGrips) {
- return this._threadGrips[form.actor];
- }
-
- this._threadGrips[form.actor] = new SourceClient(this, form);
- return this._threadGrips[form.actor];
- },
-
- /**
- * Request the prototype and own properties of mutlipleObjects.
- *
- * @param onResponse function
- * Called with the request's response.
- * @param actors [string]
- * List of actor ID of the queried objects.
- */
- getPrototypesAndProperties: DebuggerClient.requester({
- type: "prototypesAndProperties",
- actors: arg(0)
- }),
-
- events: ["newSource"]
-};
-
-eventSource(ThreadClient.prototype);
-
-/**
- * Creates a tracing profiler client for the remote debugging protocol
- * server. This client is a front to the trace actor created on the
- * server side, hiding the protocol details in a traditional
- * JavaScript API.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param actor string
- * The actor ID for this thread.
- */
-function TraceClient(client, actor) {
- this._client = client;
- this._actor = actor;
- this._activeTraces = new Set();
- this._waitingPackets = new Map();
- this._expectedPacket = 0;
- this.request = this._client.request;
- this.events = [];
-}
-
-TraceClient.prototype = {
- get actor() {
- return this._actor;
- },
- get tracing() {
- return this._activeTraces.size > 0;
- },
-
- get _transport() {
- return this._client._transport;
- },
-
- /**
- * Detach from the trace actor.
- */
- detach: DebuggerClient.requester({
- type: "detach"
- }, {
- after: function (response) {
- this._client.unregisterClient(this);
- return response;
- },
- }),
-
- /**
- * Start a new trace.
- *
- * @param trace [string]
- * An array of trace types to be recorded by the new trace.
- *
- * @param name string
- * The name of the new trace.
- *
- * @param onResponse function
- * Called with the request's response.
- */
- startTrace: DebuggerClient.requester({
- type: "startTrace",
- name: arg(1),
- trace: arg(0)
- }, {
- after: function (response) {
- if (response.error) {
- return response;
- }
-
- if (!this.tracing) {
- this._waitingPackets.clear();
- this._expectedPacket = 0;
- }
- this._activeTraces.add(response.name);
-
- return response;
- },
- }),
-
- /**
- * End a trace. If a name is provided, stop the named
- * trace. Otherwise, stop the most recently started trace.
- *
- * @param name string
- * The name of the trace to stop.
- *
- * @param onResponse function
- * Called with the request's response.
- */
- stopTrace: DebuggerClient.requester({
- type: "stopTrace",
- name: arg(0)
- }, {
- after: function (response) {
- if (response.error) {
- return response;
- }
-
- this._activeTraces.delete(response.name);
-
- return response;
- },
- })
-};
-
-/**
- * Grip clients are used to retrieve information about the relevant object.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param grip object
- * A pause-lifetime object grip returned by the protocol.
- */
-function ObjectClient(client, grip) {
- this._grip = grip;
- this._client = client;
- this.request = this._client.request;
-}
-exports.ObjectClient = ObjectClient;
-
-ObjectClient.prototype = {
- get actor() {
- return this._grip.actor;
- },
- get _transport() {
- return this._client._transport;
- },
-
- valid: true,
-
- get isFrozen() {
- return this._grip.frozen;
- },
- get isSealed() {
- return this._grip.sealed;
- },
- get isExtensible() {
- return this._grip.extensible;
- },
-
- getDefinitionSite: DebuggerClient.requester({
- type: "definitionSite"
- }, {
- before: function (packet) {
- if (this._grip.class != "Function") {
- throw new Error("getDefinitionSite is only valid for function grips.");
- }
- return packet;
- }
- }),
-
- /**
- * Request the names of a function's formal parameters.
- *
- * @param onResponse function
- * Called with an object of the form:
- * { parameterNames:[<parameterName>, ...] }
- * where each <parameterName> is the name of a parameter.
- */
- getParameterNames: DebuggerClient.requester({
- type: "parameterNames"
- }, {
- before: function (packet) {
- if (this._grip.class !== "Function") {
- throw new Error("getParameterNames is only valid for function grips.");
- }
- return packet;
- },
- }),
-
- /**
- * Request the names of the properties defined on the object and not its
- * prototype.
- *
- * @param onResponse function Called with the request's response.
- */
- getOwnPropertyNames: DebuggerClient.requester({
- type: "ownPropertyNames"
- }),
-
- /**
- * Request the prototype and own properties of the object.
- *
- * @param onResponse function Called with the request's response.
- */
- getPrototypeAndProperties: DebuggerClient.requester({
- type: "prototypeAndProperties"
- }),
-
- /**
- * Request a PropertyIteratorClient instance to ease listing
- * properties for this object.
- *
- * @param options Object
- * A dictionary object with various boolean attributes:
- * - ignoreIndexedProperties Boolean
- * If true, filters out Array items.
- * e.g. properties names between `0` and `object.length`.
- * - ignoreNonIndexedProperties Boolean
- * If true, filters out items that aren't array items
- * e.g. properties names that are not a number between `0`
- * and `object.length`.
- * - sort Boolean
- * If true, the iterator will sort the properties by name
- * before dispatching them.
- * @param onResponse function Called with the client instance.
- */
- enumProperties: DebuggerClient.requester({
- type: "enumProperties",
- options: arg(0)
- }, {
- after: function (response) {
- if (response.iterator) {
- return { iterator: new PropertyIteratorClient(this._client, response.iterator) };
- }
- return response;
- },
- }),
-
- /**
- * Request a PropertyIteratorClient instance to enumerate entries in a
- * Map/Set-like object.
- *
- * @param onResponse function Called with the request's response.
- */
- enumEntries: DebuggerClient.requester({
- type: "enumEntries"
- }, {
- before: function (packet) {
- if (!["Map", "WeakMap", "Set", "WeakSet"].includes(this._grip.class)) {
- throw new Error("enumEntries is only valid for Map/Set-like grips.");
- }
- return packet;
- },
- after: function (response) {
- if (response.iterator) {
- return {
- iterator: new PropertyIteratorClient(this._client, response.iterator)
- };
- }
- return response;
- }
- }),
-
- /**
- * Request a SymbolIteratorClient instance to enumerate symbols in an object.
- *
- * @param onResponse function Called with the request's response.
- */
- enumSymbols: DebuggerClient.requester({
- type: "enumSymbols"
- }, {
- before: function (packet) {
- if (this._grip.type !== "object") {
- throw new Error("enumSymbols is only valid for objects grips.");
- }
- return packet;
- },
- after: function (response) {
- if (response.iterator) {
- return {
- iterator: new SymbolIteratorClient(this._client, response.iterator)
- };
- }
- return response;
- }
- }),
-
- /**
- * Request the property descriptor of the object's specified property.
- *
- * @param name string The name of the requested property.
- * @param onResponse function Called with the request's response.
- */
- getProperty: DebuggerClient.requester({
- type: "property",
- name: arg(0)
- }),
-
- /**
- * Request the prototype of the object.
- *
- * @param onResponse function Called with the request's response.
- */
- getPrototype: DebuggerClient.requester({
- type: "prototype"
- }),
-
- /**
- * Request the display string of the object.
- *
- * @param onResponse function Called with the request's response.
- */
- getDisplayString: DebuggerClient.requester({
- type: "displayString"
- }),
-
- /**
- * Request the scope of the object.
- *
- * @param onResponse function Called with the request's response.
- */
- getScope: DebuggerClient.requester({
- type: "scope"
- }, {
- before: function (packet) {
- if (this._grip.class !== "Function") {
- throw new Error("scope is only valid for function grips.");
- }
- return packet;
- },
- }),
-
- /**
- * Request the promises directly depending on the current promise.
- */
- getDependentPromises: DebuggerClient.requester({
- type: "dependentPromises"
- }, {
- before: function (packet) {
- if (this._grip.class !== "Promise") {
- throw new Error("getDependentPromises is only valid for promise " +
- "grips.");
- }
- return packet;
- }
- }),
-
- /**
- * Request the stack to the promise's allocation point.
- */
- getPromiseAllocationStack: DebuggerClient.requester({
- type: "allocationStack"
- }, {
- before: function (packet) {
- if (this._grip.class !== "Promise") {
- throw new Error("getAllocationStack is only valid for promise grips.");
- }
- return packet;
- }
- }),
-
- /**
- * Request the stack to the promise's fulfillment point.
- */
- getPromiseFulfillmentStack: DebuggerClient.requester({
- type: "fulfillmentStack"
- }, {
- before: function (packet) {
- if (this._grip.class !== "Promise") {
- throw new Error("getPromiseFulfillmentStack is only valid for " +
- "promise grips.");
- }
- return packet;
- }
- }),
-
- /**
- * Request the stack to the promise's rejection point.
- */
- getPromiseRejectionStack: DebuggerClient.requester({
- type: "rejectionStack"
- }, {
- before: function (packet) {
- if (this._grip.class !== "Promise") {
- throw new Error("getPromiseRejectionStack is only valid for " +
- "promise grips.");
- }
- return packet;
- }
- })
-};
-
-/**
- * A PropertyIteratorClient provides a way to access to property names and
- * values of an object efficiently, slice by slice.
- * Note that the properties can be sorted in the backend,
- * this is controled while creating the PropertyIteratorClient
- * from ObjectClient.enumProperties.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param grip Object
- * A PropertyIteratorActor grip returned by the protocol via
- * TabActor.enumProperties request.
- */
-function PropertyIteratorClient(client, grip) {
- this._grip = grip;
- this._client = client;
- this.request = this._client.request;
-}
-
-PropertyIteratorClient.prototype = {
- get actor() {
- return this._grip.actor;
- },
-
- /**
- * Get the total number of properties available in the iterator.
- */
- get count() {
- return this._grip.count;
- },
-
- /**
- * Get one or more property names that correspond to the positions in the
- * indexes parameter.
- *
- * @param indexes Array
- * An array of property indexes.
- * @param callback Function
- * The function called when we receive the property names.
- */
- names: DebuggerClient.requester({
- type: "names",
- indexes: arg(0)
- }, {}),
-
- /**
- * Get a set of following property value(s).
- *
- * @param start Number
- * The index of the first property to fetch.
- * @param count Number
- * The number of properties to fetch.
- * @param callback Function
- * The function called when we receive the property values.
- */
- slice: DebuggerClient.requester({
- type: "slice",
- start: arg(0),
- count: arg(1)
- }, {}),
-
- /**
- * Get all the property values.
- *
- * @param callback Function
- * The function called when we receive the property values.
- */
- all: DebuggerClient.requester({
- type: "all"
- }, {}),
-};
-
-/**
- * A SymbolIteratorClient provides a way to access to symbols
- * of an object efficiently, slice by slice.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param grip Object
- * A SymbolIteratorActor grip returned by the protocol via
- * TabActor.enumSymbols request.
- */
-function SymbolIteratorClient(client, grip) {
- this._grip = grip;
- this._client = client;
- this.request = this._client.request;
-}
-
-SymbolIteratorClient.prototype = {
- get actor() {
- return this._grip.actor;
- },
-
- /**
- * Get the total number of symbols available in the iterator.
- */
- get count() {
- return this._grip.count;
- },
-
- /**
- * Get a set of following symbols.
- *
- * @param start Number
- * The index of the first symbol to fetch.
- * @param count Number
- * The number of symbols to fetch.
- * @param callback Function
- * The function called when we receive the symbols.
- */
- slice: DebuggerClient.requester({
- type: "slice",
- start: arg(0),
- count: arg(1)
- }, {}),
-
- /**
- * Get all the symbols.
- *
- * @param callback Function
- * The function called when we receive the symbols.
- */
- all: DebuggerClient.requester({
- type: "all"
- }, {}),
-};
-
-/**
- * A ArrayBufferClient provides a way to access ArrayBuffer from the
- * debugger server.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param grip Object
- * A pause-lifetime ArrayBuffer grip returned by the protocol.
- */
-function ArrayBufferClient(client, grip) {
- this._grip = grip;
- this._client = client;
- this.request = this._client.request;
-}
-ArrayBufferClient.prototype = {
- get actor() {
- return this._grip.actor;
- },
- get length() {
- return this._grip.length;
- },
- get _transport() {
- return this._client._transport;
- },
-
- valid: true,
-
- slice: DebuggerClient.requester({
- type: "slice",
- start: arg(0),
- count: arg(1)
- }),
-};
-
-/**
- * A LongStringClient provides a way to access "very long" strings from the
- * debugger server.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param grip Object
- * A pause-lifetime long string grip returned by the protocol.
- */
-function LongStringClient(client, grip) {
- this._grip = grip;
- this._client = client;
- this.request = this._client.request;
-}
-exports.LongStringClient = LongStringClient;
-
-LongStringClient.prototype = {
- get actor() {
- return this._grip.actor;
- },
- get length() {
- return this._grip.length;
- },
- get initial() {
- return this._grip.initial;
- },
- get _transport() {
- return this._client._transport;
- },
-
- valid: true,
-
- /**
- * Get the substring of this LongString from start to end.
- *
- * @param start Number
- * The starting index.
- * @param end Number
- * The ending index.
- * @param callback Function
- * The function called when we receive the substring.
- */
- substring: DebuggerClient.requester({
- type: "substring",
- start: arg(0),
- end: arg(1)
- }),
-};
-
-/**
- * A SourceClient provides a way to access the source text of a script.
- *
- * @param client ThreadClient
- * The thread client parent.
- * @param form Object
- * The form sent across the remote debugging protocol.
- */
-function SourceClient(client, form) {
- this._form = form;
- this._isBlackBoxed = form.isBlackBoxed;
- this._isPrettyPrinted = form.isPrettyPrinted;
- this._activeThread = client;
- this._client = client.client;
-}
-
-SourceClient.prototype = {
- get _transport() {
- return this._client._transport;
- },
- get isBlackBoxed() {
- return this._isBlackBoxed;
- },
- get isPrettyPrinted() {
- return this._isPrettyPrinted;
- },
- get actor() {
- return this._form.actor;
- },
- get request() {
- return this._client.request;
- },
- get url() {
- return this._form.url;
- },
-
- /**
- * Black box this SourceClient's source.
- *
- * @param callback Function
- * The callback function called when we receive the response from the server.
- */
- blackBox: DebuggerClient.requester({
- type: "blackbox"
- }, {
- after: function (response) {
- if (!response.error) {
- this._isBlackBoxed = true;
- if (this._activeThread) {
- this._activeThread.emit("blackboxchange", this);
- }
- }
- return response;
- }
- }),
-
- /**
- * Un-black box this SourceClient's source.
- *
- * @param callback Function
- * The callback function called when we receive the response from the server.
- */
- unblackBox: DebuggerClient.requester({
- type: "unblackbox"
- }, {
- after: function (response) {
- if (!response.error) {
- this._isBlackBoxed = false;
- if (this._activeThread) {
- this._activeThread.emit("blackboxchange", this);
- }
- }
- return response;
- }
- }),
-
- /**
- * Get Executable Lines from a source
- *
- * @param callback Function
- * The callback function called when we receive the response from the server.
- */
- getExecutableLines: function (cb = noop) {
- let packet = {
- to: this._form.actor,
- type: "getExecutableLines"
- };
-
- return this._client.request(packet).then(res => {
- cb(res.lines);
- return res.lines;
- });
- },
-
- /**
- * Get a long string grip for this SourceClient's source.
- */
- source: function (callback = noop) {
- let packet = {
- to: this._form.actor,
- type: "source"
- };
- return this._client.request(packet).then(response => {
- return this._onSourceResponse(response, callback);
- });
- },
-
- /**
- * Pretty print this source's text.
- */
- prettyPrint: function (indent, callback = noop) {
- const packet = {
- to: this._form.actor,
- type: "prettyPrint",
- indent
- };
- return this._client.request(packet).then(response => {
- if (!response.error) {
- this._isPrettyPrinted = true;
- this._activeThread._clearFrames();
- this._activeThread.emit("prettyprintchange", this);
- }
- return this._onSourceResponse(response, callback);
- });
- },
-
- /**
- * Stop pretty printing this source's text.
- */
- disablePrettyPrint: function (callback = noop) {
- const packet = {
- to: this._form.actor,
- type: "disablePrettyPrint"
- };
- return this._client.request(packet).then(response => {
- if (!response.error) {
- this._isPrettyPrinted = false;
- this._activeThread._clearFrames();
- this._activeThread.emit("prettyprintchange", this);
- }
- return this._onSourceResponse(response, callback);
- });
- },
-
- _onSourceResponse: function (response, callback) {
- if (response.error) {
- callback(response);
- return response;
- }
-
- if (typeof response.source === "string") {
- callback(response);
- return response;
- }
-
- let { contentType, source } = response;
- if (source.type === "arrayBuffer") {
- let arrayBuffer = this._activeThread.threadArrayBuffer(source);
- return arrayBuffer.slice(0, arrayBuffer.length).then(function (resp) {
- if (resp.error) {
- callback(resp);
- return resp;
- }
- // Keeping str as a string, ArrayBuffer/Uint8Array will not survive
- // setIn/mergeIn operations.
- const str = atob(resp.encoded);
- let newResponse = {
- source: {
- binary: str,
- toString: () => "[wasm]",
- },
- contentType,
- };
- callback(newResponse);
- return newResponse;
- });
- }
-
- let longString = this._activeThread.threadLongString(source);
- return longString.substring(0, longString.length).then(function (resp) {
- if (resp.error) {
- callback(resp);
- return resp;
- }
-
- let newResponse = {
- source: resp.substring,
- contentType: contentType
- };
- callback(newResponse);
- return newResponse;
- });
- },
-
- /**
- * Request to set a breakpoint in the specified location.
- *
- * @param object location
- * The location and condition of the breakpoint in
- * the form of { line[, column, condition] }.
- * @param function onResponse
- * Called with the thread's response.
- */
- setBreakpoint: function ({ line, column, condition, noSliding }, onResponse = noop) {
- // A helper function that sets the breakpoint.
- let doSetBreakpoint = callback => {
- let root = this._client.mainRoot;
- let location = {
- line,
- column,
- };
-
- let packet = {
- to: this.actor,
- type: "setBreakpoint",
- location,
- condition,
- noSliding,
- };
-
- // Backwards compatibility: send the breakpoint request to the
- // thread if the server doesn't support Debugger.Source actors.
- if (!root.traits.debuggerSourceActors) {
- packet.to = this._activeThread.actor;
- packet.location.url = this.url;
- }
-
- return this._client.request(packet).then(response => {
- // Ignoring errors, since the user may be setting a breakpoint in a
- // dead script that will reappear on a page reload.
- let bpClient;
- if (response.actor) {
- bpClient = new BreakpointClient(
- this._client,
- this,
- response.actor,
- location,
- root.traits.conditionalBreakpoints ? condition : undefined
- );
- }
- onResponse(response, bpClient);
- if (callback) {
- callback();
- }
- return [response, bpClient];
- });
- };
-
- // If the debuggee is paused, just set the breakpoint.
- if (this._activeThread.paused) {
- return doSetBreakpoint();
- }
- // Otherwise, force a pause in order to set the breakpoint.
- return this._activeThread.interrupt().then(response => {
- if (response.error) {
- // Can't set the breakpoint if pausing failed.
- onResponse(response);
- return response;
- }
-
- const { type, why } = response;
- const cleanUp = type == "paused" && why.type == "interrupted"
- ? () => this._activeThread.resume()
- : noop;
-
- return doSetBreakpoint(cleanUp);
- });
- }
-};
-
-/**
- * Breakpoint clients are used to remove breakpoints that are no longer used.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param sourceClient SourceClient
- * The source where this breakpoint exists
- * @param actor string
- * The actor ID for this breakpoint.
- * @param location object
- * The location of the breakpoint. This is an object with two properties:
- * url and line.
- * @param condition string
- * The conditional expression of the breakpoint
- */
-function BreakpointClient(client, sourceClient, actor, location, condition) {
- this._client = client;
- this._actor = actor;
- this.location = location;
- this.location.actor = sourceClient.actor;
- this.location.url = sourceClient.url;
- this.source = sourceClient;
- this.request = this._client.request;
-
- // The condition property should only exist if it's a truthy value
- if (condition) {
- this.condition = condition;
- }
-}
-
-BreakpointClient.prototype = {
-
- _actor: null,
- get actor() {
- return this._actor;
- },
- get _transport() {
- return this._client._transport;
- },
-
- /**
- * Remove the breakpoint from the server.
- */
- remove: DebuggerClient.requester({
- type: "delete"
- }),
-
- /**
- * Determines if this breakpoint has a condition
- */
- hasCondition: function () {
- let root = this._client.mainRoot;
- // XXX bug 990137: We will remove support for client-side handling of
- // conditional breakpoints
- if (root.traits.conditionalBreakpoints) {
- return "condition" in this;
- }
- return "conditionalExpression" in this;
- },
-
- /**
- * Get the condition of this breakpoint. Currently we have to
- * support locally emulated conditional breakpoints until the
- * debugger servers are updated (see bug 990137). We used a
- * different property when moving it server-side to ensure that we
- * are testing the right code.
- */
- getCondition: function () {
- let root = this._client.mainRoot;
- if (root.traits.conditionalBreakpoints) {
- return this.condition;
- }
- return this.conditionalExpression;
- },
-
- /**
- * Set the condition of this breakpoint
- */
- setCondition: function (gThreadClient, condition) {
- let root = this._client.mainRoot;
- let deferred = promise.defer();
-
- if (root.traits.conditionalBreakpoints) {
- let info = {
- line: this.location.line,
- column: this.location.column,
- condition: condition
- };
-
- // Remove the current breakpoint and add a new one with the
- // condition.
- this.remove(response => {
- if (response && response.error) {
- deferred.reject(response);
- return;
- }
-
- this.source.setBreakpoint(info, (resp, newBreakpoint) => {
- if (resp && resp.error) {
- deferred.reject(resp);
- } else {
- deferred.resolve(newBreakpoint);
- }
- });
- });
- } else {
- // The property shouldn't even exist if the condition is blank
- if (condition === "") {
- delete this.conditionalExpression;
- } else {
- this.conditionalExpression = condition;
- }
- deferred.resolve(this);
- }
-
- return deferred.promise;
- }
-};
-
-eventSource(BreakpointClient.prototype);
-
-/**
- * Environment clients are used to manipulate the lexical environment actors.
- *
- * @param client DebuggerClient
- * The debugger client parent.
- * @param form Object
- * The form sent across the remote debugging protocol.
- */
-function EnvironmentClient(client, form) {
- this._client = client;
- this._form = form;
- this.request = this._client.request;
-}
-exports.EnvironmentClient = EnvironmentClient;
-
-EnvironmentClient.prototype = {
-
- get actor() {
- return this._form.actor;
- },
- get _transport() {
- return this._client._transport;
- },
-
- /**
- * Fetches the bindings introduced by this lexical environment.
- */
- getBindings: DebuggerClient.requester({
- type: "bindings"
- }),
-
- /**
- * Changes the value of the identifier whose name is name (a string) to that
- * represented by value (a grip).
- */
- assign: DebuggerClient.requester({
- type: "assign",
- name: arg(0),
- value: arg(1)
- })
-};
-
-eventSource(EnvironmentClient.prototype);
+module.exports = WorkerClient;