Bug 1069673 - Add support methods on devtool's Target, r=jryans

This commit is contained in:
Jordan Santell 2014-10-22 13:33:00 +02:00
parent 2f39e96d72
commit 0ba8932661
7 changed files with 223 additions and 23 deletions

View File

@ -109,24 +109,14 @@ exports.TargetFactory = {
* The 'version' property allows the developer tools equivalent of browser
* detection. Browser detection is evil, however while we don't know what we
* will need to detect in the future, it is an easy way to postpone work.
* We should be looking to use 'supports()' in place of version where
* possible.
* We should be looking to use the support features added in bug 1069673
* in place of version where possible.
*/
function getVersion() {
// FIXME: return something better
return 20;
}
/**
* A better way to support feature detection, but we're not yet at a place
* where we have the features well enough defined for this to make lots of
* sense.
*/
function supports(feature) {
// FIXME: return something better
return false;
};
/**
* A Target represents something that we can debug. Targets are generally
* read-only. Any changes that you wish to make to a target should be done via
@ -151,12 +141,6 @@ function supports(feature) {
* - hidden: The target is not visible anymore (for TargetTab, another tab is selected)
* - visible: The target is visible (for TargetTab, tab is selected)
*
* Target also supports 2 functions to help allow 2 different versions of
* Firefox debug each other. The 'version' property is the equivalent of
* browser detection - simple and easy to implement but gets fragile when things
* are not quite what they seem. The 'supports' property is the equivalent of
* feature detection - harder to setup, but more robust long-term.
*
* Comparing Targets: 2 instances of a Target object can point at the same
* thing, so t1 !== t2 and t1 != t2 even when they represent the same object.
* To compare to targets use 't1.equals(t2)'.
@ -196,7 +180,109 @@ function TabTarget(tab) {
TabTarget.prototype = {
_webProgressListener: null,
supports: supports,
/**
* Returns a promise for the protocol description from the root actor.
* Used internally with `target.actorHasMethod`. Takes advantage of
* caching if definition was fetched previously with the corresponding
* actor information. Must be a remote target.
*
* @return {Promise}
* {
* "category": "actor",
* "typeName": "longstractor",
* "methods": [{
* "name": "substring",
* "request": {
* "type": "substring",
* "start": {
* "_arg": 0,
* "type": "primitive"
* },
* "end": {
* "_arg": 1,
* "type": "primitive"
* }
* },
* "response": {
* "substring": {
* "_retval": "primitive"
* }
* }
* }],
* "events": {}
* }
*/
getActorDescription: function (actorName) {
if (!this.client) {
throw new Error("TabTarget#getActorDescription() can only be called on remote tabs.");
}
let deferred = promise.defer();
if (this._protocolDescription && this._protocolDescription.types[actorName]) {
deferred.resolve(this._protocolDescription.types[actorName]);
} else {
this.client.mainRoot.protocolDescription(description => {
this._protocolDescription = description;
deferred.resolve(description.types[actorName]);
});
}
return deferred.promise;
},
/**
* Returns a boolean indicating whether or not the specific actor
* type exists. Must be a remote target.
*
* @param {String} actorName
* @return {Boolean}
*/
hasActor: function (actorName) {
if (!this.client) {
throw new Error("TabTarget#hasActor() can only be called on remote tabs.");
}
if (this.form) {
return !!this.form[actorName + "Actor"];
}
return false;
},
/**
* Queries the protocol description to see if an actor has
* an available method. The actor must already be lazily-loaded,
* so this is for use inside of tool. Returns a promise that
* resolves to a boolean. Must be a remote target.
*
* @param {String} actorName
* @param {String} methodName
* @return {Promise}
*/
actorHasMethod: function (actorName, methodName) {
if (!this.client) {
throw new Error("TabTarget#actorHasMethod() can only be called on remote tabs.");
}
return this.getActorDescription(actorName).then(desc => {
if (desc && desc.methods) {
return !!desc.methods.find(method => method.name === methodName);
}
return false;
});
},
/**
* Returns a trait from the root actor.
*
* @param {String} traitName
* @return {Mixed}
*/
getTrait: function (traitName) {
if (!this.client) {
throw new Error("TabTarget#getTrait() can only be called on remote tabs.");
}
return this.client.traits[traitName];
},
get version() { return getVersion(); },
get tab() {
@ -609,7 +695,6 @@ function WindowTarget(window) {
}
WindowTarget.prototype = {
supports: supports,
get version() { return getVersion(); },
get window() {

View File

@ -15,6 +15,7 @@ skip-if = e10s # Bug 1070837 - devtools/framework/toolbox.js |doc| getter not e1
[browser_new_activation_workflow.js]
[browser_target_events.js]
[browser_target_remote.js]
[browser_target_support.js]
[browser_two_tabs.js]
[browser_toolbox_dynamic_registration.js]
[browser_toolbox_highlight.js]

View File

@ -0,0 +1,89 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// Test support methods on Target, such as `hasActor`, `getActorDescription`,
// `actorHasMethod` and `getTrait`.
let { DebuggerServer } =
Cu.import("resource://gre/modules/devtools/dbg-server.jsm", {});
let { DebuggerClient } =
Cu.import("resource://gre/modules/devtools/dbg-client.jsm", {});
let { devtools } =
Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
let { Task } =
Cu.import("resource://gre/modules/Task.jsm", {});
let { WebAudioFront } =
devtools.require("devtools/server/actors/webaudio");
function* testTarget (client, target) {
yield target.makeRemote();
ise(target.hasActor("timeline"), true, "target.hasActor() true when actor exists.");
ise(target.hasActor("webaudio"), true, "target.hasActor() true when actor exists.");
ise(target.hasActor("notreal"), false, "target.hasActor() false when actor does not exist.");
// Create a front to ensure the actor is loaded
let front = new WebAudioFront(target.client, target.form);
let desc = yield target.getActorDescription("webaudio");
ise(desc.typeName, "webaudio",
"target.getActorDescription() returns definition data for corresponding actor");
ise(desc.events["start-context"]["type"], "startContext",
"target.getActorDescription() returns event data for corresponding actor");
desc = yield target.getActorDescription("nope");
ise(desc, undefined, "target.getActorDescription() returns undefined for non-existing actor");
desc = yield target.getActorDescription();
ise(desc, undefined, "target.getActorDescription() returns undefined for undefined actor");
let hasMethod = yield target.actorHasMethod("audionode", "getType");
ise(hasMethod, true,
"target.actorHasMethod() returns true for existing actor with method");
hasMethod = yield target.actorHasMethod("audionode", "nope");
ise(hasMethod, false,
"target.actorHasMethod() returns false for existing actor with no method");
hasMethod = yield target.actorHasMethod("nope", "nope");
ise(hasMethod, false,
"target.actorHasMethod() returns false for non-existing actor with no method");
hasMethod = yield target.actorHasMethod();
ise(hasMethod, false,
"target.actorHasMethod() returns false for undefined params");
ise(target.getTrait("customHighlighters")[0], "BoxModelHighlighter",
"target.getTrait() returns objects when trait exists");
ise(target.getTrait("giddyup"), undefined,
"target.getTrait() returns undefined when trait does not exist");
close(target, client);
}
// Ensure target is closed if client is closed directly
function test() {
waitForExplicitFinish();
if (!DebuggerServer.initialized) {
DebuggerServer.init(function () { return true; });
DebuggerServer.addBrowserActors();
}
var client = new DebuggerClient(DebuggerServer.connectPipe());
client.connect(() => {
client.listTabs(response => {
let options = {
form: response,
client: client,
chrome: true
};
devtools.TargetFactory.forRemoteTab(options).then(Task.async(testTarget).bind(null, client));
});
});
}
function close (target, client) {
target.on("close", () => {
ok(true, "Target was closed");
DebuggerServer.destroy();
finish();
});
client.close();
}

View File

@ -313,7 +313,7 @@ Tools.timeline = {
tooltip: l10n("timeline.tooltip", timelineStrings),
isTargetSupported: function(target) {
return !target.isAddon;
return !target.isAddon && target.hasActor("timeline");
},
build: function (iframeWindow, toolbox) {

View File

@ -193,13 +193,15 @@ let CommandUtils = {
* reflects the current debug target
*/
createEnvironment: function(container, targetProperty='target') {
if (container[targetProperty].supports == null) {
if (!container[targetProperty].toString ||
!/TabTarget/.test(container[targetProperty].toString())) {
throw new Error('Missing target');
}
return {
get target() {
if (container[targetProperty].supports == null) {
if (!container[targetProperty].toString ||
!/TabTarget/.test(container[targetProperty].toString())) {
throw new Error('Removed target');
}

View File

@ -5139,6 +5139,20 @@
"n_buckets": "1000",
"description": "The time (in milliseconds) that it took a 'listTabs' request to go round trip."
},
"DEVTOOLS_DEBUGGER_RDP_LOCAL_PROTOCOLDESCRIPTION_MS": {
"expires_in_version": "never",
"kind": "exponential",
"high": "10000",
"n_buckets": "1000",
"description": "The time (in milliseconds) that it took a 'protocolDescription' request to go round trip."
},
"DEVTOOLS_DEBUGGER_RDP_REMOTE_PROTOCOLDESCRIPTION_MS": {
"expires_in_version": "never",
"kind": "exponential",
"high": "10000",
"n_buckets": "1000",
"description": "The time (in milliseconds) that it took a 'protocolDescription' request to go round trip."
},
"DEVTOOLS_DEBUGGER_RDP_LOCAL_LISTADDONS_MS": {
"expires_in_version": "never",
"kind": "exponential",

View File

@ -1451,6 +1451,15 @@ RootClient.prototype = {
listAddons: DebuggerClient.requester({ type: "listAddons" },
{ telemetry: "LISTADDONS" }),
/**
* Description of protocol's actors and methods.
*
* @param function aOnResponse
* Called with the response packet.
*/
protocolDescription: DebuggerClient.requester({ type: "protocolDescription" },
{ telemetry: "PROTOCOLDESCRIPTION" }),
/*
* Methods constructed by DebuggerClient.requester require these forwards
* on their 'this'.