Commit 2cc58b53 authored by lukas.niegsch's avatar lukas.niegsch

finished dynamic api for methods

parent 67150aa0
......@@ -9,8 +9,8 @@
</head>
<body>
<div
data-gadget-url="gadget_browser.html"
data-gadget-scope="browser"
data-gadget-url="gadget_devtools.html"
data-gadget-scope="devtools"
data-gadget-sandbox="public">
</div>
</body>
......
......@@ -31,33 +31,46 @@
rJS(window)
.declareMethod("convert", function(html) {
var gadget = this;
var browser;
var page;
var devtools;
var targetId;
var frameId;
var base64data;
return gadget.getDeclaredGadget("browser")
/**
* Steps:
*
* // new page setup
* Target.createTarget();
* Target.attachToTarget();
* Target.activateTarget();
*
* // enable events
* Network.enable();
* Page.enable();
*
* // change content
* Page.frameAttached();
* Page.setDocumentContent();
* Page.loadEventFired(); // Is this event fired once loading starts or stops?
*
* // convert html to pdf
* Page.printToPDF();
*
* // cleanup
* Target.detachFromTarget();
* Target.closeTarget();
*/
return gadget.getDeclaredGadget("devtools")
.push(function (subgadget) {
browser = subgadget;
return browser.openNewPage("http://www.example.com");
return subgadget.getDevtools();
})
.push(function (result) {
page = result;
return browser.setPageContent(page, html);
})
.push(function () {
return browser.getVersion();
})
.push(function () {
return browser.enablePage(page);
})
.push(function () {
return browser.printToPdf(page, optionsForPrintToPdf);
devtools = result;
return devtools.Browser.getVersion();
})
.push(function (result) {
base64data = result;
return browser.closePage(page);
})
.push(function () {
return base64data;
console.log(result);
})
})
}(window, rJS));
\ No newline at end of file
......@@ -5,52 +5,6 @@ var BROWSER_PASSWORD = "ignored";
(function(window, rJS, RSVP) {
"use strict";
/**
* The proxy to a specific browser target. Each proxy makes a connection
* to the targets websocket and handles the outgoing and incoming messages.
* It also automatically reconnects on network errors.
*
* TODO: maybe make this work with async or RSVP?
*/
class TargetProxy
{
constructor (path, targetId) {
this.targetId = this.targetId;
this.path = path;
this.websocket = null;
this.messages = [];
}
connect (callback) {
this.disconnect();
this.websocket = new WebSocket(this.path);
this.websocket.onopen = callback;
this.websocket.onmessage = this.messages.push;
this.websocket.onerror = (error) => {
console.error(error);
this.connect(callback);
};
}
disconnect () {
if (this.websocket) {
this.websocket.close();
this.websocket = null;
};
}
clearMessages () {
this.messages = [];
}
sendCommand (command, params) {
this.clearMessages();
var message = {id: 0, method: command, params: params};
this.websocket.send(JSON.stringify(message));
}
waitMessage (response) {
// TODO: implement this function
}
debug () {
console.log(this.messages);
}
};
/**
* Makes an HTTP GET request to the browser to retrieve some JSON data.
*
......@@ -72,46 +26,9 @@ var BROWSER_PASSWORD = "ignored";
request.send();
}
return RSVP.Queue().push(function () {
return new RSVP.Promise(callback);
return RSVP.Promise(callback);
});
}
/**
* Adds the method API from the method JSON data to the object.
*
* @param {object} object The container for the method.
* @param {object} method The method data from the protocol.
*/
function addMethodAPI (object, method) {
var callback = (proxy, params) => {
return;
proxy.clearMessages();
proxy.sendCommand(`${object.domain}.${method.name}`, params);
}
//callback["description"] = method.description;
//callback["experimental"] = method.experimental ? true : false;
//callback["type"] = "method";
object[method.name] = callback;
}
/**
* Adds the event API from the event JSON data to the object.
*
* @param {object} object The container for the event.
* @param {object} event The event data from the protocol.
*/
function addEventAPI (object, event) {
// todo
}
/**
* Adds the type API from the type JSON data to the object.
*
* @param {object} object The container for the type.
* @param {object} type The type data from the protocol.
*/
function addTypeAPI (object, type) {
// todo
}
/**
* Returns the devtools API for the given domain. Domains are defined
* by the devtools protocol, e.g. Page. Each domain defines multiple
......@@ -120,27 +37,75 @@ var BROWSER_PASSWORD = "ignored";
* @param {object} domain The domain data from the devtools API.
* @returns The promise for the devtools API object and its name.
*/
function getDevtoolsAPI (domain) {
function getDevtoolsAPI (domain) {
var callback = (resolve) => {
var object = {};
//object["domain"] = domain.domain;
//object["description"] = domain.description;
//object["experimental"] = domain.experimental ? true : false;
(domain.commands || []).forEach((method) => {
addMethodAPI(object, method);
addMethodAPI(object, domain, method);
});
(domain.events || []).forEach((event) => {
addEventAPI(object, event);
});
(domain.types || []).forEach((type) => {
addTypeAPI(object, type);
addEventAPI(object, domain, event);
});
resolve([domain.domain, object]);
}
return RSVP.Queue().push(function () {
return new RSVP.Promise(callback);
return RSVP.Promise(callback);
});
}
/**
* Adds the method API from the method JSON data to the object.
*
* @param {object} object The container for the method.
* @param {object} domain The domain data from the protocol.
* @param {object} method The method data from the protocol.
*/
function addMethodAPI (object, domain, method) {
var result = (gadget, params) => {
var callback = (resolve, reject) => {
var sessionId = gadget.state.sessionId;
var command = {
sessionId,
id: gadget.state.nextIndex,
method: `${domain.domain}.${method.name}`,
params: params,
}
var message = JSON.stringify(command);
gadget.state.websocket.send(message);
var handler = {
id: gadget.state.nextIndex,
result: resolve,
error: reject
}
gadget.state.handlers.add(handler);
}
return RSVP.Queue().push(function () {
return RSVP.Promise(callback);
});
}
object[method.name] = result;
}
/**
* Adds the event API from the method JSON data to the object.
*
* @param {object} object The container for the method.
* @param {object} domain The domain data from the protocol.
* @param {object} method The method data from the protocol.
*/
function addEventAPI (object, domain, method) {
var result = (gadget, params) => {
var callback = (resolve, reject) => {
// todo: implement this method
resolve(null);
}
return RSVP.Queue().push(function () {
return RSVP.Promise(callback);
});
}
object[method.name] = result;
}
/**
* Wraps the devtools API inside the gadget state which handels the
* connection with specific targets.
......@@ -148,49 +113,98 @@ var BROWSER_PASSWORD = "ignored";
* @param {*} gadget
* @param {*} devtoolsAPI
*/
function getWrappedDevtoolsAPI (gadget, devtoolsAPI) {
var callback = (resolve) => {
// "map(partial(partial, proxy = gadget.proxy), leafs(devtoolsAPI))"
function updateDevtools (gadget, devtoolsAPI) {
var callback = () => {
for (let [domain, methods] of Object.entries(devtoolsAPI)) {
for (let [method, callback] of Object.entries(methods)) {
devtoolsAPI[domain][method] = (params) => {
callback(gadget.proxy, params);
return callback(gadget, params);
};
}
}
// TODO: special case for targets to make the abstraction work:
// TODO: attachTarget/detachFromTarget -> add/remove from target list
// TODO: activateTarget -> change current proxy target
console.log(devtoolsAPI);
resolve(devtoolsAPI);
let attachToTarget = devtoolsAPI.Target.attachToTarget;
devtoolsAPI.Target.attachToTarget = (params) => {
var response;
return attachToTarget(params)
.push(function (result) {
response = result;
return gadget.changeState({sessionId: response.sessionId});
})
.push(function () {
return response;
})
}
let detachFromTarget = devtoolsAPI.Target.detachFromTarget;
devtoolsAPI.Target.detachFromTarget = (params) => {
var response;
return detachFromTarget(params)
.push(function (result) {
response = result;
return gadget.changeState({sessionId: undefined});
})
.push(function () {
return response;
})
}
return gadget.changeState({devtools: devtoolsAPI});
}
return RSVP.Queue().push(callback);
}
/**
* Creates a new websocket that puts messages into the gadget's state.
* It also automatically reconnects on network errors.
*
* @param {object} gadget The gadget that should receive messages.
* @param {string} websocketUrl The url of the websocket.
*/
function updateWebsocket (gadget, websocketUrl) {
var callback = (resolve) => {
var websocket = new WebSocket(websocketUrl);
websocket.onopen = resolve;
websocket.onmessage = (message) => {
gadget.state.messages.add(JSON.parse(message.data));
gadget.changeState({nextIndex: gadget.state.nextIndex + 1});
}
websocket.onerror = (error) => {
console.log(error);
websocket.close();
updateWebsocket(gadget, websocketUrl);
}
gadget.changeState({websocket: websocket});
}
return RSVP.Queue().push(function () {
return new RSVP.Promise(callback);
return RSVP.Promise(callback);
});
}
rJS(window)
.setState({
browser: null,
targets: [],
proxy: null,
websocket: null,
sessionId: undefined,
nextIndex: 0,
handlers: null,
messages: null,
devtools: {},
})
.declareService(function () {
.ready(function () {
var gadget = this;
return getBrowserJSON("json/protocol")
gadget.state.handlers = new Set();
gadget.state.messages = new Set();
return getBrowserJSON("json/version")
.push(function (result) {
return updateWebsocket(gadget, result.webSocketDebuggerUrl);
})
.push(function () {
return getBrowserJSON("json/protocol")
})
.push(function (result) {
var promises = result.domains.map(getDevtoolsAPI);
return RSVP.all(promises);
})
.push(function (result) {
var devtools = devtools = Object.fromEntries(result);
return getWrappedDevtoolsAPI(gadget, devtools);
return updateDevtools(gadget, devtools);
})
.push(function (result) {
return gadget.changeState({devtools: result});
})
})
.declareMethod("getDevtools", function () {
var gadget = this;
......@@ -198,8 +212,35 @@ var BROWSER_PASSWORD = "ignored";
return gadget.state.devtools;
})
})
.onStateChange(function (ignored) {
.declareMethod("clearMessages", function () {
var gadget = this;
console.log(gadget.state.devtoolsAPI);
gadget.state.messages = [];
})
.onStateChange(function (modifications) {
var gadget = this;
/*
* This is O(n * m) with n = messages.size and m = handlers.size.
*
* I think it is fine since new messages should complete all handlers,
* which means m stays very small. We also have messages from events,
* but there are only finite amount, so n should stay small aswell.
*
* This can be implemented much faster if performance becomes an issue.
*/
for (let i = gadget.state.handlers.values(), handler = null; handler = i.next().value;) {
for (let j = gadget.state.messages.values(), message = null; message = j.next().value;) {
if (handler.id != message.id) {
continue;
}
gadget.state.handlers.delete(handler);
gadget.state.messages.delete(message);
if ('error' in message) {
handler.error(message.error);
} else {
handler.result(message.result)
}
}
}
})
}(window, rJS, RSVP));
\ No newline at end of file
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment