Skip to content

Instantly share code, notes, and snippets.

@cbrem
Last active August 29, 2015 14:03
Show Gist options
  • Save cbrem/8bb224197911b33649ca to your computer and use it in GitHub Desktop.
Save cbrem/8bb224197911b33649ca to your computer and use it in GitHub Desktop.
Object Grip Client
const { Class } = require('sdk/core/heritage');
const { defer } = require('sdk/core/promise');
const { Cu } = require("chrome");
const { ObjectClient } = Cu.import('resource://gre/modules/devtools/dbg-client.jsm', {});
const SPECIAL_TYPES = Set(["null", "undefined", "Infinity", "-Infinity", "NaN", "-0"]);
const valueFromSpecialType = type => {
if (SPECIAL_TYPES.has(type)) {
return eval(type)
} else {
throw new Error("Invalid grip type: " + type);
}
};
/*
* Wraps a property descriptor so that its `value` field is either a primitive
* or an Obj.
*/
const Descriptor = (debuggerClient, descriptor) => {
let value;
if (descriptor.value.actor) {
// Its value is an object grip. Wrap it as an Obj.
//
// TODO: consider other clients for Array, Functions, etc.
let grip = descriptor.value;
value = Obj(debuggerClient, grip);
} else if (descriptor.value.type) {
// It has a type field which names its value.
// Evalate this type field to the value.
value = valueFromSpecialType(descriptor.value.type);
} else {
// Its value is a primitive. Return the property as is.
value = descriptor.value;
}
return {
configurable: descriptor.configurable,
enumerable: descriptor.enumerable,
writable: descriptor.writable,
value: value
};
};
/*
* Wraps an ObjectClient so that its interface is restricted and promise-based.
*/
const Obj = Class({
initialize: function initialize(debuggerClient, grip) {
this.grip = grip;
this.debuggerClient = debuggerClient;
this.client = new ObjectClient(debuggerClient, grip);
},
/*
* Resolves to a property descriptor with the following fields:
* - configurable (boolean)
* - enumerable (boolean)
* - writable (boolean)
* - value (primitive | Obj)
* or rejects if the Obj has no value for this key.
*/
descriptor: function descriptor(key) {
let deferred = defer();
this.client.getProperty(key, ({descriptor}) => {
if (descriptor) {
// The descriptor will have a value field which will be
// a grip (not necessarily for an object).
// Dress it up correctly.
deferred.resolve(Descriptor(this.debuggerClient, descriptor));
} else {
deferred.reject();
}
});
return deferred.promise;
},
/*
* Resolve to an Obj for this Obj's prototype,
* or rejects if this Obj has no prototype.
*/
prototype: function prototype() {
let deferred = defer();
this.client.getPrototype(({prototype}) => {
if (prototype) {
// The prototpe will be an object grip.
// Dress it up as an Obj.
deferred.resolve(Obj(this.debuggerClient, prototype));
} else {
deferred.reject();
}
});
return deferred.promise;
},
/*
* Returns an array of the names of keys for this Obj's own properties.
*/
keys: function keys() {
let deferred = defer();
this.client.getOwnPropertyNames(({ownPropertyNames}) => {
deferred.resolve(ownPropertyNames);
});
return deferred.promise;
}
});
exports.Object = Obj;
/*
* Creates a client.Object from a grip and its debuggerClient.
*/
const fromGrip = (debuggerClient, grip) => Obj(debuggerClient, grip);
exports.fromGrip = fromGrip;
/*
* Creates a client.Object from an existing ObjectClient.
*/
const fromClient = objectClient => Obj(objectClient._client, objectClient._grip);
exports.fromClient = fromClien
// When run as part of an addon, this file tests client.Object implementation in client.js.
const { Cu } = require('chrome');
const clients = require('dev/client');
const promise = require('sdk/core/promise');
const { require: __require__ } = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools;
const hudservice = __require__("devtools/webconsole/hudservice");
// JS strings to be evaled.
const INPUTS = [
"({a: 1, b: {c: 2}})",
"({a: {b: 1, c: {d: 2}}, e: 'hello', f: [1, 2, 3]})",
"(function (a, b) { return a + b })",
];
// We need this so that we don't recurse infinitely when fetching functions.
const DEPTH_LIMIT = 5;
// Globals.
let gWebConsoleClient;
let gDebuggerClient;
hudservice.toggleBrowserConsole().then(hud => {
gWebConsoleClient = hud.ui.webConsoleClient;
gDebuggerClient = gWebConsoleClient._client;
for (let js of INPUTS) evalAndFetch(js);
});
// Eval a JS string, and recursively fetch the properties
// of the resulting actor via the client.Object interface.
const evalAndFetch = js => {
gWebConsoleClient.evaluateJS(js, response => {
let grip = response.result;
let objectClient = clients.fromGrip(gDebuggerClient, grip);
fetchObject(objectClient, 0).then(object => {
console.log("input: ", js);
console.log("fetched", JSON.stringify(object));
});
});
};
const fetchObject = (objectClient, depth) => {
let deferred = promise.defer();
objectClient.keys().then(keys => {
promise.all(keys.map(key => fetchKey(objectClient, key, depth))).then(fetchedKeys => {
deferred.resolve(fetchedKeys.reduce((obj, [key, value]) => {
obj[key] = value;
return obj;
}, {}));
});
});
return deferred.promise;
};
const fetchKey = (objectClient, key, depth) => {
let deferred = promise.defer();
objectClient.descriptor(key).then(({value}) => {
if (depth == DEPTH_LIMIT) {
deferred.resolve([key, "..."]);
} else if (value instanceof clients.Object) {
fetchObject(value, depth + 1).then(fetchedValue => {
deferred.resolve([key, fetchedValue])
});
} else {
deferred.resolve([key, value]);
}
});
return deferred.promise;
};

#Object Grip Client

##Motivation

Devtools components like the Inspector will rely on debugger-side clients for Objects, Functions, etc. Protocol.js and Volcan do not make clients for these objects, as they do no have set interfaces. Additionally, it makes sense for us to fix an API for these clients in case devtools decides to change it.

##Current State

  • Client for Objects.

##Next Steps

  • Special client types for Function, Array, etc. in addition to just Objects.

##Attachments

  • client.js: Object client implementation
  • main.js: Tester for Object client implementation
@janodvarko
Copy link

I like this. What about other clients like e.g. LongStringClient that supports additional API (i.e. substring). Or clients that doesn't exist now e.g. FunctionClient that could also support additional API (e.g. getArguments). What do you think is better, having a hierarchy of clients or rather just one that is generic?

Honza

@janodvarko
Copy link

Now I saw your note: "It would function similarly to Volcan, except for arbitrary objects which do not have actors with known methods."

... so sounds like other (more specific) client objects could appear....

Honza

@cbrem
Copy link
Author

cbrem commented Jul 14, 2014

Honza, I agree that it makes sense to have a few other sorts of clients. Adding a function client and a LongString client shouldn't be too difficult. I'll look into this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment