2013-10-12 13:44:57 +08:00
|
|
|
/* -*- tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 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";
|
|
|
|
|
|
|
|
/* Root actor for the remote debugging protocol. */
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Methods shared between RootActor and BrowserTabActor.
|
|
|
|
*/
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Populate |this._extraActors| as specified by |aFactories|, reusing whatever
|
|
|
|
* actors are already there. Add all actors in the final extra actors table to
|
|
|
|
* |aPool|.
|
|
|
|
*
|
|
|
|
* The root actor and the tab actor use this to instantiate actors that other
|
|
|
|
* parts of the browser have specified with DebuggerServer.addTabActor antd
|
|
|
|
* DebuggerServer.addGlobalActor.
|
|
|
|
*
|
|
|
|
* @param aFactories
|
|
|
|
* An object whose own property names are the names of properties to add to
|
|
|
|
* some reply packet (say, a tab actor grip or the "listTabs" response
|
|
|
|
* form), and whose own property values are actor constructor functions, as
|
|
|
|
* documented for addTabActor and addGlobalActor.
|
|
|
|
*
|
|
|
|
* @param this
|
|
|
|
* The BrowserRootActor or BrowserTabActor with which the new actors will
|
|
|
|
* be associated. It should support whatever API the |aFactories|
|
|
|
|
* constructor functions might be interested in, as it is passed to them.
|
|
|
|
* For the sake of CommonCreateExtraActors itself, it should have at least
|
|
|
|
* the following properties:
|
|
|
|
*
|
|
|
|
* - _extraActors
|
|
|
|
* An object whose own property names are factory table (and packet)
|
|
|
|
* property names, and whose values are no-argument actor constructors,
|
|
|
|
* of the sort that one can add to an ActorPool.
|
|
|
|
*
|
|
|
|
* - conn
|
|
|
|
* The DebuggerServerConnection in which the new actors will participate.
|
|
|
|
*
|
|
|
|
* - actorID
|
|
|
|
* The actor's name, for use as the new actors' parentID.
|
|
|
|
*/
|
|
|
|
function CommonCreateExtraActors(aFactories, aPool) {
|
|
|
|
// Walk over global actors added by extensions.
|
|
|
|
for (let name in aFactories) {
|
|
|
|
let actor = this._extraActors[name];
|
|
|
|
if (!actor) {
|
|
|
|
actor = aFactories[name].bind(null, this.conn, this);
|
|
|
|
actor.prototype = aFactories[name].prototype;
|
|
|
|
actor.parentID = this.actorID;
|
|
|
|
this._extraActors[name] = actor;
|
|
|
|
}
|
|
|
|
aPool.addActor(actor);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Append the extra actors in |this._extraActors|, constructed by a prior call
|
|
|
|
* to CommonCreateExtraActors, to |aObject|.
|
|
|
|
*
|
|
|
|
* @param aObject
|
|
|
|
* The object to which the extra actors should be added, under the
|
|
|
|
* property names given in the |aFactories| table passed to
|
|
|
|
* CommonCreateExtraActors.
|
|
|
|
*
|
|
|
|
* @param this
|
|
|
|
* The BrowserRootActor or BrowserTabActor whose |_extraActors| table we
|
|
|
|
* should use; see above.
|
|
|
|
*/
|
|
|
|
function CommonAppendExtraActors(aObject) {
|
|
|
|
for (let name in this._extraActors) {
|
|
|
|
let actor = this._extraActors[name];
|
|
|
|
aObject[name] = actor.actorID;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Create a remote debugging protocol root actor.
|
|
|
|
*
|
|
|
|
* @param aConnection
|
|
|
|
* The DebuggerServerConnection whose root actor we are constructing.
|
|
|
|
*
|
|
|
|
* @param aParameters
|
|
|
|
* The properties of |aParameters| provide backing objects for the root
|
|
|
|
* actor's requests; if a given property is omitted from |aParameters|, the
|
|
|
|
* root actor won't implement the corresponding requests or notifications.
|
|
|
|
* Supported properties:
|
|
|
|
*
|
|
|
|
* - tabList: a live list (see below) of tab actors. If present, the
|
|
|
|
* new root actor supports the 'listTabs' request, providing the live
|
|
|
|
* list's elements as its tab actors, and sending 'tabListChanged'
|
|
|
|
* notifications when the live list's contents change. One actor in
|
|
|
|
* this list must have a true '.selected' property.
|
|
|
|
*
|
|
|
|
* - globalActorFactories: an object |A| describing further actors to
|
|
|
|
* attach to the 'listTabs' reply. This is the type accumulated by
|
|
|
|
* DebuggerServer.addGlobalActor. For each own property |P| of |A|,
|
|
|
|
* the root actor adds a property named |P| to the 'listTabs'
|
|
|
|
* reply whose value is the name of an actor constructed by
|
|
|
|
* |A[P]|.
|
|
|
|
*
|
|
|
|
* - onShutdown: a function to call when the root actor is disconnected.
|
|
|
|
*
|
|
|
|
* Instance properties:
|
|
|
|
*
|
|
|
|
* - applicationType: the string the root actor will include as the
|
|
|
|
* "applicationType" property in the greeting packet. By default, this
|
|
|
|
* is "browser".
|
|
|
|
*
|
|
|
|
* Live lists:
|
|
|
|
*
|
|
|
|
* A "live list", as used for the |tabList|, is an object that presents a
|
|
|
|
* list of actors, and also notifies its clients of changes to the list. A
|
|
|
|
* live list's interface is two properties:
|
|
|
|
*
|
|
|
|
* - iterator: a method that returns an iterator. A for-of loop will call
|
|
|
|
* this method to obtain an iterator for the loop, so if LL is
|
|
|
|
* a live list, one can simply write 'for (i of LL) ...'.
|
|
|
|
*
|
|
|
|
* - onListChanged: a handler called, with no arguments, when the set of
|
|
|
|
* values the iterator would produce has changed since the last
|
|
|
|
* time 'iterator' was called. This may only be set to null or a
|
|
|
|
* callable value (one for which the typeof operator returns
|
|
|
|
* 'function'). (Note that the live list will not call the
|
|
|
|
* onListChanged handler until the list has been iterated over
|
|
|
|
* once; if nobody's seen the list in the first place, nobody
|
|
|
|
* should care if its contents have changed!)
|
|
|
|
*
|
|
|
|
* When the list changes, the list implementation should ensure that any
|
|
|
|
* actors yielded in previous iterations whose referents (tabs) still exist
|
|
|
|
* get yielded again in subsequent iterations. If the underlying referent
|
|
|
|
* is the same, the same actor should be presented for it.
|
|
|
|
*
|
|
|
|
* The root actor registers an 'onListChanged' handler on the appropriate
|
|
|
|
* list when it may need to send the client 'tabListChanged' notifications,
|
|
|
|
* and is careful to remove the handler whenever it does not need to send
|
|
|
|
* such notifications (including when it is disconnected). This means that
|
|
|
|
* live list implementations can use the state of the handler property (set
|
|
|
|
* or null) to install and remove observers and event listeners.
|
|
|
|
*
|
|
|
|
* Note that, as the only way for the root actor to see the members of the
|
|
|
|
* live list is to begin an iteration over the list, the live list need not
|
|
|
|
* actually produce any actors until they are reached in the course of
|
|
|
|
* iteration: alliterative lazy live lists.
|
|
|
|
*/
|
|
|
|
function RootActor(aConnection, aParameters) {
|
|
|
|
this.conn = aConnection;
|
|
|
|
this._parameters = aParameters;
|
|
|
|
this._onTabListChanged = this.onTabListChanged.bind(this);
|
|
|
|
this._extraActors = {};
|
|
|
|
}
|
|
|
|
|
|
|
|
RootActor.prototype = {
|
|
|
|
constructor: RootActor,
|
|
|
|
applicationType: "browser",
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Return a 'hello' packet as specified by the Remote Debugging Protocol.
|
|
|
|
*/
|
|
|
|
sayHello: function() {
|
|
|
|
return {
|
2013-10-31 10:20:45 +08:00
|
|
|
from: this.actorID,
|
2013-10-12 13:44:57 +08:00
|
|
|
applicationType: this.applicationType,
|
|
|
|
/* This is not in the spec, but it's used by tests. */
|
|
|
|
testConnectionPrefix: this.conn.prefix,
|
|
|
|
traits: {
|
|
|
|
sources: true
|
|
|
|
}
|
|
|
|
};
|
|
|
|
},
|
|
|
|
|
2013-10-31 10:20:45 +08:00
|
|
|
/**
|
|
|
|
* This is true for the root actor only, used by some child actors
|
|
|
|
*/
|
|
|
|
get isRootActor() true,
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The (chrome) window, for use by child actors
|
|
|
|
*/
|
|
|
|
get window() Services.wm.getMostRecentWindow(DebuggerServer.chromeWindowType),
|
|
|
|
|
2013-10-12 13:44:57 +08:00
|
|
|
/**
|
|
|
|
* Disconnects the actor from the browser window.
|
|
|
|
*/
|
|
|
|
disconnect: function() {
|
|
|
|
/* Tell the live lists we aren't watching any more. */
|
|
|
|
if (this._parameters.tabList) {
|
|
|
|
this._parameters.tabList.onListChanged = null;
|
|
|
|
}
|
|
|
|
if (typeof this._parameters.onShutdown === 'function') {
|
|
|
|
this._parameters.onShutdown();
|
|
|
|
}
|
|
|
|
this._extraActors = null;
|
|
|
|
},
|
|
|
|
|
|
|
|
/* The 'listTabs' request and the 'tabListChanged' notification. */
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Handles the listTabs request. The actors will survive until at least
|
|
|
|
* the next listTabs request.
|
|
|
|
*/
|
|
|
|
onListTabs: function() {
|
|
|
|
let tabList = this._parameters.tabList;
|
|
|
|
if (!tabList) {
|
2013-10-31 10:20:45 +08:00
|
|
|
return { from: this.actorID, error: "noTabs",
|
2013-10-12 13:44:57 +08:00
|
|
|
message: "This root actor has no browser tabs." };
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Walk the tab list, accumulating the array of tab actors for the
|
|
|
|
* reply, and moving all the actors to a new ActorPool. We'll
|
|
|
|
* replace the old tab actor pool with the one we build here, thus
|
|
|
|
* retiring any actors that didn't get listed again, and preparing any
|
|
|
|
* new actors to receive packets.
|
|
|
|
*/
|
|
|
|
let newActorPool = new ActorPool(this.conn);
|
|
|
|
let tabActorList = [];
|
|
|
|
let selected;
|
|
|
|
for (let tabActor of tabList) {
|
|
|
|
if (tabActor.selected) {
|
|
|
|
selected = tabActorList.length;
|
|
|
|
}
|
|
|
|
tabActor.parentID = this.actorID;
|
|
|
|
newActorPool.addActor(tabActor);
|
|
|
|
tabActorList.push(tabActor);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* DebuggerServer.addGlobalActor support: create actors. */
|
|
|
|
this._createExtraActors(this._parameters.globalActorFactories, newActorPool);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Drop the old actorID -> actor map. Actors that still mattered were
|
|
|
|
* added to the new map; others will go away.
|
|
|
|
*/
|
|
|
|
if (this._tabActorPool) {
|
|
|
|
this.conn.removeActorPool(this._tabActorPool);
|
|
|
|
}
|
|
|
|
this._tabActorPool = newActorPool;
|
|
|
|
this.conn.addActorPool(this._tabActorPool);
|
|
|
|
|
|
|
|
let reply = {
|
2013-10-31 10:20:45 +08:00
|
|
|
"from": this.actorID,
|
2013-10-12 13:44:57 +08:00
|
|
|
"selected": selected || 0,
|
2013-10-31 10:20:45 +08:00
|
|
|
"tabs": [actor.grip() for (actor of tabActorList)],
|
2013-10-12 13:44:57 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
/* DebuggerServer.addGlobalActor support: name actors in 'listTabs' reply. */
|
|
|
|
this._appendExtraActors(reply);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Now that we're actually going to report the contents of tabList to
|
|
|
|
* the client, we're responsible for letting the client know if it
|
|
|
|
* changes.
|
|
|
|
*/
|
|
|
|
tabList.onListChanged = this._onTabListChanged;
|
|
|
|
|
|
|
|
return reply;
|
|
|
|
},
|
|
|
|
|
|
|
|
onTabListChanged: function () {
|
2013-10-31 10:20:45 +08:00
|
|
|
this.conn.send({ from: this.actorID, type:"tabListChanged" });
|
2013-10-12 13:44:57 +08:00
|
|
|
/* It's a one-shot notification; no need to watch any more. */
|
|
|
|
this._parameters.tabList.onListChanged = null;
|
|
|
|
},
|
|
|
|
|
|
|
|
/* This is not in the spec, but it's used by tests. */
|
2013-10-31 10:20:45 +08:00
|
|
|
onEcho: function (aRequest) {
|
|
|
|
/*
|
|
|
|
* Request packets are frozen. Copy aRequest, so that
|
|
|
|
* DebuggerServerConnection.onPacket can attach a 'from' property.
|
|
|
|
*/
|
|
|
|
return JSON.parse(JSON.stringify(aRequest));
|
|
|
|
},
|
2013-10-12 13:44:57 +08:00
|
|
|
|
|
|
|
/* Support for DebuggerServer.addGlobalActor. */
|
|
|
|
_createExtraActors: CommonCreateExtraActors,
|
|
|
|
_appendExtraActors: CommonAppendExtraActors,
|
|
|
|
|
|
|
|
/* ThreadActor hooks. */
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Prepare to enter a nested event loop by disabling debuggee events.
|
|
|
|
*/
|
|
|
|
preNest: function() {
|
|
|
|
// Disable events in all open windows.
|
|
|
|
let e = windowMediator.getEnumerator(null);
|
|
|
|
while (e.hasMoreElements()) {
|
|
|
|
let win = e.getNext();
|
|
|
|
let windowUtils = win.QueryInterface(Ci.nsIInterfaceRequestor)
|
|
|
|
.getInterface(Ci.nsIDOMWindowUtils);
|
|
|
|
windowUtils.suppressEventHandling(true);
|
|
|
|
windowUtils.suspendTimeouts();
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Prepare to exit a nested event loop by enabling debuggee events.
|
|
|
|
*/
|
|
|
|
postNest: function(aNestData) {
|
|
|
|
// Enable events in all open windows.
|
|
|
|
let e = windowMediator.getEnumerator(null);
|
|
|
|
while (e.hasMoreElements()) {
|
|
|
|
let win = e.getNext();
|
|
|
|
let windowUtils = win.QueryInterface(Ci.nsIInterfaceRequestor)
|
|
|
|
.getInterface(Ci.nsIDOMWindowUtils);
|
|
|
|
windowUtils.resumeTimeouts();
|
|
|
|
windowUtils.suppressEventHandling(false);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
/* ChromeDebuggerActor hooks. */
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Add the specified actor to the default actor pool connection, in order to
|
|
|
|
* keep it alive as long as the server is. This is used by breakpoints in the
|
|
|
|
* thread and chrome debugger actors.
|
|
|
|
*
|
|
|
|
* @param actor aActor
|
|
|
|
* The actor object.
|
|
|
|
*/
|
|
|
|
addToParentPool: function(aActor) {
|
|
|
|
this.conn.addActor(aActor);
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Remove the specified actor from the default actor pool.
|
|
|
|
*
|
|
|
|
* @param BreakpointActor aActor
|
|
|
|
* The actor object.
|
|
|
|
*/
|
|
|
|
removeFromParentPool: function(aActor) {
|
|
|
|
this.conn.removeActor(aActor);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
RootActor.prototype.requestTypes = {
|
|
|
|
"listTabs": RootActor.prototype.onListTabs,
|
|
|
|
"echo": RootActor.prototype.onEcho
|
|
|
|
};
|