зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1223907: Refactor cookies in Marionette
Moves most of the cookie implementation to a new file, testing/marionette/cookies.js. The new Cookies class encapsulates all APIs for manipulating and querying cookies from content space. It communicates with chrome space, where the cookie manager lives, through a new SyncContentSender provided by testing/marionette/proxy.js. This new interface provides synchronous and transparent communication from content to chrome, not dissimilar from how the original listener proxy works. r=dburns --HG-- extra : commitid : 2pWjZbTRPR9 extra : rebase_source : 35cdfe2ee552a56f4e6266afa1363fc40ae623e4
This commit is contained in:
Родитель
903bae3946
Коммит
eb0c7225bb
|
@ -0,0 +1,131 @@
|
|||
/* 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 {classes: Cc, interfaces: Ci, utils: Cu} = Components;
|
||||
|
||||
Cu.import("resource://gre/modules/Log.jsm");
|
||||
Cu.import("chrome://marionette/content/error.js");
|
||||
|
||||
const logger = Log.repository.getLogger("Marionette");
|
||||
|
||||
this.EXPORTED_SYMBOLS = ["Cookies"];
|
||||
|
||||
const IPV4_PORT_EXPR = /:\d+$/;
|
||||
|
||||
/**
|
||||
* Interface for manipulating cookies from content space.
|
||||
*/
|
||||
this.Cookies = class {
|
||||
|
||||
/**
|
||||
* @param {function(): Document} documentFn
|
||||
* Closure that returns the current content document.
|
||||
* @param {Proxy(SyncChromeSender)} chromeProxy
|
||||
* A synchronous proxy interface to chrome space.
|
||||
*/
|
||||
constructor(documentFn, chromeProxy) {
|
||||
this.documentFn_ = documentFn;
|
||||
this.chrome = chromeProxy;
|
||||
}
|
||||
|
||||
get document() {
|
||||
return this.documentFn_();
|
||||
}
|
||||
|
||||
[Symbol.iterator]() {
|
||||
let path = this.document.location.pathname || "/";
|
||||
let cs = this.chrome.getVisibleCookies(path, this.document.location.hostname)[0];
|
||||
return cs[Symbol.iterator]();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new cookie to a content document.
|
||||
*
|
||||
* @param {string} name
|
||||
* Cookie key.
|
||||
* @param {string} value
|
||||
* Cookie value.
|
||||
* @param {Object.<string, ?>} opts
|
||||
* An object with the optional fields {@code domain}, {@code path},
|
||||
* {@code secure}, {@code httpOnly}, and {@code expiry}.
|
||||
*
|
||||
* @return {Object.<string, ?>}
|
||||
* A serialisation of the cookie that was added.
|
||||
*
|
||||
* @throws UnableToSetCookieError
|
||||
* If the document's content type isn't HTML, the current document's
|
||||
* domain is a mismatch to the cookie's provided domain, or there
|
||||
* otherwise was issues with the input data.
|
||||
*/
|
||||
add(name, value, opts={}) {
|
||||
if (typeof this.document == "undefined" || !this.document.contentType.match(/html/i)) {
|
||||
throw new UnableToSetCookieError(
|
||||
"You may only set cookies on HTML documents: " + this.document.contentType);
|
||||
}
|
||||
|
||||
if (!opts.expiry) {
|
||||
// date twenty years into future, in seconds
|
||||
let date = new Date();
|
||||
let now = new Date(Date.now());
|
||||
date.setYear(now.getFullYear() + 20);
|
||||
opts.expiry = date.getTime() / 1000;
|
||||
}
|
||||
|
||||
if (!opts.domain) {
|
||||
opts.domain = this.document.location.host;
|
||||
} else if (this.document.location.host.indexOf(opts.domain) < 0) {
|
||||
throw new InvalidCookieDomainError(
|
||||
"You may only set cookies for the current domain");
|
||||
}
|
||||
|
||||
// remove port from domain, if present.
|
||||
// unfortunately this catches IPv6 addresses by mistake
|
||||
// TODO: Bug 814416
|
||||
opts.domain = opts.domain.replace(IPV4_PORT_EXPR, "");
|
||||
|
||||
let cookie = {
|
||||
domain: opts.domain,
|
||||
path: opts.path,
|
||||
name: name,
|
||||
value: value,
|
||||
secure: opts.secure,
|
||||
httpOnly: opts.httpOnly,
|
||||
session: false,
|
||||
expiry: opts.expiry,
|
||||
};
|
||||
if (!this.chrome.addCookie(cookie)) {
|
||||
throw new UnableToSetCookieError();
|
||||
}
|
||||
|
||||
return cookie;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete cookie by reference or by name.
|
||||
*
|
||||
* @param {(string|Object.<string, ?>)} cookie
|
||||
* Name of cookie or cookie object.
|
||||
*
|
||||
* @throws {UnknownError}
|
||||
* If unable to delete the cookie.
|
||||
*/
|
||||
delete(cookie) {
|
||||
let name;
|
||||
if (cookie.hasOwnProperty("name")) {
|
||||
name = cookie.name;
|
||||
} else {
|
||||
name = cookie;
|
||||
}
|
||||
|
||||
for (let candidate of this) {
|
||||
if (candidate.name == name) {
|
||||
if (!this.chrome.deleteCookie(candidate)) {
|
||||
throw new UnknownError("Unable to delete cookie by name: " + name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
|
@ -2291,7 +2291,22 @@ GeckoDriver.prototype.switchToShadowRoot = function(cmd, resp) {
|
|||
|
||||
/** Add a cookie to the document. */
|
||||
GeckoDriver.prototype.addCookie = function(cmd, resp) {
|
||||
yield this.listener.addCookie({cookie: cmd.parameters.cookie});
|
||||
let cb = msg => {
|
||||
this.mm.removeMessageListener("Marionette:addCookie", cb);
|
||||
let cookie = msg.json;
|
||||
Services.cookies.add(
|
||||
cookie.domain,
|
||||
cookie.path,
|
||||
cookie.name,
|
||||
cookie.value,
|
||||
cookie.secure,
|
||||
cookie.httpOnly,
|
||||
cookie.session,
|
||||
cookie.expiry);
|
||||
return true;
|
||||
};
|
||||
this.mm.addMessageListener("Marionette:addCookie", cb);
|
||||
yield this.listener.addCookie(cmd.parameters.cookie);
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -2306,12 +2321,34 @@ GeckoDriver.prototype.getCookies = function(cmd, resp) {
|
|||
|
||||
/** Delete all cookies that are visible to a document. */
|
||||
GeckoDriver.prototype.deleteAllCookies = function(cmd, resp) {
|
||||
let cb = msg => {
|
||||
let cookie = msg.json;
|
||||
cookieManager.remove(
|
||||
cookie.host,
|
||||
cookie.name,
|
||||
cookie.path,
|
||||
false);
|
||||
return true;
|
||||
};
|
||||
this.mm.addMessageListener("Marionette:deleteCookie", cb);
|
||||
yield this.listener.deleteAllCookies();
|
||||
this.mm.removeMessageListener("Marionette:deleteCookie", cb);
|
||||
};
|
||||
|
||||
/** Delete a cookie by name. */
|
||||
GeckoDriver.prototype.deleteCookie = function(cmd, resp) {
|
||||
yield this.listener.deleteCookie({name: cmd.parameters.name});
|
||||
let cb = msg => {
|
||||
this.mm.removeMessageListener("Marionette:deleteCookie", cb);
|
||||
let cookie = msg.json;
|
||||
cookieManager.remove(
|
||||
cookie.host,
|
||||
cookie.name,
|
||||
cookie.path,
|
||||
false);
|
||||
return true;
|
||||
};
|
||||
this.mm.addMessageListener("Marionette:deleteCookie", cb);
|
||||
yield this.listener.deleteCookie(cmd.parameters.name);
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -2821,7 +2858,7 @@ GeckoDriver.prototype.receiveMessage = function(message) {
|
|||
break;
|
||||
|
||||
case "Marionette:getVisibleCookies":
|
||||
let [currentPath, host] = message.json.value;
|
||||
let [currentPath, host] = message.json;
|
||||
let isForCurrentPath = path => currentPath.indexOf(path) != -1;
|
||||
let results = [];
|
||||
|
||||
|
@ -2849,28 +2886,6 @@ GeckoDriver.prototype.receiveMessage = function(message) {
|
|||
}
|
||||
return results;
|
||||
|
||||
case "Marionette:addCookie":
|
||||
let cookieToAdd = message.json.value;
|
||||
Services.cookies.add(
|
||||
cookieToAdd.domain,
|
||||
cookieToAdd.path,
|
||||
cookieToAdd.name,
|
||||
cookieToAdd.value,
|
||||
cookieToAdd.secure,
|
||||
cookieToAdd.httpOnly,
|
||||
false,
|
||||
cookieToAdd.expiry);
|
||||
return true;
|
||||
|
||||
case "Marionette:deleteCookie":
|
||||
let cookieToDelete = message.json.value;
|
||||
cookieManager.remove(
|
||||
cookieToDelete.host,
|
||||
cookieToDelete.name,
|
||||
cookieToDelete.path,
|
||||
false);
|
||||
return true;
|
||||
|
||||
case "Marionette:getFiles":
|
||||
// Generates file objects to send back to the content script
|
||||
// for handling file uploads.
|
||||
|
|
|
@ -31,33 +31,8 @@ const errors = [
|
|||
|
||||
this.EXPORTED_SYMBOLS = ["error"].concat(errors);
|
||||
|
||||
// Because XPCOM is a cesspool of undocumented odd behaviour,
|
||||
// Object.getPrototypeOf(err) causes another exception if err is an XPCOM
|
||||
// exception, and cannot be used to determine if err is a prototypal Error.
|
||||
//
|
||||
// Consequently we need to check for properties in its prototypal chain
|
||||
// (using in, instead of err.hasOwnProperty because that causes other
|
||||
// issues).
|
||||
//
|
||||
// Since the input is arbitrary it might _not_ be an Error, and can as
|
||||
// such be an object with a "result" property without it being considered to
|
||||
// be an exception. The solution is to build a lookup table of XPCOM
|
||||
// exceptions from Components.results and check if the value of err#results
|
||||
// is in that table.
|
||||
const XPCOM_EXCEPTIONS = [];
|
||||
{
|
||||
for (let prop in Cr) {
|
||||
XPCOM_EXCEPTIONS.push(Cr[prop]);
|
||||
}
|
||||
}
|
||||
|
||||
this.error = {};
|
||||
|
||||
/**
|
||||
* Determines if the given status is successful.
|
||||
*/
|
||||
error.isSuccess = status => status === "success";
|
||||
|
||||
/**
|
||||
* Checks if obj is an instance of the Error prototype in a safe manner.
|
||||
* Prefer using this over using instanceof since the Error prototype
|
||||
|
@ -72,7 +47,7 @@ error.isSuccess = status => status === "success";
|
|||
error.isError = function(val) {
|
||||
if (val === null || typeof val != "object") {
|
||||
return false;
|
||||
} else if ("result" in val && val.result in XPCOM_EXCEPTIONS) {
|
||||
} else if ("result" in val) {
|
||||
return true;
|
||||
} else {
|
||||
return Object.getPrototypeOf(val) == "Error";
|
||||
|
|
|
@ -187,9 +187,7 @@ FrameManager.prototype = {
|
|||
mm.addWeakMessageListener("Marionette:shareData", this.server);
|
||||
mm.addWeakMessageListener("Marionette:switchToModalOrigin", this.server);
|
||||
mm.addWeakMessageListener("Marionette:switchedToFrame", this.server);
|
||||
mm.addWeakMessageListener("Marionette:addCookie", this.server);
|
||||
mm.addWeakMessageListener("Marionette:getVisibleCookies", this.server);
|
||||
mm.addWeakMessageListener("Marionette:deleteCookie", this.server);
|
||||
mm.addWeakMessageListener("Marionette:register", this.server);
|
||||
mm.addWeakMessageListener("Marionette:listenersAttached", this.server);
|
||||
mm.addWeakMessageListener("Marionette:getFiles", this.server);
|
||||
|
@ -216,9 +214,7 @@ FrameManager.prototype = {
|
|||
mm.removeWeakMessageListener("Marionette:runEmulatorCmd", this.server);
|
||||
mm.removeWeakMessageListener("Marionette:runEmulatorShell", this.server);
|
||||
mm.removeWeakMessageListener("Marionette:switchedToFrame", this.server);
|
||||
mm.removeWeakMessageListener("Marionette:addCookie", this.server);
|
||||
mm.removeWeakMessageListener("Marionette:getVisibleCookies", this.server);
|
||||
mm.removeWeakMessageListener("Marionette:deleteCookie", this.server);
|
||||
mm.removeWeakMessageListener("Marionette:listenersAttached", this.server);
|
||||
mm.removeWeakMessageListener("Marionette:register", this.server);
|
||||
mm.removeWeakMessageListener("Marionette:getFiles", this.server);
|
||||
|
|
|
@ -22,6 +22,7 @@ marionette.jar:
|
|||
content/modal.js (modal.js)
|
||||
content/proxy.js (proxy.js)
|
||||
content/capture.js (capture.js)
|
||||
content/cookies.js (cookies.js)
|
||||
#ifdef ENABLE_TESTS
|
||||
content/test.xul (client/marionette/chrome/test.xul)
|
||||
content/test2.xul (client/marionette/chrome/test2.xul)
|
||||
|
|
|
@ -14,8 +14,11 @@ loader.loadSubScript("chrome://marionette/content/simpletest.js");
|
|||
loader.loadSubScript("chrome://marionette/content/common.js");
|
||||
loader.loadSubScript("chrome://marionette/content/actions.js");
|
||||
Cu.import("chrome://marionette/content/capture.js");
|
||||
Cu.import("chrome://marionette/content/cookies.js");
|
||||
Cu.import("chrome://marionette/content/elements.js");
|
||||
Cu.import("chrome://marionette/content/error.js");
|
||||
Cu.import("chrome://marionette/content/proxy.js");
|
||||
|
||||
Cu.import("resource://gre/modules/FileUtils.jsm");
|
||||
Cu.import("resource://gre/modules/NetUtil.jsm");
|
||||
Cu.import("resource://gre/modules/Task.jsm");
|
||||
|
@ -77,6 +80,9 @@ var EVENT_INTERVAL = 30; // milliseconds
|
|||
// last touch for each fingerId
|
||||
var multiLast = {};
|
||||
|
||||
var chrome = proxy.toChrome(sendSyncMessage.bind(this));
|
||||
var cookies = new Cookies(() => curContainer.frame.document, chrome);
|
||||
|
||||
Cu.import("resource://gre/modules/Log.jsm");
|
||||
var logger = Log.repository.getLogger("Marionette");
|
||||
logger.info("loaded listener.js");
|
||||
|
@ -217,6 +223,9 @@ var switchToShadowRootFn = dispatch(switchToShadowRoot);
|
|||
var getCookiesFn = dispatch(getCookies);
|
||||
var singleTapFn = dispatch(singleTap);
|
||||
var takeScreenshotFn = dispatch(takeScreenshot);
|
||||
var addCookieFn = dispatch(addCookie);
|
||||
var deleteCookieFn = dispatch(deleteCookie);
|
||||
var deleteAllCookiesFn = dispatch(deleteAllCookies);
|
||||
|
||||
/**
|
||||
* Start all message listeners
|
||||
|
@ -263,10 +272,10 @@ function startListeners() {
|
|||
addMessageListenerId("Marionette:getAppCacheStatus", getAppCacheStatus);
|
||||
addMessageListenerId("Marionette:setTestName", setTestName);
|
||||
addMessageListenerId("Marionette:takeScreenshot", takeScreenshotFn);
|
||||
addMessageListenerId("Marionette:addCookie", addCookie);
|
||||
addMessageListenerId("Marionette:addCookie", addCookieFn);
|
||||
addMessageListenerId("Marionette:getCookies", getCookiesFn);
|
||||
addMessageListenerId("Marionette:deleteAllCookies", deleteAllCookies);
|
||||
addMessageListenerId("Marionette:deleteCookie", deleteCookie);
|
||||
addMessageListenerId("Marionette:deleteAllCookies", deleteAllCookiesFn);
|
||||
addMessageListenerId("Marionette:deleteCookie", deleteCookieFn);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -368,10 +377,10 @@ function deleteSession(msg) {
|
|||
removeMessageListenerId("Marionette:getAppCacheStatus", getAppCacheStatus);
|
||||
removeMessageListenerId("Marionette:setTestName", setTestName);
|
||||
removeMessageListenerId("Marionette:takeScreenshot", takeScreenshotFn);
|
||||
removeMessageListenerId("Marionette:addCookie", addCookie);
|
||||
removeMessageListenerId("Marionette:addCookie", addCookieFn);
|
||||
removeMessageListenerId("Marionette:getCookies", getCookiesFn);
|
||||
removeMessageListenerId("Marionette:deleteAllCookies", deleteAllCookies);
|
||||
removeMessageListenerId("Marionette:deleteCookie", deleteCookie);
|
||||
removeMessageListenerId("Marionette:deleteAllCookies", deleteAllCookiesFn);
|
||||
removeMessageListenerId("Marionette:deleteCookie", deleteCookieFn);
|
||||
if (isB2G) {
|
||||
content.removeEventListener("mozbrowsershowmodalprompt", modalHandler, false);
|
||||
}
|
||||
|
@ -1837,47 +1846,9 @@ function switchToFrame(msg) {
|
|||
|
||||
sendResponse({value: rv}, command_id);
|
||||
}
|
||||
/**
|
||||
* Add a cookie to the document
|
||||
*/
|
||||
function addCookie(msg) {
|
||||
let cookie = msg.json.cookie;
|
||||
if (!cookie.expiry) {
|
||||
var date = new Date();
|
||||
var thePresent = new Date(Date.now());
|
||||
date.setYear(thePresent.getFullYear() + 20);
|
||||
cookie.expiry = date.getTime() / 1000; // Stored in seconds.
|
||||
}
|
||||
|
||||
if (!cookie.domain) {
|
||||
var location = curContainer.frame.document.location;
|
||||
cookie.domain = location.hostname;
|
||||
} else {
|
||||
var currLocation = curContainer.frame.location;
|
||||
var currDomain = currLocation.host;
|
||||
if (currDomain.indexOf(cookie.domain) == -1) {
|
||||
sendError(new InvalidCookieDomainError("You may only set cookies for the current domain"), msg.json.command_id);
|
||||
}
|
||||
}
|
||||
|
||||
// The cookie's domain may include a port. Which is bad. Remove it
|
||||
// We'll catch ip6 addresses by mistake. Since no-one uses those
|
||||
// this will be okay for now. See Bug 814416
|
||||
if (cookie.domain.match(/:\d+$/)) {
|
||||
cookie.domain = cookie.domain.replace(/:\d+$/, '');
|
||||
}
|
||||
|
||||
var document = curContainer.frame.document;
|
||||
if (!document || !document.contentType.match(/html/i)) {
|
||||
sendError(new UnableToSetCookieError("You may only set cookies on html documents"), msg.json.command_id);
|
||||
}
|
||||
|
||||
let added = sendSyncMessage("Marionette:addCookie", {value: cookie});
|
||||
if (added[0] !== true) {
|
||||
sendError(new UnableToSetCookieError(), msg.json.command_id);
|
||||
return;
|
||||
}
|
||||
sendOk(msg.json.command_id);
|
||||
function addCookie(cookie) {
|
||||
cookies.add(cookie.name, cookie.value, cookie);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1885,7 +1856,6 @@ function addCookie(msg) {
|
|||
*/
|
||||
function getCookies() {
|
||||
let rv = [];
|
||||
let cookies = getVisibleCookies(curContainer.frame.location);
|
||||
|
||||
for (let cookie of cookies) {
|
||||
let expires = cookie.expires;
|
||||
|
@ -1911,47 +1881,19 @@ function getCookies() {
|
|||
}
|
||||
|
||||
/**
|
||||
* Delete a cookie by name
|
||||
* Delete a cookie by name.
|
||||
*/
|
||||
function deleteCookie(msg) {
|
||||
let toDelete = msg.json.name;
|
||||
let cookies = getVisibleCookies(curContainer.frame.location);
|
||||
for (let cookie of cookies) {
|
||||
if (cookie.name == toDelete) {
|
||||
let deleted = sendSyncMessage("Marionette:deleteCookie", {value: cookie});
|
||||
if (deleted[0] !== true) {
|
||||
sendError(new UnknownError("Could not delete cookie: " + msg.json.name), msg.json.command_id);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sendOk(msg.json.command_id);
|
||||
function deleteCookie(name) {
|
||||
cookies.delete(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all the visibile cookies on a page
|
||||
* Delete all the visibile cookies on a page.
|
||||
*/
|
||||
function deleteAllCookies(msg) {
|
||||
let cookies = getVisibleCookies(curContainer.frame.location);
|
||||
function deleteAllCookies() {
|
||||
for (let cookie of cookies) {
|
||||
let deleted = sendSyncMessage("Marionette:deleteCookie", {value: cookie});
|
||||
if (!deleted[0]) {
|
||||
sendError(new UnknownError("Could not delete cookie: " + JSON.stringify(cookie)), msg.json.command_id);
|
||||
return;
|
||||
}
|
||||
cookies.delete(cookie);
|
||||
}
|
||||
sendOk(msg.json.command_id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all the visible cookies from a location
|
||||
*/
|
||||
function getVisibleCookies(location) {
|
||||
let currentPath = location.pathname || '/';
|
||||
let result = sendSyncMessage("Marionette:getVisibleCookies",
|
||||
{value: [currentPath, location.hostname]});
|
||||
return result[0];
|
||||
}
|
||||
|
||||
function getAppCacheStatus(msg) {
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
"use strict";
|
||||
|
||||
var {classes: Cc, interfaces: Ci, utils: Cu} = Components;
|
||||
const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
|
||||
|
||||
Cu.import("resource://gre/modules/Log.jsm");
|
||||
Cu.import("chrome://marionette/content/modal.js");
|
||||
|
@ -18,11 +18,22 @@ const MARIONETTE_ERROR = "Marionette:error";
|
|||
const logger = Log.repository.getLogger("Marionette");
|
||||
const uuidgen = Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUIDGenerator);
|
||||
|
||||
// Proxy handler that traps requests to get a property. Will prioritise
|
||||
// properties that exist on the object's own prototype.
|
||||
var ownPriorityGetterTrap = {
|
||||
get: (obj, prop) => {
|
||||
if (obj.hasOwnProperty(prop)) {
|
||||
return obj[prop];
|
||||
}
|
||||
return (...args) => obj.send(prop, args);
|
||||
}
|
||||
};
|
||||
|
||||
this.proxy = {};
|
||||
|
||||
/**
|
||||
* Creates a transparent interface between the chrome- and content
|
||||
* processes.
|
||||
* contexts.
|
||||
*
|
||||
* Calls to this object will be proxied via the message manager to the active
|
||||
* browsing context (content) and responses will be provided back as
|
||||
|
@ -39,20 +50,12 @@ this.proxy = {};
|
|||
* Callback for sending async messages to the current listener.
|
||||
*/
|
||||
proxy.toListener = function(mmFn, sendAsyncFn) {
|
||||
let sender = new ContentSender(mmFn, sendAsyncFn);
|
||||
let handler = {
|
||||
get: (obj, prop) => {
|
||||
if (obj.hasOwnProperty(prop)) {
|
||||
return obj[prop];
|
||||
}
|
||||
return (...args) => obj.send(prop, args);
|
||||
}
|
||||
};
|
||||
return new Proxy(sender, handler);
|
||||
let sender = new AsyncContentSender(mmFn, sendAsyncFn);
|
||||
return new Proxy(sender, ownPriorityGetterTrap);
|
||||
};
|
||||
|
||||
/**
|
||||
* The ContentSender allows one to make synchronous calls to the
|
||||
* The AsyncContentSender allows one to make synchronous calls to the
|
||||
* message listener of the content frame of the current browsing context.
|
||||
*
|
||||
* Presumptions about the responses from content space are made so we
|
||||
|
@ -62,103 +65,138 @@ proxy.toListener = function(mmFn, sendAsyncFn) {
|
|||
*
|
||||
* The promise is guaranteed not to resolve until the execution of the
|
||||
* command in content space is complete.
|
||||
*
|
||||
* @param {function(): (nsIMessageSender|nsIMessageBroadcaster)} mmFn
|
||||
* Function returning the current message manager.
|
||||
* @param {function(string, Object, number)} sendAsyncFn
|
||||
* Callback for sending async messages to the current listener.
|
||||
*/
|
||||
var ContentSender = function(mmFn, sendAsyncFn) {
|
||||
this.curId = null;
|
||||
this.sendAsync = sendAsyncFn;
|
||||
this.mmFn_ = mmFn;
|
||||
this._listeners = [];
|
||||
};
|
||||
|
||||
Object.defineProperty(ContentSender.prototype, "mm", {
|
||||
get: function() { return this.mmFn_(); }
|
||||
});
|
||||
|
||||
ContentSender.prototype.removeListeners = function () {
|
||||
this._listeners.map(l => this.mm.removeMessageListener(l[0], l[1]));
|
||||
this._listeners = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Call registered function in the frame script environment of the
|
||||
* current browsing context's content frame.
|
||||
*
|
||||
* @param {string} name
|
||||
* Function to call in the listener, e.g. for "Marionette:foo8",
|
||||
* use "foo".
|
||||
* @param {Array} args
|
||||
* Argument list to pass the function. If args has a single entry
|
||||
* that is an object, we assume it's an old style dispatch, and
|
||||
* the object will passed literally.
|
||||
*
|
||||
* @return {Promise}
|
||||
* A promise that resolves to the result of the command.
|
||||
*/
|
||||
ContentSender.prototype.send = function(name, args) {
|
||||
if (this._listeners[0]) {
|
||||
// A prior (probably timed-out) request has left listeners behind.
|
||||
// Remove them before proceeding.
|
||||
logger.warn("A previous failed command left content listeners behind!");
|
||||
this.removeListeners();
|
||||
this.AsyncContentSender = class {
|
||||
constructor(mmFn, sendAsyncFn) {
|
||||
this.curId = null;
|
||||
this.sendAsync = sendAsyncFn;
|
||||
this.mmFn_ = mmFn;
|
||||
this._listeners = [];
|
||||
}
|
||||
|
||||
this.curId = uuidgen.generateUUID().toString();
|
||||
get mm() {
|
||||
return this.mmFn_();
|
||||
}
|
||||
|
||||
let proxy = new Promise((resolve, reject) => {
|
||||
let removeListeners = (n, fn) => {
|
||||
let rmFn = msg => {
|
||||
if (this.curId !== msg.json.command_id) {
|
||||
logger.warn("Skipping out-of-sync response from listener: " +
|
||||
`Expected response to ${name} with ID ${this.curId}, ` +
|
||||
"but got: " + msg.name + msg.json.toSource());
|
||||
return;
|
||||
}
|
||||
removeListeners() {
|
||||
this._listeners.map(l => this.mm.removeMessageListener(l[0], l[1]));
|
||||
this._listeners = [];
|
||||
}
|
||||
|
||||
this.removeListeners();
|
||||
modal.removeHandler(handleDialog);
|
||||
|
||||
fn(msg);
|
||||
this.curId = null;
|
||||
};
|
||||
|
||||
this._listeners.push([n, rmFn]);
|
||||
return rmFn;
|
||||
};
|
||||
|
||||
let okListener = () => resolve();
|
||||
let valListener = msg => resolve(msg.json.value);
|
||||
let errListener = msg => reject(msg.objects.error);
|
||||
|
||||
let handleDialog = (subject, topic) => {
|
||||
this.removeListeners()
|
||||
modal.removeHandler(handleDialog);
|
||||
this.sendAsync("cancelRequest");
|
||||
resolve();
|
||||
};
|
||||
|
||||
// start content process listeners, and install observers for global-
|
||||
// and tab modal dialogues
|
||||
this.mm.addMessageListener(MARIONETTE_OK, removeListeners(MARIONETTE_OK, okListener));
|
||||
this.mm.addMessageListener(MARIONETTE_DONE, removeListeners(MARIONETTE_DONE, valListener));
|
||||
this.mm.addMessageListener(MARIONETTE_ERROR, removeListeners(MARIONETTE_ERROR, errListener));
|
||||
modal.addHandler(handleDialog);
|
||||
|
||||
// new style dispatches are arrays of arguments, old style dispatches
|
||||
// are key-value objects
|
||||
let msg = args;
|
||||
if (args.length == 1 && typeof args[0] == "object") {
|
||||
msg = args[0];
|
||||
/**
|
||||
* Call registered function in the frame script environment of the
|
||||
* current browsing context's content frame.
|
||||
*
|
||||
* @param {string} name
|
||||
* Function to call in the listener, e.g. for "Marionette:foo8",
|
||||
* use "foo".
|
||||
* @param {Array} args
|
||||
* Argument list to pass the function. If args has a single entry
|
||||
* that is an object, we assume it's an old style dispatch, and
|
||||
* the object will passed literally.
|
||||
*
|
||||
* @return {Promise}
|
||||
* A promise that resolves to the result of the command.
|
||||
*/
|
||||
send(name, args) {
|
||||
if (this._listeners[0]) {
|
||||
// A prior (probably timed-out) request has left listeners behind.
|
||||
// Remove them before proceeding.
|
||||
logger.warn("A previous failed command left content listeners behind!");
|
||||
this.removeListeners();
|
||||
}
|
||||
|
||||
this.sendAsync(name, msg, this.curId);
|
||||
});
|
||||
this.curId = uuidgen.generateUUID().toString();
|
||||
|
||||
return proxy;
|
||||
let proxy = new Promise((resolve, reject) => {
|
||||
let removeListeners = (n, fn) => {
|
||||
let rmFn = msg => {
|
||||
if (this.curId !== msg.json.command_id) {
|
||||
logger.warn("Skipping out-of-sync response from listener: " +
|
||||
`Expected response to ${name} with ID ${this.curId}, ` +
|
||||
"but got: " + msg.name + msg.json.toSource());
|
||||
return;
|
||||
}
|
||||
|
||||
this.removeListeners();
|
||||
modal.removeHandler(handleDialog);
|
||||
|
||||
fn(msg);
|
||||
this.curId = null;
|
||||
};
|
||||
|
||||
this._listeners.push([n, rmFn]);
|
||||
return rmFn;
|
||||
};
|
||||
|
||||
let okListener = () => resolve();
|
||||
let valListener = msg => resolve(msg.json.value);
|
||||
let errListener = msg => reject(msg.objects.error);
|
||||
|
||||
let handleDialog = (subject, topic) => {
|
||||
this.removeListeners()
|
||||
modal.removeHandler(handleDialog);
|
||||
this.sendAsync("cancelRequest");
|
||||
resolve();
|
||||
};
|
||||
|
||||
// start content process listeners, and install observers for global-
|
||||
// and tab modal dialogues
|
||||
this.mm.addMessageListener(MARIONETTE_OK, removeListeners(MARIONETTE_OK, okListener));
|
||||
this.mm.addMessageListener(MARIONETTE_DONE, removeListeners(MARIONETTE_DONE, valListener));
|
||||
this.mm.addMessageListener(MARIONETTE_ERROR, removeListeners(MARIONETTE_ERROR, errListener));
|
||||
modal.addHandler(handleDialog);
|
||||
|
||||
this.sendAsync(name, marshal(args), this.curId);
|
||||
});
|
||||
|
||||
return proxy;
|
||||
}
|
||||
};
|
||||
|
||||
proxy.ContentSender = ContentSender;
|
||||
/**
|
||||
* Creates a transparent interface from the content- to the chrome context.
|
||||
*
|
||||
* Calls to this object will be proxied via the frame's sendSyncMessage
|
||||
* (nsISyncMessageSender) function. Since the message is synchronous,
|
||||
* the return value is presented as a return value.
|
||||
*
|
||||
* Example on how to use from a frame content script:
|
||||
*
|
||||
* let chrome = proxy.toChrome(sendSyncMessage.bind(this));
|
||||
* let cookie = chrome.getCookie("foo");
|
||||
*
|
||||
* @param {nsISyncMessageSender} sendSyncMessageFn
|
||||
* The frame message manager's sendSyncMessage function.
|
||||
*/
|
||||
proxy.toChrome = function(sendSyncMessageFn) {
|
||||
let sender = new SyncChromeSender(sendSyncMessageFn);
|
||||
return new Proxy(sender, ownPriorityGetterTrap);
|
||||
};
|
||||
|
||||
/**
|
||||
* The SyncChromeSender sends synchronous RPC messages to the chrome
|
||||
* context, using a frame's sendSyncMessage (nsISyncMessageSender) function.
|
||||
*
|
||||
* Example on how to use from a frame content script:
|
||||
*
|
||||
* let sender = new SyncChromeSender(sendSyncMessage.bind(this));
|
||||
* let res = sender.send("addCookie", cookie);
|
||||
*/
|
||||
this.SyncChromeSender = class {
|
||||
constructor(sendSyncMessage) {
|
||||
this.sendSyncMessage_ = sendSyncMessage;
|
||||
}
|
||||
|
||||
send(func, args) {
|
||||
let name = "Marionette:" + func;
|
||||
return this.sendSyncMessage_(name, marshal(args));
|
||||
}
|
||||
};
|
||||
|
||||
function marshal(args) {
|
||||
if (args.length == 1 && typeof args[0] == "object") {
|
||||
return args[0];
|
||||
}
|
||||
return args;
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче