toolkit/devtools/server/actors/root.js

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/toolkit/devtools/server/actors/root.js	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,340 @@
     1.4 +/* -*- tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
     1.5 +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
     1.6 +/* This Source Code Form is subject to the terms of the Mozilla Public
     1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this
     1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.9 +
    1.10 +"use strict";
    1.11 +
    1.12 +let devtools_ = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools;
    1.13 +let { createExtraActors, appendExtraActors } = devtools_.require("devtools/server/actors/common");
    1.14 +
    1.15 +/* Root actor for the remote debugging protocol. */
    1.16 +
    1.17 +/**
    1.18 + * Create a remote debugging protocol root actor.
    1.19 + *
    1.20 + * @param aConnection
    1.21 + *     The DebuggerServerConnection whose root actor we are constructing.
    1.22 + *
    1.23 + * @param aParameters
    1.24 + *     The properties of |aParameters| provide backing objects for the root
    1.25 + *     actor's requests; if a given property is omitted from |aParameters|, the
    1.26 + *     root actor won't implement the corresponding requests or notifications.
    1.27 + *     Supported properties:
    1.28 + *
    1.29 + *     - tabList: a live list (see below) of tab actors. If present, the
    1.30 + *       new root actor supports the 'listTabs' request, providing the live
    1.31 + *       list's elements as its tab actors, and sending 'tabListChanged'
    1.32 + *       notifications when the live list's contents change. One actor in
    1.33 + *       this list must have a true '.selected' property.
    1.34 + *
    1.35 + *     - addonList: a live list (see below) of addon actors. If present, the
    1.36 + *       new root actor supports the 'listAddons' request, providing the live
    1.37 + *       list's elements as its addon actors, and sending 'addonListchanged'
    1.38 + *       notifications when the live list's contents change.
    1.39 + *
    1.40 + *     - globalActorFactories: an object |A| describing further actors to
    1.41 + *       attach to the 'listTabs' reply. This is the type accumulated by
    1.42 + *       DebuggerServer.addGlobalActor. For each own property |P| of |A|,
    1.43 + *       the root actor adds a property named |P| to the 'listTabs'
    1.44 + *       reply whose value is the name of an actor constructed by
    1.45 + *       |A[P]|.
    1.46 + *
    1.47 + *     - onShutdown: a function to call when the root actor is disconnected.
    1.48 + *
    1.49 + * Instance properties:
    1.50 + *
    1.51 + * - applicationType: the string the root actor will include as the
    1.52 + *      "applicationType" property in the greeting packet. By default, this
    1.53 + *      is "browser".
    1.54 + *
    1.55 + * Live lists:
    1.56 + *
    1.57 + * A "live list", as used for the |tabList|, is an object that presents a
    1.58 + * list of actors, and also notifies its clients of changes to the list. A
    1.59 + * live list's interface is two properties:
    1.60 + *
    1.61 + * - getList: a method that returns a promise to the contents of the list.
    1.62 + *
    1.63 + * - onListChanged: a handler called, with no arguments, when the set of
    1.64 + *             values the iterator would produce has changed since the last
    1.65 + *             time 'iterator' was called. This may only be set to null or a
    1.66 + *             callable value (one for which the typeof operator returns
    1.67 + *             'function'). (Note that the live list will not call the
    1.68 + *             onListChanged handler until the list has been iterated over
    1.69 + *             once; if nobody's seen the list in the first place, nobody
    1.70 + *             should care if its contents have changed!)
    1.71 + *
    1.72 + * When the list changes, the list implementation should ensure that any
    1.73 + * actors yielded in previous iterations whose referents (tabs) still exist
    1.74 + * get yielded again in subsequent iterations. If the underlying referent
    1.75 + * is the same, the same actor should be presented for it.
    1.76 + *
    1.77 + * The root actor registers an 'onListChanged' handler on the appropriate
    1.78 + * list when it may need to send the client 'tabListChanged' notifications,
    1.79 + * and is careful to remove the handler whenever it does not need to send
    1.80 + * such notifications (including when it is disconnected). This means that
    1.81 + * live list implementations can use the state of the handler property (set
    1.82 + * or null) to install and remove observers and event listeners.
    1.83 + *
    1.84 + * Note that, as the only way for the root actor to see the members of the
    1.85 + * live list is to begin an iteration over the list, the live list need not
    1.86 + * actually produce any actors until they are reached in the course of
    1.87 + * iteration: alliterative lazy live lists.
    1.88 + */
    1.89 +function RootActor(aConnection, aParameters) {
    1.90 +  this.conn = aConnection;
    1.91 +  this._parameters = aParameters;
    1.92 +  this._onTabListChanged = this.onTabListChanged.bind(this);
    1.93 +  this._onAddonListChanged = this.onAddonListChanged.bind(this);
    1.94 +  this._extraActors = {};
    1.95 +}
    1.96 +
    1.97 +RootActor.prototype = {
    1.98 +  constructor: RootActor,
    1.99 +  applicationType: "browser",
   1.100 +
   1.101 +  /**
   1.102 +   * Return a 'hello' packet as specified by the Remote Debugging Protocol.
   1.103 +   */
   1.104 +  sayHello: function() {
   1.105 +    return {
   1.106 +      from: this.actorID,
   1.107 +      applicationType: this.applicationType,
   1.108 +      /* This is not in the spec, but it's used by tests. */
   1.109 +      testConnectionPrefix: this.conn.prefix,
   1.110 +      traits: {
   1.111 +        sources: true,
   1.112 +        editOuterHTML: true,
   1.113 +        // Wether the server-side highlighter actor exists and can be used to
   1.114 +        // remotely highlight nodes (see server/actors/highlighter.js)
   1.115 +        highlightable: true,
   1.116 +        // Wether the inspector actor implements the getImageDataFromURL
   1.117 +        // method that returns data-uris for image URLs. This is used for image
   1.118 +        // tooltips for instance
   1.119 +        urlToImageDataResolver: true,
   1.120 +        networkMonitor: true,
   1.121 +        // Wether the storage inspector actor to inspect cookies, etc.
   1.122 +        storageInspector: true,
   1.123 +        // Wether storage inspector is read only
   1.124 +        storageInspectorReadOnly: true,
   1.125 +        // Wether conditional breakpoints are supported
   1.126 +        conditionalBreakpoints: true
   1.127 +      }
   1.128 +    };
   1.129 +  },
   1.130 +
   1.131 +  /**
   1.132 +   * This is true for the root actor only, used by some child actors
   1.133 +   */
   1.134 +  get isRootActor() true,
   1.135 +
   1.136 +  /**
   1.137 +   * The (chrome) window, for use by child actors
   1.138 +   */
   1.139 +  get window() Services.wm.getMostRecentWindow(DebuggerServer.chromeWindowType),
   1.140 +
   1.141 +  /**
   1.142 +   * URL of the chrome window.
   1.143 +   */
   1.144 +  get url() { return this.window ? this.window.document.location.href : null; },
   1.145 +
   1.146 +  /**
   1.147 +   * Getter for the best nsIWebProgress for to watching this window.
   1.148 +   */
   1.149 +  get webProgress() {
   1.150 +    return this.window
   1.151 +      .QueryInterface(Ci.nsIInterfaceRequestor)
   1.152 +      .getInterface(Ci.nsIDocShell)
   1.153 +      .QueryInterface(Ci.nsIInterfaceRequestor)
   1.154 +      .getInterface(Ci.nsIWebProgress);
   1.155 +  },
   1.156 +
   1.157 +  /**
   1.158 +   * Disconnects the actor from the browser window.
   1.159 +   */
   1.160 +  disconnect: function() {
   1.161 +    /* Tell the live lists we aren't watching any more. */
   1.162 +    if (this._parameters.tabList) {
   1.163 +      this._parameters.tabList.onListChanged = null;
   1.164 +    }
   1.165 +    if (this._parameters.addonList) {
   1.166 +      this._parameters.addonList.onListChanged = null;
   1.167 +    }
   1.168 +    if (typeof this._parameters.onShutdown === 'function') {
   1.169 +      this._parameters.onShutdown();
   1.170 +    }
   1.171 +    this._extraActors = null;
   1.172 +  },
   1.173 +
   1.174 +  /* The 'listTabs' request and the 'tabListChanged' notification. */
   1.175 +
   1.176 +  /**
   1.177 +   * Handles the listTabs request. The actors will survive until at least
   1.178 +   * the next listTabs request.
   1.179 +   */
   1.180 +  onListTabs: function() {
   1.181 +    let tabList = this._parameters.tabList;
   1.182 +    if (!tabList) {
   1.183 +      return { from: this.actorID, error: "noTabs",
   1.184 +               message: "This root actor has no browser tabs." };
   1.185 +    }
   1.186 +
   1.187 +    /*
   1.188 +     * Walk the tab list, accumulating the array of tab actors for the
   1.189 +     * reply, and moving all the actors to a new ActorPool. We'll
   1.190 +     * replace the old tab actor pool with the one we build here, thus
   1.191 +     * retiring any actors that didn't get listed again, and preparing any
   1.192 +     * new actors to receive packets.
   1.193 +     */
   1.194 +    let newActorPool = new ActorPool(this.conn);
   1.195 +    let tabActorList = [];
   1.196 +    let selected;
   1.197 +    return tabList.getList().then((tabActors) => {
   1.198 +      for (let tabActor of tabActors) {
   1.199 +        if (tabActor.selected) {
   1.200 +          selected = tabActorList.length;
   1.201 +        }
   1.202 +        tabActor.parentID = this.actorID;
   1.203 +        newActorPool.addActor(tabActor);
   1.204 +        tabActorList.push(tabActor);
   1.205 +      }
   1.206 +
   1.207 +      /* DebuggerServer.addGlobalActor support: create actors. */
   1.208 +      if (!this._globalActorPool) {
   1.209 +        this._globalActorPool = new ActorPool(this.conn);
   1.210 +        this._createExtraActors(this._parameters.globalActorFactories, this._globalActorPool);
   1.211 +        this.conn.addActorPool(this._globalActorPool);
   1.212 +      }
   1.213 +
   1.214 +      /*
   1.215 +       * Drop the old actorID -> actor map. Actors that still mattered were
   1.216 +       * added to the new map; others will go away.
   1.217 +       */
   1.218 +      if (this._tabActorPool) {
   1.219 +        this.conn.removeActorPool(this._tabActorPool);
   1.220 +      }
   1.221 +      this._tabActorPool = newActorPool;
   1.222 +      this.conn.addActorPool(this._tabActorPool);
   1.223 +
   1.224 +      let reply = {
   1.225 +        "from": this.actorID,
   1.226 +        "selected": selected || 0,
   1.227 +        "tabs": [actor.form() for (actor of tabActorList)],
   1.228 +      };
   1.229 +
   1.230 +      /* If a root window is accessible, include its URL. */
   1.231 +      if (this.url) {
   1.232 +        reply.url = this.url;
   1.233 +      }
   1.234 +
   1.235 +      /* DebuggerServer.addGlobalActor support: name actors in 'listTabs' reply. */
   1.236 +      this._appendExtraActors(reply);
   1.237 +
   1.238 +      /*
   1.239 +       * Now that we're actually going to report the contents of tabList to
   1.240 +       * the client, we're responsible for letting the client know if it
   1.241 +       * changes.
   1.242 +       */
   1.243 +      tabList.onListChanged = this._onTabListChanged;
   1.244 +
   1.245 +      return reply;
   1.246 +    });
   1.247 +  },
   1.248 +
   1.249 +  onTabListChanged: function () {
   1.250 +    this.conn.send({ from: this.actorID, type:"tabListChanged" });
   1.251 +    /* It's a one-shot notification; no need to watch any more. */
   1.252 +    this._parameters.tabList.onListChanged = null;
   1.253 +  },
   1.254 +
   1.255 +  onListAddons: function () {
   1.256 +    let addonList = this._parameters.addonList;
   1.257 +    if (!addonList) {
   1.258 +      return { from: this.actorID, error: "noAddons",
   1.259 +               message: "This root actor has no browser addons." };
   1.260 +    }
   1.261 +
   1.262 +    return addonList.getList().then((addonActors) => {
   1.263 +      let addonActorPool = new ActorPool(this.conn);
   1.264 +      for (let addonActor of addonActors) {
   1.265 +          addonActorPool.addActor(addonActor);
   1.266 +      }
   1.267 +
   1.268 +      if (this._addonActorPool) {
   1.269 +        this.conn.removeActorPool(this._addonActorPool);
   1.270 +      }
   1.271 +      this._addonActorPool = addonActorPool;
   1.272 +      this.conn.addActorPool(this._addonActorPool);
   1.273 +
   1.274 +      addonList.onListChanged = this._onAddonListChanged;
   1.275 +
   1.276 +      return {
   1.277 +        "from": this.actorID,
   1.278 +        "addons": [addonActor.form() for (addonActor of addonActors)]
   1.279 +      };
   1.280 +    });
   1.281 +  },
   1.282 +
   1.283 +  onAddonListChanged: function () {
   1.284 +    this.conn.send({ from: this.actorID, type: "addonListChanged" });
   1.285 +    this._parameters.addonList.onListChanged = null;
   1.286 +  },
   1.287 +
   1.288 +  /* This is not in the spec, but it's used by tests. */
   1.289 +  onEcho: function (aRequest) {
   1.290 +    /*
   1.291 +     * Request packets are frozen. Copy aRequest, so that
   1.292 +     * DebuggerServerConnection.onPacket can attach a 'from' property.
   1.293 +     */
   1.294 +    return JSON.parse(JSON.stringify(aRequest));
   1.295 +  },
   1.296 +
   1.297 +  onProtocolDescription: function (aRequest) {
   1.298 +    return protocol.dumpProtocolSpec()
   1.299 +  },
   1.300 +
   1.301 +  /* Support for DebuggerServer.addGlobalActor. */
   1.302 +  _createExtraActors: createExtraActors,
   1.303 +  _appendExtraActors: appendExtraActors,
   1.304 +
   1.305 +  /* ThreadActor hooks. */
   1.306 +
   1.307 +  /**
   1.308 +   * Prepare to enter a nested event loop by disabling debuggee events.
   1.309 +   */
   1.310 +  preNest: function() {
   1.311 +    // Disable events in all open windows.
   1.312 +    let e = Services.wm.getEnumerator(null);
   1.313 +    while (e.hasMoreElements()) {
   1.314 +      let win = e.getNext();
   1.315 +      let windowUtils = win.QueryInterface(Ci.nsIInterfaceRequestor)
   1.316 +                           .getInterface(Ci.nsIDOMWindowUtils);
   1.317 +      windowUtils.suppressEventHandling(true);
   1.318 +      windowUtils.suspendTimeouts();
   1.319 +    }
   1.320 +  },
   1.321 +
   1.322 +  /**
   1.323 +   * Prepare to exit a nested event loop by enabling debuggee events.
   1.324 +   */
   1.325 +  postNest: function(aNestData) {
   1.326 +    // Enable events in all open windows.
   1.327 +    let e = Services.wm.getEnumerator(null);
   1.328 +    while (e.hasMoreElements()) {
   1.329 +      let win = e.getNext();
   1.330 +      let windowUtils = win.QueryInterface(Ci.nsIInterfaceRequestor)
   1.331 +                           .getInterface(Ci.nsIDOMWindowUtils);
   1.332 +      windowUtils.resumeTimeouts();
   1.333 +      windowUtils.suppressEventHandling(false);
   1.334 +    }
   1.335 +  }
   1.336 +};
   1.337 +
   1.338 +RootActor.prototype.requestTypes = {
   1.339 +  "listTabs": RootActor.prototype.onListTabs,
   1.340 +  "listAddons": RootActor.prototype.onListAddons,
   1.341 +  "echo": RootActor.prototype.onEcho,
   1.342 +  "protocolDescription": RootActor.prototype.onProtocolDescription
   1.343 +};

mercurial