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 +};