--- a/devtools/server/actors/chrome.js
+++ b/devtools/server/actors/chrome.js
@@ -5,16 +5,20 @@
"use strict";
const { Ci } = require("chrome");
const Services = require("Services");
const { DebuggerServer } = require("../main");
const { getChildDocShells, TabActor } = require("./tab");
const makeDebugger = require("./utils/make-debugger");
+const { extend } = require("devtools/shared/extend");
+const { ActorClassWithSpec, Actor } = require("devtools/shared/protocol");
+const { tabSpec } = require("devtools/shared/specs/tab");
+
/**
* Creates a TabActor for debugging all the chrome content in the
* current process. Most of the implementation is inherited from TabActor.
* ChromeActor is a child of RootActor, it can be instanciated via
* RootActor.getProcess request.
* ChromeActor exposes all tab actors via its form() request, like TabActor.
*
* History lecture:
@@ -25,17 +29,26 @@ const makeDebugger = require("./utils/ma
* So we are now exposing a process actor that offers the same API as TabActor
* by inheriting its functionality.
* Global actors are now only the actors that are meant to be global,
* and are no longer related to any specific scope/document.
*
* @param connection DebuggerServerConnection
* The connection to the client.
*/
-function ChromeActor(connection) {
+
+/* We are creating a prototype object rather than a class here, so in order
+ * to inherit from the TabActor, we extend from a normal object, and apply the
+ * properties of the TabActor prototype
+ * */
+
+const chromePrototype = extend({}, TabActor.prototype);
+
+chromePrototype.initialize = function(connection) {
+ Actor.prototype.initialize.call(this, connection);
TabActor.call(this, connection);
// This creates a Debugger instance for chrome debugging all globals.
this.makeDebugger = makeDebugger.bind(null, {
findDebuggees: dbg => dbg.findAllGlobals(),
shouldAddNewGlobalAsDebuggee: () => true
});
@@ -64,62 +77,57 @@ function ChromeActor(connection) {
// On XPCShell, there is no window/docshell
let docShell = window ? window.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDocShell)
: null;
Object.defineProperty(this, "docShell", {
value: docShell,
configurable: true
});
-}
-exports.ChromeActor = ChromeActor;
+};
-ChromeActor.prototype = Object.create(TabActor.prototype);
-
-ChromeActor.prototype.constructor = ChromeActor;
-
-ChromeActor.prototype.isRootActor = true;
+chromePrototype.isRootActor = true;
/**
* Getter for the list of all docshells in this tabActor
* @return {Array}
*/
-Object.defineProperty(ChromeActor.prototype, "docShells", {
+Object.defineProperty(chromePrototype, "docShells", {
get: function() {
// Iterate over all top-level windows and all their docshells.
let docShells = [];
let e = Services.ww.getWindowEnumerator();
while (e.hasMoreElements()) {
let window = e.getNext();
let docShell = window.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebNavigation)
.QueryInterface(Ci.nsIDocShell);
docShells = docShells.concat(getChildDocShells(docShell));
}
return docShells;
}
});
-ChromeActor.prototype.observe = function(subject, topic, data) {
+chromePrototype.observe = function(subject, topic, data) {
TabActor.prototype.observe.call(this, subject, topic, data);
if (!this.attached) {
return;
}
subject.QueryInterface(Ci.nsIDocShell);
if (topic == "chrome-webnavigation-create") {
this._onDocShellCreated(subject);
} else if (topic == "chrome-webnavigation-destroy") {
this._onDocShellDestroy(subject);
}
};
-ChromeActor.prototype._attach = function() {
+chromePrototype._attach = function() {
if (this.attached) {
return false;
}
TabActor.prototype._attach.call(this);
// Listen for any new/destroyed chrome docshell
Services.obs.addObserver(this, "chrome-webnavigation-create");
@@ -135,17 +143,17 @@ ChromeActor.prototype._attach = function
if (docShell == this.docShell) {
continue;
}
this._progressListener.watch(docShell);
}
return undefined;
};
-ChromeActor.prototype._detach = function() {
+chromePrototype._detach = function() {
if (!this.attached) {
return false;
}
Services.obs.removeObserver(this, "chrome-webnavigation-create");
Services.obs.removeObserver(this, "chrome-webnavigation-destroy");
// Iterate over all top-level windows.
@@ -165,34 +173,38 @@ ChromeActor.prototype._detach = function
return undefined;
};
/* ThreadActor hooks. */
/**
* Prepare to enter a nested event loop by disabling debuggee events.
*/
-ChromeActor.prototype.preNest = function() {
+chromePrototype.preNest = function() {
// Disable events in all open windows.
let e = Services.wm.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.
*/
-ChromeActor.prototype.postNest = function(nestData) {
+chromePrototype.postNest = function(nestData) {
// Enable events in all open windows.
let e = Services.wm.getEnumerator(null);
while (e.hasMoreElements()) {
let win = e.getNext();
let windowUtils = win.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils);
windowUtils.resumeTimeouts();
windowUtils.suppressEventHandling(false);
}
};
+
+chromePrototype.typeName = "Chrome";
+exports.chromePrototype = chromePrototype;
+exports.ChromeActor = ActorClassWithSpec(tabSpec, chromePrototype);
--- a/devtools/server/actors/tab.js
+++ b/devtools/server/actors/tab.js
@@ -193,16 +193,17 @@ function getInnerId(window) {
* @param connection DebuggerServerConnection
* The conection to the client.
*/
function TabActor(connection) {
// This usage of decorate should be removed in favor of using ES6 extends EventEmitter.
// See Bug 1394816.
EventEmitter.decorate(this);
+ dump('^^^^^^^^^^^^^^^^^ initialized\n');
this.conn = connection;
this._tabActorPool = null;
// A map of actor names to actor instances provided by extensions.
this._extraActors = {};
this._exited = false;
this._sources = null;
// Map of DOM stylesheets to StyleSheetActors
@@ -476,16 +477,17 @@ TabActor.prototype = {
if (!this._tabActorPool) {
this._tabActorPool = new ActorPool(this.conn);
this.conn.addActorPool(this._tabActorPool);
}
// Walk over tab actor factories and make sure they are all
// instantiated and added into the ActorPool. Note that some
// factories can be added dynamically by extensions.
+ dump(`$$$$$$$$$$$$$$$$$$$$$$ Problem ${this._extraActors}\n`);
this._createExtraActors(DebuggerServer.tabActorFactories,
this._tabActorPool);
this._appendExtraActors(response);
return response;
},
/**
@@ -606,17 +608,17 @@ TabActor.prototype = {
},
_unwatchDocShell(docShell) {
if (this._progressListener) {
this._progressListener.unwatch(docShell);
}
},
- onSwitchToFrame(request) {
+ switchToFrame(request) {
let windowId = request.windowId;
let win;
try {
win = Services.wm.getOuterWindowWithId(windowId);
} catch (e) {
// ignore
}
@@ -628,22 +630,22 @@ TabActor.prototype = {
}
// Reply first before changing the document
DevToolsUtils.executeSoon(() => this._changeTopLevelDocument(win));
return {};
},
- onListFrames(request) {
+ listFrames(request) {
let windows = this._docShellsToWindows(this.docShells);
return { frames: windows };
},
- onListWorkers(request) {
+ listWorkers(request) {
if (!this.attached) {
return { error: "wrongState" };
}
if (this._workerActorList === null) {
this._workerActorList = new WorkerActorList(this.conn, {
type: Ci.nsIWorkerDebugger.TYPE_DEDICATED,
window: this.window
@@ -664,17 +666,17 @@ TabActor.prototype = {
return {
"from": this.actorID,
"workers": actors.map((actor) => actor.form())
};
});
},
- onLogInPage(request) {
+ logInPage(request) {
let {text, category, flags} = request;
let scriptErrorClass = Cc["@mozilla.org/scripterror;1"];
let scriptError = scriptErrorClass.createInstance(Ci.nsIScriptError);
scriptError.initWithWindowID(text, null, null, 0, 0, flags,
category, getInnerId(this.window));
Services.console.logMessage(scriptError);
return {};
},
@@ -934,86 +936,86 @@ TabActor.prototype = {
this.conn.send({ from: this.actorID,
type: "tabDetached" });
return true;
},
// Protocol Request Handlers
- onAttach(request) {
+ attach(request) {
if (this.exited) {
return { type: "exited" };
}
this._attach();
return {
type: "tabAttached",
threadActor: this.threadActor.actorID,
cacheDisabled: this._getCacheDisabled(),
javascriptEnabled: this._getJavascriptEnabled(),
traits: this.traits,
};
},
- onDetach(request) {
+ detach(request) {
if (!this._detach()) {
return { error: "wrongState" };
}
return { type: "detached" };
},
/**
* Bring the tab's window to front.
*/
- onFocus() {
+ focus() {
if (this.window) {
this.window.focus();
}
return {};
},
/**
* Reload the page in this tab.
*/
- onReload(request) {
+ reload(request) {
let force = request && request.options && request.options.force;
// Wait a tick so that the response packet can be dispatched before the
// subsequent navigation event packet.
Services.tm.dispatchToMainThread(DevToolsUtils.makeInfallible(() => {
// This won't work while the browser is shutting down and we don't really
// care.
if (Services.startup.shuttingDown) {
return;
}
this.webNavigation.reload(force ?
Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE :
Ci.nsIWebNavigation.LOAD_FLAGS_NONE);
- }, "TabActor.prototype.onReload's delayed body"));
+ }, "TabActor.prototype.reload's delayed body"));
return {};
},
/**
* Navigate this tab to a new location
*/
- onNavigateTo(request) {
+ navigateTo(request) {
// Wait a tick so that the response packet can be dispatched before the
// subsequent navigation event packet.
Services.tm.dispatchToMainThread(DevToolsUtils.makeInfallible(() => {
this.window.location = request.url;
- }, "TabActor.prototype.onNavigateTo's delayed body"));
+ }, "TabActor.prototype.navigateTo's delayed body"));
return {};
},
/**
* Reconfigure options.
*/
- onReconfigure(request) {
+ reconfigure(request) {
let options = request.options || {};
if (!this.docShell) {
// The tab is already closed.
return {};
}
this._toggleDevToolsSettings(options);
@@ -1073,17 +1075,17 @@ TabActor.prototype = {
}
// Reload if:
// - there's an explicit `performReload` flag and it's true
// - there's no `performReload` flag, but it makes sense to do so
let hasExplicitReloadFlag = "performReload" in options;
if ((hasExplicitReloadFlag && options.performReload) ||
(!hasExplicitReloadFlag && reload)) {
- this.onReload();
+ this.reload();
}
},
/**
* Opposite of the _toggleDevToolsSettings method, that reset document state
* when closing the toolbox.
*/
_restoreDocumentSettings() {
@@ -1236,16 +1238,17 @@ TabActor.prototype = {
// Here is the very important call where we switch the currently
// targeted context (it will indirectly update this.window and
// many other attributes defined from docShell).
Object.defineProperty(this, "docShell", {
value: docShell,
enumerable: true,
configurable: true
});
+ dump(`%%%%%%%%%%%%%%%%%%%%% connection ${Object.keys(this)}`);
this.emit("changed-toplevel-document");
this.conn.send({
from: this.actorID,
type: "frameUpdate",
selected: this.outerWindowID
});
},
@@ -1459,27 +1462,27 @@ TabActor.prototype = {
}
},
};
/**
* The request types this actor can handle.
*/
TabActor.prototype.requestTypes = {
- "attach": TabActor.prototype.onAttach,
- "detach": TabActor.prototype.onDetach,
- "focus": TabActor.prototype.onFocus,
- "reload": TabActor.prototype.onReload,
- "navigateTo": TabActor.prototype.onNavigateTo,
- "reconfigure": TabActor.prototype.onReconfigure,
+ "attach": TabActor.prototype.attach,
+ "detach": TabActor.prototype.detach,
+ "focus": TabActor.prototype.focus,
+ "reload": TabActor.prototype.reload,
+ "navigateTo": TabActor.prototype.navigateTo,
+ "reconfigure": TabActor.prototype.reconfigure,
"ensureCSSErrorReportingEnabled": TabActor.prototype.ensureCSSErrorReportingEnabled,
- "switchToFrame": TabActor.prototype.onSwitchToFrame,
- "listFrames": TabActor.prototype.onListFrames,
- "listWorkers": TabActor.prototype.onListWorkers,
- "logInPage": TabActor.prototype.onLogInPage,
+ "switchToFrame": TabActor.prototype.switchToFrame,
+ "listFrames": TabActor.prototype.listFrames,
+ "listWorkers": TabActor.prototype.listWorkers,
+ "logInPage": TabActor.prototype.logInPage,
};
exports.TabActor = TabActor;
/**
* The DebuggerProgressListener object is an nsIWebProgressListener which
* handles onStateChange events for the inspected browser. If the user tries to
* navigate away from a paused page, the listener makes sure that the debuggee
--- a/devtools/server/main.js
+++ b/devtools/server/main.js
@@ -10,17 +10,17 @@
*/
var { Ci, Cc } = require("chrome");
var Services = require("Services");
var { ActorPool, OriginalLocation, RegisteredActorFactory,
ObservedActorFactory } = require("devtools/server/actors/common");
var { LocalDebuggerTransport, ChildDebuggerTransport, WorkerDebuggerTransport } =
require("devtools/shared/transport/transport");
var DevToolsUtils = require("devtools/shared/DevToolsUtils");
-var { dumpn } = DevToolsUtils;
+var { dumpn, formatTypeSignature } = DevToolsUtils;
DevToolsUtils.defineLazyGetter(this, "DebuggerSocket", () => {
let { DebuggerSocket } = require("devtools/shared/security/socket");
return DebuggerSocket;
});
DevToolsUtils.defineLazyGetter(this, "Authentication", () => {
return require("devtools/shared/security/auth");
});
@@ -1758,16 +1758,23 @@ DebuggerServerConnection.prototype = {
ret = actor.requestTypes[packet.type].bind(actor)(packet, this);
} catch (error) {
let prefix = "error occurred while processing '" + packet.type;
this.transport.send(this._unknownError(actor.actorID, prefix, error));
} finally {
this.currentPacket = undefined;
}
} else {
+ dump(
+ `Unrecognized packet with type "${packet.type}" and following request type` +
+ ` signiture = \n${formatTypeSignature(packet)}\n`
+ );
+ dump(`known types for this actor are: \n ${
+ Object.keys(actor.requestTypes).join("\n ")
+ }\n`);
ret = { error: "unrecognizedPacketType",
message: ("Actor " + actor.actorID +
" does not recognize the packet type " +
packet.type) };
}
// There will not be a return value if a bulk reply is sent.
if (ret) {
--- a/devtools/shared/DevToolsUtils.js
+++ b/devtools/shared/DevToolsUtils.js
@@ -376,16 +376,47 @@ exports.dumpn = function(str) {
*/
exports.dumpv = function(msg) {
if (flags.wantVerbose) {
exports.dumpn(msg);
}
};
/**
+ * Formats the types of a request in an easy to read way.
+ *
+ * @param object
+ * The object being traversed.
+ * @param depth
+ * The indentation of this segment of string.
+ */
+function formatTypeSignature(object, depth = 0) {
+ function typeSignatureToString(string, key) {
+ if (key === "to" || key === "type") {
+ return string;
+ }
+ const padding = " ".repeat(depth);
+ const value = object[key];
+ const type = typeof value;
+ let newSegment;
+ if (type === "object") {
+ const stringOutput = formatTypeSignature(value, depth + 1);
+ newSegment = `${padding}${key}: {\n${stringOutput}${padding}}\n`;
+ } else {
+ newSegment = `${padding}${key}: ${type}\n`;
+ }
+ return string + newSegment;
+ }
+
+ return Object.keys(object).reduce(typeSignatureToString, "");
+}
+
+exports.formatTypeSignature = formatTypeSignature;
+
+/**
* Defines a getter on a specified object that will be created upon first use.
*
* @param object
* The object to define the lazy getter on.
* @param name
* The name of the getter to define on object.
* @param lambda
* A function that returns what the getter should return. This will
--- a/devtools/shared/protocol.js
+++ b/devtools/shared/protocol.js
@@ -1065,16 +1065,23 @@ var generateActorSpec = function(actorDe
actorSpec.methods.push(spec);
}
}
// Find additional method specifications
if (actorDesc.methods) {
for (let name in actorDesc.methods) {
let methodSpec = actorDesc.methods[name];
+ if (methodSpec.request && methodSpec.request.type) {
+ dump(
+ `Request object for protocol method "${name}" has a "type" field and this ` +
+ "will override the spec name. Have you use an Option or Arg without a " +
+ "wrapping it in request object?\n"
+ );
+ }
let spec = {};
spec.name = methodSpec.name || name;
spec.request = new Request(Object.assign({type: spec.name},
methodSpec.request || undefined));
spec.response = new Response(methodSpec.response || undefined);
spec.release = methodSpec.release;
spec.oneway = methodSpec.oneway;
@@ -1102,16 +1109,18 @@ var generateActorSpec = function(actorDe
};
exports.generateActorSpec = generateActorSpec;
/**
* Generates request handlers as described by the given actor specification on
* the given actor prototype. Returns the actor prototype.
*/
var generateRequestHandlers = function(actorSpec, actorProto) {
+ dump(`Actor Proto ${actorProto.typeName}\n`);
+ dump(`Actor Proto ${actorProto._actorSpec}\n`);
if (actorProto._actorSpec) {
throw new Error("actorProto called twice on the same actor prototype!");
}
actorProto.typeName = actorSpec.typeName;
// Generate request handlers for each method definition
actorProto.requestTypes = Object.create(null);
@@ -1191,16 +1200,17 @@ var ActorClassWithSpec = function(actorS
}
// Existing Actors are relying on the initialize instead of constructor methods.
let cls = function() {
let instance = Object.create(cls.prototype);
instance.initialize.apply(instance, arguments);
return instance;
};
+
cls.prototype = extend(Actor.prototype, generateRequestHandlers(actorSpec, actorProto));
return cls;
};
exports.ActorClassWithSpec = ActorClassWithSpec;
/**
* Base class for client-side actor fronts.
--- a/devtools/shared/specs/index.js
+++ b/devtools/shared/specs/index.js
@@ -200,16 +200,21 @@ const Types = exports.__TypesForTests =
front: "devtools/shared/fronts/stylesheets",
},
{
types: ["symbolIterator"],
spec: "devtools/shared/specs/symbol-iterator",
front: null,
},
{
+ types: ["tab"],
+ spec: "devtools/shared/specs/tab",
+ front: null,
+ },
+ {
types: ["timeline"],
spec: "devtools/shared/specs/timeline",
front: "devtools/shared/fronts/timeline",
},
{
types: ["audionode", "webaudio"],
spec: "devtools/shared/specs/webaudio",
front: "devtools/shared/fronts/webaudio",
--- a/devtools/shared/specs/moz.build
+++ b/devtools/shared/specs/moz.build
@@ -36,15 +36,16 @@ DevToolsModules(
'reflow.js',
'script.js',
'source.js',
'storage.js',
'string.js',
'styles.js',
'stylesheets.js',
'symbol-iterator.js',
+ 'tab.js',
'timeline.js',
'webaudio.js',
'webextension-inspected-window.js',
'webextension-parent.js',
'webgl.js',
'worker.js'
)
new file mode 100644
--- /dev/null
+++ b/devtools/shared/specs/tab.js
@@ -0,0 +1,116 @@
+/* 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 {types, generateActorSpec, RetVal, Arg, Option} = require("devtools/shared/protocol");
+
+types.addDictType("tab.attach", {
+ type: "string",
+ threadActor: "number",
+ cacheDisabled: "boolean",
+ javascriptEnabled: "boolean",
+ traits: "json",
+});
+
+types.addDictType("tab.detach", {
+ error: "nullable:string",
+ type: "nullable:string",
+});
+
+types.addDictType("tab.switchtoframe", {
+ error: "nullable:string",
+ message: "nullable:string",
+});
+
+types.addDictType("tab.listframes", {
+ frames: "array:tab.window",
+});
+
+types.addDictType("tab.window", {
+ id: "string",
+ parentID: "nullable:string",
+ url: "string",
+ title: "string",
+});
+
+types.addDictType("tab.workers", {
+ error: "nullable:string",
+ from: "nullable:string",
+});
+
+types.addDictType("tab.reloadoptions", {
+ force: "boolean"
+});
+
+types.addDictType("tab.reconfigure", {
+ javascriptEnabled: "nullable:boolean",
+ cacheDisabled: "nullable:boolean",
+ serviceWorkersTestingEnabled: "nullable:boolean",
+ performReload: "nullable:boolean"
+});
+
+types.addDictType("tab.loginpagerequest", {
+ text: "string",
+ category: "string",
+ flags: "strng"
+});
+
+const tabSpec = generateActorSpec({
+ typeName: "tab",
+
+ methods: {
+ attach: {
+ request: {},
+ response: RetVal("tab.attach")
+ },
+ detach: {
+ request: {},
+ response: RetVal("tab.detach")
+ },
+ focus: {
+ request: {},
+ response: {}
+ },
+ reload: {
+ request: {
+ options: Option(0, "tab.reloadoptions"),
+ },
+ response: {}
+ },
+ navigateTo: {
+ request: {
+ window: Arg(0, "string"),
+ },
+ response: {}
+ },
+ reconfigure: {
+ request: {
+ option: Option(0, "tab.reconfigure")
+ },
+ response: {}
+ },
+ switchToFrame: {
+ request: {},
+ response: RetVal("tab.switchtoframe")
+ },
+ listFrames: {
+ request: {},
+ response: RetVal("tab.listframes")
+ },
+ listWorkers: {
+ request: {},
+ response: RetVal("tab.workers")
+ },
+ logInPage: {
+ request: {
+ text: Arg(0, "string"),
+ category: Arg(1, "string"),
+ flags: Arg(2, "string")
+ },
+ response: {}
+ }
+ },
+});
+
+exports.tabSpec = tabSpec;