Bug 1561435 - Format remote/, a=automatic-formatting

# ignore-this-changeset

Differential Revision: https://phabricator.services.mozilla.com/D35925

--HG--
extra : source : b793788d0f38244b33eb59ea36e2c6624dbd12c5
This commit is contained in:
Victor Porof 2019-07-05 10:56:48 +02:00
parent 91f67eb25e
commit 991b3c93c6
64 changed files with 1477 additions and 674 deletions

View file

@ -45,7 +45,6 @@ module.exports = {
"overrides": [{ "overrides": [{
"files": [ "files": [
"devtools/**", "devtools/**",
"remote/**",
"security/**", "security/**",
"services/**", "services/**",
"servo/**", "servo/**",

View file

@ -40,7 +40,6 @@ toolkit/components/telemetry/datareporting-prefs.js
toolkit/components/telemetry/healthreport-prefs.js toolkit/components/telemetry/healthreport-prefs.js
# Ignore all top-level directories for now. # Ignore all top-level directories for now.
remote/**
security/** security/**
services/** services/**
servo/** servo/**

View file

@ -6,11 +6,18 @@
var EXPORTED_SYMBOLS = ["Connection"]; var EXPORTED_SYMBOLS = ["Connection"];
const {Log} = ChromeUtils.import("chrome://remote/content/Log.jsm"); const { Log } = ChromeUtils.import("chrome://remote/content/Log.jsm");
const {XPCOMUtils} = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm"); const { XPCOMUtils } = ChromeUtils.import(
"resource://gre/modules/XPCOMUtils.jsm"
);
XPCOMUtils.defineLazyGetter(this, "log", Log.get); XPCOMUtils.defineLazyGetter(this, "log", Log.get);
XPCOMUtils.defineLazyServiceGetter(this, "UUIDGen", "@mozilla.org/uuid-generator;1", "nsIUUIDGenerator"); XPCOMUtils.defineLazyServiceGetter(
this,
"UUIDGen",
"@mozilla.org/uuid-generator;1",
"nsIUUIDGenerator"
);
class Connection { class Connection {
/** /**
@ -40,8 +47,10 @@ class Connection {
registerSession(session) { registerSession(session) {
if (!session.id) { if (!session.id) {
if (this.defaultSession) { if (this.defaultSession) {
throw new Error("Default session is already set on Connection," + throw new Error(
"can't register another one."); "Default session is already set on Connection," +
"can't register another one."
);
} }
this.defaultSession = session; this.defaultSession = session;
} }
@ -58,14 +67,14 @@ class Connection {
message: e.message, message: e.message,
data: e.stack, data: e.stack,
}; };
this.send({id, error}); this.send({ id, error });
} }
deserialize(data) { deserialize(data) {
const id = data.id; const id = data.id;
const method = data.method; const method = data.method;
const params = data.params || {}; const params = data.params || {};
return {id, method, params}; return { id, method, params };
} }
// transport hooks // transport hooks
@ -73,7 +82,7 @@ class Connection {
onPacket(packet) { onPacket(packet) {
log.trace(`(connection ${this.id})-> ${JSON.stringify(packet)}`); log.trace(`(connection ${this.id})-> ${JSON.stringify(packet)}`);
let message = {id: null}; let message = { id: null };
try { try {
message = this.deserialize(packet); message = this.deserialize(packet);
const { sessionId } = packet; const { sessionId } = packet;

View file

@ -11,9 +11,11 @@ var EXPORTED_SYMBOLS = [
"UnsupportedError", "UnsupportedError",
]; ];
const {Log} = ChromeUtils.import("chrome://remote/content/Log.jsm"); const { Log } = ChromeUtils.import("chrome://remote/content/Log.jsm");
const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm"); const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
const {XPCOMUtils} = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm"); const { XPCOMUtils } = ChromeUtils.import(
"resource://gre/modules/XPCOMUtils.jsm"
);
XPCOMUtils.defineLazyGetter(this, "log", Log.get); XPCOMUtils.defineLazyGetter(this, "log", Log.get);
@ -31,15 +33,15 @@ class RemoteAgentError extends Error {
notify() { notify() {
Cu.reportError(this); Cu.reportError(this);
log.error(this.toString({stack: true})); log.error(this.toString({ stack: true }));
} }
toString({stack = false} = {}) { toString({ stack = false } = {}) {
return RemoteAgentError.format(this, {stack}); return RemoteAgentError.format(this, { stack });
} }
static format(e, {stack = false} = {}) { static format(e, { stack = false } = {}) {
return formatError(e, {stack}); return formatError(e, { stack });
} }
/** /**
@ -88,7 +90,7 @@ class FatalError extends RemoteAgentError {
} }
notify() { notify() {
log.fatal(this.toString({stack: true})); log.fatal(this.toString({ stack: true }));
} }
quit(mode = Ci.nsIAppStartup.eForceQuit) { quit(mode = Ci.nsIAppStartup.eForceQuit) {
@ -110,7 +112,7 @@ class UnknownMethodError extends RemoteAgentError {
} }
} }
function formatError(error, {stack = false} = {}) { function formatError(error, { stack = false } = {}) {
const els = []; const els = [];
els.push(error.name); els.push(error.name);
@ -127,7 +129,7 @@ function formatError(error, {stack = false} = {}) {
if (error.cause) { if (error.cause) {
els.push("\n"); els.push("\n");
els.push("caused by: " + formatError(error.cause, {stack})); els.push("caused by: " + formatError(error.cause, { stack }));
} }
} }

View file

@ -6,10 +6,14 @@
var EXPORTED_SYMBOLS = ["JSONHandler"]; var EXPORTED_SYMBOLS = ["JSONHandler"];
const {HTTP_404, HTTP_505} = ChromeUtils.import("chrome://remote/content/server/HTTPD.jsm"); const { HTTP_404, HTTP_505 } = ChromeUtils.import(
const {Log} = ChromeUtils.import("chrome://remote/content/Log.jsm"); "chrome://remote/content/server/HTTPD.jsm"
const {Protocol} = ChromeUtils.import("chrome://remote/content/Protocol.jsm"); );
const {RemoteAgentError} = ChromeUtils.import("chrome://remote/content/Error.jsm"); const { Log } = ChromeUtils.import("chrome://remote/content/Log.jsm");
const { Protocol } = ChromeUtils.import("chrome://remote/content/Protocol.jsm");
const { RemoteAgentError } = ChromeUtils.import(
"chrome://remote/content/Error.jsm"
);
class JSONHandler { class JSONHandler {
constructor(agent) { constructor(agent) {
@ -24,12 +28,12 @@ class JSONHandler {
getVersion() { getVersion() {
const mainProcessTarget = this.agent.targets.getMainProcessTarget(); const mainProcessTarget = this.agent.targets.getMainProcessTarget();
return { return {
"Browser": "Firefox", Browser: "Firefox",
"Protocol-Version": "1.0", "Protocol-Version": "1.0",
"User-Agent": "Mozilla", "User-Agent": "Mozilla",
"V8-Version": "1.0", "V8-Version": "1.0",
"WebKit-Version": "1.0", "WebKit-Version": "1.0",
"webSocketDebuggerUrl": mainProcessTarget.toJSON().webSocketDebuggerUrl, webSocketDebuggerUrl: mainProcessTarget.toJSON().webSocketDebuggerUrl,
}; };
} }
@ -54,7 +58,11 @@ class JSONHandler {
try { try {
const body = this.routes[request.path](); const body = this.routes[request.path]();
const payload = JSON.stringify(body, sanitise, Log.verbose ? "\t" : undefined); const payload = JSON.stringify(
body,
sanitise,
Log.verbose ? "\t" : undefined
);
response.setStatusLine(request.httpVersion, 200, "OK"); response.setStatusLine(request.httpVersion, 200, "OK");
response.setHeader("Content-Type", "application/json"); response.setHeader("Content-Type", "application/json");

View file

@ -6,8 +6,8 @@
var EXPORTED_SYMBOLS = ["Log"]; var EXPORTED_SYMBOLS = ["Log"];
const {Log: StdLog} = ChromeUtils.import("resource://gre/modules/Log.jsm"); const { Log: StdLog } = ChromeUtils.import("resource://gre/modules/Log.jsm");
const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm"); const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
const LOG_LEVEL = "remote.log.level"; const LOG_LEVEL = "remote.log.level";

View file

@ -6,7 +6,7 @@
var EXPORTED_SYMBOLS = ["Observer"]; var EXPORTED_SYMBOLS = ["Observer"];
const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm"); const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
class Observer { class Observer {
static observe(type, observer) { static observe(type, observer) {

View file

@ -18,6 +18,7 @@ const RecommendedPreferences = {
// Prevent various error message on the console // Prevent various error message on the console
// jest-puppeteer asserts that no error message is emitted by the console // jest-puppeteer asserts that no error message is emitted by the console
"browser.contentblocking.features.standard": "-tp,tpPrivate,cookieBehavior0,-cm,-fp", "browser.contentblocking.features.standard":
"-tp,tpPrivate,cookieBehavior0,-cm,-fp",
"network.cookie.cookieBehavior": 0, "network.cookie.cookieBehavior": 0,
}; };

View file

@ -6,8 +6,10 @@
var EXPORTED_SYMBOLS = ["RemoteAgent", "RemoteAgentFactory"]; var EXPORTED_SYMBOLS = ["RemoteAgent", "RemoteAgentFactory"];
const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm"); const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
const {XPCOMUtils} = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm"); const { XPCOMUtils } = ChromeUtils.import(
"resource://gre/modules/XPCOMUtils.jsm"
);
XPCOMUtils.defineLazyModuleGetters(this, { XPCOMUtils.defineLazyModuleGetters(this, {
FatalError: "chrome://remote/content/Error.jsm", FatalError: "chrome://remote/content/Error.jsm",
@ -36,7 +38,9 @@ class RemoteAgentClass {
throw new Error("Remote agent is disabled by its preference"); throw new Error("Remote agent is disabled by its preference");
} }
if (Services.appinfo.processType != Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT) { if (Services.appinfo.processType != Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT) {
throw new Error("Remote agent can only be instantiated from the parent process"); throw new Error(
"Remote agent can only be instantiated from the parent process"
);
} }
if (this.server) { if (this.server) {
@ -48,7 +52,7 @@ class RemoteAgentClass {
this.server.registerPrefixHandler("/json/", new JSONHandler(this)); this.server.registerPrefixHandler("/json/", new JSONHandler(this));
this.tabs = new TabObserver({registerExisting: true}); this.tabs = new TabObserver({ registerExisting: true });
this.tabs.on("open", (eventName, tab) => { this.tabs.on("open", (eventName, tab) => {
this.targets.connect(tab.linkedBrowser); this.targets.connect(tab.linkedBrowser);
}); });
@ -78,7 +82,7 @@ class RemoteAgentClass {
throw new TypeError(`Expected nsIURI: ${address}`); throw new TypeError(`Expected nsIURI: ${address}`);
} }
let {host, port} = address; let { host, port } = address;
if (Preferences.get(FORCE_LOCAL) && !LOOPBACKS.includes(host)) { if (Preferences.get(FORCE_LOCAL) && !LOOPBACKS.includes(host)) {
throw new Error("Restricted to loopback devices"); throw new Error("Restricted to loopback devices");
} }
@ -167,7 +171,9 @@ class RemoteAgentClass {
const remoteDebuggingPort = flag("remote-debugging-port"); const remoteDebuggingPort = flag("remote-debugging-port");
if (remoteDebugger && remoteDebuggingPort) { if (remoteDebugger && remoteDebuggingPort) {
log.fatal("Conflicting flags --remote-debugger and --remote-debugging-port"); log.fatal(
"Conflicting flags --remote-debugger and --remote-debugging-port"
);
cmdLine.preventDefault = true; cmdLine.preventDefault = true;
return; return;
} }
@ -185,9 +191,14 @@ class RemoteAgentClass {
let addr; let addr;
try { try {
addr = NetUtil.newURI(`http://${host || DEFAULT_HOST}:${port || DEFAULT_PORT}/`); addr = NetUtil.newURI(
`http://${host || DEFAULT_HOST}:${port || DEFAULT_PORT}/`
);
} catch (e) { } catch (e) {
log.fatal(`Expected address syntax [<host>]:<port>: ${remoteDebugger || remoteDebuggingPort}`); log.fatal(
`Expected address syntax [<host>]:<port>: ${remoteDebugger ||
remoteDebuggingPort}`
);
cmdLine.preventDefault = true; cmdLine.preventDefault = true;
return; return;
} }
@ -200,15 +211,20 @@ class RemoteAgentClass {
this.listen(addr); this.listen(addr);
} catch (e) { } catch (e) {
this.close(); this.close();
throw new FatalError(`Unable to start remote agent on ${addr.spec}: ${e.message}`, e); throw new FatalError(
} `Unable to start remote agent on ${addr.spec}: ${e.message}`,
e
);
}
} }
get helpInfo() { get helpInfo() {
return " --remote-debugger [<host>][:<port>]\n" + return (
" --remote-debugging-port <port> Start the Firefox remote agent, which is \n" + " --remote-debugger [<host>][:<port>]\n" +
" a low-level debugging interface based on the CDP protocol.\n" + " --remote-debugging-port <port> Start the Firefox remote agent, which is \n" +
" Defaults to listen on localhost:9222.\n"; " a low-level debugging interface based on the CDP protocol.\n" +
" Defaults to listen on localhost:9222.\n"
);
} }
// XPCOM // XPCOM

View file

@ -10,7 +10,7 @@ var EXPORTED_SYMBOLS = [
"MessagePromise", "MessagePromise",
]; ];
const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm"); const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
/** /**
* Wait for a single event to be fired on a specific EventListener. * Wait for a single event to be fired on a specific EventListener.
@ -43,34 +43,48 @@ const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
* *
* @throws {TypeError} * @throws {TypeError}
*/ */
function EventPromise(listener, type, options = { function EventPromise(
listener,
type,
options = {
capture: false, capture: false,
wantsUntrusted: false, wantsUntrusted: false,
mozSystemGroup: false, mozSystemGroup: false,
}) { }
) {
if (!listener || !("addEventListener" in listener)) { if (!listener || !("addEventListener" in listener)) {
throw new TypeError(); throw new TypeError();
} }
if (typeof type != "string") { if (typeof type != "string") {
throw new TypeError(); throw new TypeError();
} }
if (("capture" in options && typeof options.capture != "boolean") || if (
("wantsUntrusted" in options && typeof options.wantsUntrusted != "boolean") || ("capture" in options && typeof options.capture != "boolean") ||
("mozSystemGroup" in options && typeof options.mozSystemGroup != "boolean")) { ("wantsUntrusted" in options &&
typeof options.wantsUntrusted != "boolean") ||
("mozSystemGroup" in options && typeof options.mozSystemGroup != "boolean")
) {
throw new TypeError(); throw new TypeError();
} }
options.once = true; options.once = true;
return new Promise(resolve => { return new Promise(resolve => {
listener.addEventListener(type, event => { listener.addEventListener(
Services.tm.dispatchToMainThread(() => resolve(event)); type,
}, options); event => {
Services.tm.dispatchToMainThread(() => resolve(event));
},
options
);
}); });
} }
function DOMContentLoadedPromise(window, options = {mozSystemGroup: true}) { function DOMContentLoadedPromise(window, options = { mozSystemGroup: true }) {
if (window.document.readyState == "complete" || window.document.readyState == "interactive") { if (
window.document.readyState == "complete" ||
window.document.readyState == "interactive"
) {
return Promise.resolve(); return Promise.resolve();
} }
return new EventPromise(window, "DOMContentLoaded", options); return new EventPromise(window, "DOMContentLoaded", options);

View file

@ -4,15 +4,15 @@
"use strict"; "use strict";
var EXPORTED_SYMBOLS = [ var EXPORTED_SYMBOLS = ["TabManager", "TabObserver", "WindowObserver"];
"TabManager",
"TabObserver",
"WindowObserver",
];
const {DOMContentLoadedPromise} = ChromeUtils.import("chrome://remote/content/Sync.jsm"); const { DOMContentLoadedPromise } = ChromeUtils.import(
const {EventEmitter} = ChromeUtils.import("resource://gre/modules/EventEmitter.jsm"); "chrome://remote/content/Sync.jsm"
const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm"); );
const { EventEmitter } = ChromeUtils.import(
"resource://gre/modules/EventEmitter.jsm"
);
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
/** /**
* The WindowManager provides tooling for application-agnostic * The WindowManager provides tooling for application-agnostic
@ -40,7 +40,7 @@ class WindowObserver {
* Events will be despatched for the ChromeWindows that exist * Events will be despatched for the ChromeWindows that exist
* at the time the observer is started. * at the time the observer is started.
*/ */
constructor({registerExisting = false} = {}) { constructor({ registerExisting = false } = {}) {
this.registerExisting = registerExisting; this.registerExisting = registerExisting;
EventEmitter.decorate(this); EventEmitter.decorate(this);
} }
@ -63,16 +63,16 @@ class WindowObserver {
async onOpenWindow(xulWindow) { async onOpenWindow(xulWindow) {
const window = xulWindow const window = xulWindow
.QueryInterface(Ci.nsIInterfaceRequestor) .QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindow); .getInterface(Ci.nsIDOMWindow);
await new DOMContentLoadedPromise(window); await new DOMContentLoadedPromise(window);
this.emit("open", window); this.emit("open", window);
} }
onCloseWindow(xulWindow) { onCloseWindow(xulWindow) {
const window = xulWindow const window = xulWindow
.QueryInterface(Ci.nsIInterfaceRequestor) .QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindow); .getInterface(Ci.nsIDOMWindow);
this.emit("close", window); this.emit("close", window);
} }
@ -95,8 +95,8 @@ class TabObserver {
* Events will be fired for ChromeWIndows and their respective tabs * Events will be fired for ChromeWIndows and their respective tabs
* at the time when the observer is started. * at the time when the observer is started.
*/ */
constructor({registerExisting = false} = {}) { constructor({ registerExisting = false } = {}) {
this.windows = new WindowObserver({registerExisting}); this.windows = new WindowObserver({ registerExisting });
EventEmitter.decorate(this); EventEmitter.decorate(this);
} }
@ -136,8 +136,10 @@ class TabObserver {
this.onTabOpen(tab); this.onTabOpen(tab);
} }
window.addEventListener("TabOpen", ({target}) => this.onTabOpen(target)); window.addEventListener("TabOpen", ({ target }) => this.onTabOpen(target));
window.addEventListener("TabClose", ({target}) => this.onTabClose(target)); window.addEventListener("TabClose", ({ target }) =>
this.onTabClose(target)
);
} }
onWindowClose(window) { onWindowClose(window) {

View file

@ -6,10 +6,15 @@
var EXPORTED_SYMBOLS = ["ContentProcessDomain"]; var EXPORTED_SYMBOLS = ["ContentProcessDomain"];
const {Domain} = ChromeUtils.import("chrome://remote/content/domains/Domain.jsm"); const { Domain } = ChromeUtils.import(
"chrome://remote/content/domains/Domain.jsm"
);
ChromeUtils.defineModuleGetter(this, "ContextObserver", ChromeUtils.defineModuleGetter(
"chrome://remote/content/domains/ContextObserver.jsm"); this,
"ContextObserver",
"chrome://remote/content/domains/ContextObserver.jsm"
);
class ContentProcessDomain extends Domain { class ContentProcessDomain extends Domain {
destructor() { destructor() {

View file

@ -6,7 +6,9 @@
var EXPORTED_SYMBOLS = ["ContentProcessDomains"]; var EXPORTED_SYMBOLS = ["ContentProcessDomains"];
const {XPCOMUtils} = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm"); const { XPCOMUtils } = ChromeUtils.import(
"resource://gre/modules/XPCOMUtils.jsm"
);
const ContentProcessDomains = {}; const ContentProcessDomains = {};

View file

@ -25,37 +25,45 @@
var EXPORTED_SYMBOLS = ["ContextObserver"]; var EXPORTED_SYMBOLS = ["ContextObserver"];
const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm"); const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
const {EventEmitter} = ChromeUtils.import("resource://gre/modules/EventEmitter.jsm"); const { EventEmitter } = ChromeUtils.import(
"resource://gre/modules/EventEmitter.jsm"
);
class ContextObserver { class ContextObserver {
constructor(chromeEventHandler) { constructor(chromeEventHandler) {
this.chromeEventHandler = chromeEventHandler; this.chromeEventHandler = chromeEventHandler;
EventEmitter.decorate(this); EventEmitter.decorate(this);
this.chromeEventHandler.addEventListener("DOMWindowCreated", this, this.chromeEventHandler.addEventListener("DOMWindowCreated", this, {
{mozSystemGroup: true}); mozSystemGroup: true,
});
// Listen for pageshow and pagehide to track pages going in/out to/from the BF Cache // Listen for pageshow and pagehide to track pages going in/out to/from the BF Cache
this.chromeEventHandler.addEventListener("pageshow", this, this.chromeEventHandler.addEventListener("pageshow", this, {
{mozSystemGroup: true}); mozSystemGroup: true,
this.chromeEventHandler.addEventListener("pagehide", this, });
{mozSystemGroup: true}); this.chromeEventHandler.addEventListener("pagehide", this, {
mozSystemGroup: true,
});
Services.obs.addObserver(this, "inner-window-destroyed"); Services.obs.addObserver(this, "inner-window-destroyed");
} }
destructor() { destructor() {
this.chromeEventHandler.removeEventListener("DOMWindowCreated", this, this.chromeEventHandler.removeEventListener("DOMWindowCreated", this, {
{mozSystemGroup: true}); mozSystemGroup: true,
this.chromeEventHandler.removeEventListener("pageshow", this, });
{mozSystemGroup: true}); this.chromeEventHandler.removeEventListener("pageshow", this, {
this.chromeEventHandler.removeEventListener("pagehide", this, mozSystemGroup: true,
{mozSystemGroup: true}); });
this.chromeEventHandler.removeEventListener("pagehide", this, {
mozSystemGroup: true,
});
Services.obs.removeObserver(this, "inner-window-destroyed"); Services.obs.removeObserver(this, "inner-window-destroyed");
} }
handleEvent({type, target, persisted}) { handleEvent({ type, target, persisted }) {
const window = target.defaultView; const window = target.defaultView;
if (window.top != this.chromeEventHandler.ownerGlobal) { if (window.top != this.chromeEventHandler.ownerGlobal) {
// Ignore iframes for now. // Ignore iframes for now.
@ -65,31 +73,31 @@ class ContextObserver {
const frameId = windowUtils.outerWindowID; const frameId = windowUtils.outerWindowID;
const id = windowUtils.currentInnerWindowID; const id = windowUtils.currentInnerWindowID;
switch (type) { switch (type) {
case "DOMWindowCreated": case "DOMWindowCreated":
// Do not pass `id` here as that's the new document ID instead of the old one // Do not pass `id` here as that's the new document ID instead of the old one
// that is destroyed. Instead, pass the frameId and let the listener figure out // that is destroyed. Instead, pass the frameId and let the listener figure out
// what ExecutionContext to destroy. // what ExecutionContext to destroy.
this.emit("context-destroyed", { frameId }); this.emit("context-destroyed", { frameId });
this.emit("frame-navigated", { frameId, window }); this.emit("frame-navigated", { frameId, window });
this.emit("context-created", { id, window }); this.emit("context-created", { id, window });
break; break;
case "pageshow": case "pageshow":
// `persisted` is true when this is about a page being resurected from BF Cache // `persisted` is true when this is about a page being resurected from BF Cache
if (!persisted) { if (!persisted) {
return; return;
} }
// XXX(ochameau) we might have to emit FrameNavigate here to properly handle BF Cache // XXX(ochameau) we might have to emit FrameNavigate here to properly handle BF Cache
// scenario in Page domain events // scenario in Page domain events
this.emit("context-created", { id, window }); this.emit("context-created", { id, window });
break; break;
case "pagehide": case "pagehide":
// `persisted` is true when this is about a page being frozen into BF Cache // `persisted` is true when this is about a page being frozen into BF Cache
if (!persisted) { if (!persisted) {
return; return;
} }
this.emit("context-destroyed", { id }); this.emit("context-destroyed", { id });
break; break;
} }
} }
@ -99,5 +107,3 @@ class ContextObserver {
this.emit("context-destroyed", { id: innerWindowID }); this.emit("context-destroyed", { id: innerWindowID });
} }
} }

View file

@ -45,7 +45,7 @@ class Domain {
} }
function isEventHandler(listener) { function isEventHandler(listener) {
return listener && return (
"onEvent" in listener && listener && "onEvent" in listener && typeof listener.onEvent == "function"
typeof listener.onEvent == "function"; );
} }

View file

@ -6,8 +6,12 @@
var EXPORTED_SYMBOLS = ["Domains"]; var EXPORTED_SYMBOLS = ["Domains"];
const {UnknownMethodError} = ChromeUtils.import("chrome://remote/content/Error.jsm"); const { UnknownMethodError } = ChromeUtils.import(
const {Domain} = ChromeUtils.import("chrome://remote/content/domains/Domain.jsm"); "chrome://remote/content/Error.jsm"
);
const { Domain } = ChromeUtils.import(
"chrome://remote/content/domains/Domain.jsm"
);
/** /**
* Lazy domain instance cache. * Lazy domain instance cache.

View file

@ -6,7 +6,9 @@
var EXPORTED_SYMBOLS = ["ParentProcessDomains"]; var EXPORTED_SYMBOLS = ["ParentProcessDomains"];
const {XPCOMUtils} = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm"); const { XPCOMUtils } = ChromeUtils.import(
"resource://gre/modules/XPCOMUtils.jsm"
);
const ParentProcessDomains = {}; const ParentProcessDomains = {};

View file

@ -6,7 +6,9 @@
var EXPORTED_SYMBOLS = ["DOM"]; var EXPORTED_SYMBOLS = ["DOM"];
const {ContentProcessDomain} = ChromeUtils.import("chrome://remote/content/domains/ContentProcessDomain.jsm"); const { ContentProcessDomain } = ChromeUtils.import(
"chrome://remote/content/domains/ContentProcessDomain.jsm"
);
class DOM extends ContentProcessDomain { class DOM extends ContentProcessDomain {
constructor(session) { constructor(session) {
@ -48,10 +50,14 @@ class DOM extends ContentProcessDomain {
let quads = unsafeObject.getBoxQuads({ relativeTo: this.content.document }); let quads = unsafeObject.getBoxQuads({ relativeTo: this.content.document });
quads = quads.map(quad => { quads = quads.map(quad => {
return [ return [
quad.p1.x, quad.p1.y, quad.p1.x,
quad.p2.x, quad.p2.y, quad.p1.y,
quad.p3.x, quad.p3.y, quad.p2.x,
quad.p4.x, quad.p4.y, quad.p2.y,
quad.p3.x,
quad.p3.y,
quad.p4.x,
quad.p4.y,
].map(Math.round); ].map(Math.round);
}); });
return { quads }; return { quads };
@ -67,7 +73,10 @@ class DOM extends ContentProcessDomain {
height: Math.round(bounding.height), height: Math.round(bounding.height),
}; };
for (const box of ["content", "padding", "border", "margin"]) { for (const box of ["content", "padding", "border", "margin"]) {
const quads = unsafeObject.getBoxQuads({box, relativeTo: this.content.document}); const quads = unsafeObject.getBoxQuads({
box,
relativeTo: this.content.document,
});
// getBoxQuads may return more than one element. In this case we have to compute the bounding box // getBoxQuads may return more than one element. In this case we have to compute the bounding box
// of all these boxes. // of all these boxes.
@ -99,10 +108,14 @@ class DOM extends ContentProcessDomain {
}); });
model[box] = [ model[box] = [
bounding.p1.x, bounding.p1.y, bounding.p1.x,
bounding.p2.x, bounding.p2.y, bounding.p1.y,
bounding.p3.x, bounding.p3.y, bounding.p2.x,
bounding.p4.x, bounding.p4.y, bounding.p2.y,
bounding.p3.x,
bounding.p3.y,
bounding.p4.x,
bounding.p4.y,
].map(Math.round); ].map(Math.round);
} }
return { return {

View file

@ -6,7 +6,9 @@
var EXPORTED_SYMBOLS = ["Emulation"]; var EXPORTED_SYMBOLS = ["Emulation"];
const {ContentProcessDomain} = ChromeUtils.import("chrome://remote/content/domains/ContentProcessDomain.jsm"); const { ContentProcessDomain } = ChromeUtils.import(
"chrome://remote/content/domains/ContentProcessDomain.jsm"
);
class Emulation extends ContentProcessDomain { class Emulation extends ContentProcessDomain {
// commands // commands

View file

@ -6,6 +6,8 @@
var EXPORTED_SYMBOLS = ["Input"]; var EXPORTED_SYMBOLS = ["Input"];
const {ContentProcessDomain} = ChromeUtils.import("chrome://remote/content/domains/ContentProcessDomain.jsm"); const { ContentProcessDomain } = ChromeUtils.import(
"chrome://remote/content/domains/ContentProcessDomain.jsm"
);
class Input extends ContentProcessDomain {} class Input extends ContentProcessDomain {}

View file

@ -6,8 +6,10 @@
var EXPORTED_SYMBOLS = ["Log"]; var EXPORTED_SYMBOLS = ["Log"];
const {ContentProcessDomain} = ChromeUtils.import("chrome://remote/content/domains/ContentProcessDomain.jsm"); const { ContentProcessDomain } = ChromeUtils.import(
const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm"); "chrome://remote/content/domains/ContentProcessDomain.jsm"
);
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
class Log extends ContentProcessDomain { class Log extends ContentProcessDomain {
constructor(session) { constructor(session) {
@ -57,7 +59,7 @@ class Log extends ContentProcessDomain {
entry = fromConsoleAPI(message.wrappedJSObject); entry = fromConsoleAPI(message.wrappedJSObject);
} }
this.emit("Log.entryAdded", {entry}); this.emit("Log.entryAdded", { entry });
} }
// XPCOM // XPCOM
@ -91,12 +93,11 @@ function fromConsoleAPI(message) {
// A couple of possible level are defined here: // A couple of possible level are defined here:
// https://searchfox.org/mozilla-central/rev/00c0d068ece99717bea7475f7dc07e61f7f35984/dom/console/Console.cpp#1086-1100 // https://searchfox.org/mozilla-central/rev/00c0d068ece99717bea7475f7dc07e61f7f35984/dom/console/Console.cpp#1086-1100
const levels = { const levels = {
"log": "verbose", log: "verbose",
"info": "info", info: "info",
"warn": "warning", warn: "warning",
"error": "error", error: "error",
"exception": "error", exception: "error",
}; };
const level = levels[message.level] || "info"; const level = levels[message.level] || "info";
@ -112,15 +113,19 @@ function fromConsoleAPI(message) {
} }
function fromScriptError(error) { function fromScriptError(error) {
const {flags, errorMessage, sourceName, lineNumber, stack} = error; const { flags, errorMessage, sourceName, lineNumber, stack } = error;
// lossy reduction from bitmask to CDP string level // lossy reduction from bitmask to CDP string level
let level = "verbose"; let level = "verbose";
if ((flags & Ci.nsIScriptError.exceptionFlag) || if (
(flags & Ci.nsIScriptError.errorFlag)) { flags & Ci.nsIScriptError.exceptionFlag ||
flags & Ci.nsIScriptError.errorFlag
) {
level = "error"; level = "error";
} else if ((flags & Ci.nsIScriptError.warningFlag) || } else if (
(flags & Ci.nsIScriptError.strictFlag)) { flags & Ci.nsIScriptError.warningFlag ||
flags & Ci.nsIScriptError.strictFlag
) {
level = "warning"; level = "warning";
} else if (flags & Ci.nsIScriptError.infoFlag) { } else if (flags & Ci.nsIScriptError.infoFlag) {
level = "info"; level = "info";

View file

@ -6,9 +6,13 @@
var EXPORTED_SYMBOLS = ["Page"]; var EXPORTED_SYMBOLS = ["Page"];
const {ContentProcessDomain} = ChromeUtils.import("chrome://remote/content/domains/ContentProcessDomain.jsm"); const { ContentProcessDomain } = ChromeUtils.import(
const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm"); "chrome://remote/content/domains/ContentProcessDomain.jsm"
const {UnsupportedError} = ChromeUtils.import("chrome://remote/content/Error.jsm"); );
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
const { UnsupportedError } = ChromeUtils.import(
"chrome://remote/content/Error.jsm"
);
class Page extends ContentProcessDomain { class Page extends ContentProcessDomain {
constructor(session) { constructor(session) {
@ -31,10 +35,12 @@ class Page extends ContentProcessDomain {
this.enabled = true; this.enabled = true;
this.contextObserver.on("frame-navigated", this.onFrameNavigated); this.contextObserver.on("frame-navigated", this.onFrameNavigated);
this.chromeEventHandler.addEventListener("DOMContentLoaded", this, this.chromeEventHandler.addEventListener("DOMContentLoaded", this, {
{mozSystemGroup: true}); mozSystemGroup: true,
this.chromeEventHandler.addEventListener("pageshow", this, });
{mozSystemGroup: true}); this.chromeEventHandler.addEventListener("pageshow", this, {
mozSystemGroup: true,
});
} }
} }
@ -42,15 +48,17 @@ class Page extends ContentProcessDomain {
if (this.enabled) { if (this.enabled) {
this.contextObserver.off("frame-navigated", this.onFrameNavigated); this.contextObserver.off("frame-navigated", this.onFrameNavigated);
this.chromeEventHandler.removeEventListener("DOMContentLoaded", this, this.chromeEventHandler.removeEventListener("DOMContentLoaded", this, {
{mozSystemGroup: true}); mozSystemGroup: true,
this.chromeEventHandler.removeEventListener("pageshow", this, });
{mozSystemGroup: true}); this.chromeEventHandler.removeEventListener("pageshow", this, {
mozSystemGroup: true,
});
this.enabled = false; this.enabled = false;
} }
} }
async navigate({url, referrer, transitionType, frameId} = {}) { async navigate({ url, referrer, transitionType, frameId } = {}) {
if (frameId && frameId != this.content.windowUtils.outerWindowID) { if (frameId && frameId != this.content.windowUtils.outerWindowID) {
throw new UnsupportedError("frameId not supported"); throw new UnsupportedError("frameId not supported");
} }
@ -108,7 +116,7 @@ class Page extends ContentProcessDomain {
}); });
} }
handleEvent({type, target}) { handleEvent({ type, target }) {
if (target.defaultView != this.content) { if (target.defaultView != this.content) {
// Ignore iframes for now // Ignore iframes for now
return; return;
@ -119,26 +127,26 @@ class Page extends ContentProcessDomain {
const url = target.location.href; const url = target.location.href;
switch (type) { switch (type) {
case "DOMContentLoaded": case "DOMContentLoaded":
this.emit("Page.domContentEventFired", {timestamp}); this.emit("Page.domContentEventFired", { timestamp });
break; break;
case "pageshow": case "pageshow":
this.emit("Page.loadEventFired", {timestamp, frameId}); this.emit("Page.loadEventFired", { timestamp, frameId });
// XXX this should most likely be sent differently // XXX this should most likely be sent differently
this.emit("Page.navigatedWithinDocument", {timestamp, frameId, url}); this.emit("Page.navigatedWithinDocument", { timestamp, frameId, url });
this.emit("Page.frameStoppedLoading", {timestamp, frameId}); this.emit("Page.frameStoppedLoading", { timestamp, frameId });
break; break;
} }
} }
} }
function transitionToLoadFlag(transitionType) { function transitionToLoadFlag(transitionType) {
switch (transitionType) { switch (transitionType) {
case "reload": case "reload":
return Ci.nsIWebNavigation.LOAD_FLAGS_IS_REFRESH; return Ci.nsIWebNavigation.LOAD_FLAGS_IS_REFRESH;
case "link": case "link":
default: default:
return Ci.nsIWebNavigation.LOAD_FLAGS_IS_LINK; return Ci.nsIWebNavigation.LOAD_FLAGS_IS_LINK;
} }
} }

View file

@ -6,7 +6,9 @@
var EXPORTED_SYMBOLS = ["Performance"]; var EXPORTED_SYMBOLS = ["Performance"];
const {ContentProcessDomain} = ChromeUtils.import("chrome://remote/content/domains/ContentProcessDomain.jsm"); const { ContentProcessDomain } = ChromeUtils.import(
"chrome://remote/content/domains/ContentProcessDomain.jsm"
);
class Performance extends ContentProcessDomain { class Performance extends ContentProcessDomain {
constructor(session) { constructor(session) {

View file

@ -6,10 +6,17 @@
var EXPORTED_SYMBOLS = ["Runtime"]; var EXPORTED_SYMBOLS = ["Runtime"];
const {ContentProcessDomain} = ChromeUtils.import("chrome://remote/content/domains/ContentProcessDomain.jsm"); const { ContentProcessDomain } = ChromeUtils.import(
const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm"); "chrome://remote/content/domains/ContentProcessDomain.jsm"
const {ExecutionContext} = ChromeUtils.import("chrome://remote/content/domains/content/runtime/ExecutionContext.jsm"); );
const {addDebuggerToGlobal} = ChromeUtils.import("resource://gre/modules/jsdebugger.jsm", {}); const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
const { ExecutionContext } = ChromeUtils.import(
"chrome://remote/content/domains/content/runtime/ExecutionContext.jsm"
);
const { addDebuggerToGlobal } = ChromeUtils.import(
"resource://gre/modules/jsdebugger.jsm",
{}
);
// Import the `Debugger` constructor in the current scope // Import the `Debugger` constructor in the current scope
addDebuggerToGlobal(Cu.getGlobalForObject(this)); addDebuggerToGlobal(Cu.getGlobalForObject(this));
@ -63,11 +70,15 @@ class Runtime extends ContentProcessDomain {
evaluate(request) { evaluate(request) {
const context = this.contexts.get(request.contextId); const context = this.contexts.get(request.contextId);
if (!context) { if (!context) {
throw new Error(`Unable to find execution context with id: ${request.contextId}`); throw new Error(
`Unable to find execution context with id: ${request.contextId}`
);
} }
if (typeof(request.expression) != "string") { if (typeof request.expression != "string") {
throw new Error(`Expecting 'expression' attribute to be a string. ` + throw new Error(
`But was: ${typeof(request.expression)}`); `Expecting 'expression' attribute to be a string. ` +
`But was: ${typeof request.expression}`
);
} }
return context.evaluate(request.expression); return context.evaluate(request.expression);
} }
@ -75,7 +86,7 @@ class Runtime extends ContentProcessDomain {
getRemoteObject(objectId) { getRemoteObject(objectId) {
for (const ctx of this.contexts.values()) { for (const ctx of this.contexts.values()) {
const obj = ctx.getRemoteObject(objectId); const obj = ctx.getRemoteObject(objectId);
if (typeof(obj) != "undefined") { if (typeof obj != "undefined") {
return obj; return obj;
} }
} }
@ -108,33 +119,47 @@ class Runtime extends ContentProcessDomain {
} }
} }
if (!context) { if (!context) {
throw new Error(`Unable to get the context for object with id: ${request.objectId}`); throw new Error(
`Unable to get the context for object with id: ${request.objectId}`
);
} }
} else { } else {
context = this.contexts.get(request.executionContextId); context = this.contexts.get(request.executionContextId);
if (!context) { if (!context) {
throw new Error(`Unable to find execution context with id: ${request.executionContextId}`); throw new Error(
`Unable to find execution context with id: ${
request.executionContextId
}`
);
} }
} }
if (typeof(request.functionDeclaration) != "string") { if (typeof request.functionDeclaration != "string") {
throw new Error("Expect 'functionDeclaration' attribute to be passed and be a string"); throw new Error(
"Expect 'functionDeclaration' attribute to be passed and be a string"
);
} }
if (request.arguments && !Array.isArray(request.arguments)) { if (request.arguments && !Array.isArray(request.arguments)) {
throw new Error("Expect 'arguments' to be an array"); throw new Error("Expect 'arguments' to be an array");
} }
if (request.returnByValue && typeof(request.returnByValue) != "boolean") { if (request.returnByValue && typeof request.returnByValue != "boolean") {
throw new Error("Expect 'returnByValue' to be a boolean"); throw new Error("Expect 'returnByValue' to be a boolean");
} }
if (request.awaitPromise && typeof(request.awaitPromise) != "boolean") { if (request.awaitPromise && typeof request.awaitPromise != "boolean") {
throw new Error("Expect 'awaitPromise' to be a boolean"); throw new Error("Expect 'awaitPromise' to be a boolean");
} }
return context.callFunctionOn(request.functionDeclaration, request.arguments, request.returnByValue, request.awaitPromise, request.objectId); return context.callFunctionOn(
request.functionDeclaration,
request.arguments,
request.returnByValue,
request.awaitPromise,
request.objectId
);
} }
getProperties({ objectId, ownProperties }) { getProperties({ objectId, ownProperties }) {
for (const ctx of this.contexts.values()) { for (const ctx of this.contexts.values()) {
const obj = ctx.getRemoteObject(objectId); const obj = ctx.getRemoteObject(objectId);
if (typeof(obj) != "undefined") { if (typeof obj != "undefined") {
return ctx.getProperties({ objectId, ownProperties }); return ctx.getProperties({ objectId, ownProperties });
} }
} }

View file

@ -6,7 +6,9 @@
var EXPORTED_SYMBOLS = ["Security"]; var EXPORTED_SYMBOLS = ["Security"];
const {ContentProcessDomain} = ChromeUtils.import("chrome://remote/content/domains/ContentProcessDomain.jsm"); const { ContentProcessDomain } = ChromeUtils.import(
"chrome://remote/content/domains/ContentProcessDomain.jsm"
);
class Security extends ContentProcessDomain { class Security extends ContentProcessDomain {
constructor(session) { constructor(session) {

View file

@ -6,13 +6,26 @@
var EXPORTED_SYMBOLS = ["ExecutionContext"]; var EXPORTED_SYMBOLS = ["ExecutionContext"];
const uuidGen = Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUIDGenerator); const uuidGen = Cc["@mozilla.org/uuid-generator;1"].getService(
Ci.nsIUUIDGenerator
);
const TYPED_ARRAY_CLASSES = ["Uint8Array", "Uint8ClampedArray", "Uint16Array", const TYPED_ARRAY_CLASSES = [
"Uint32Array", "Int8Array", "Int16Array", "Int32Array", "Uint8Array",
"Float32Array", "Float64Array"]; "Uint8ClampedArray",
"Uint16Array",
"Uint32Array",
"Int8Array",
"Int16Array",
"Int32Array",
"Float32Array",
"Float64Array",
];
function uuid() { function uuid() {
return uuidGen.generateUUID().toString().slice(1, -1); return uuidGen
.generateUUID()
.toString()
.slice(1, -1);
} }
/** /**
@ -89,10 +102,15 @@ class ExecutionContext {
* describing the exception by following CDP ExceptionDetails specification. * describing the exception by following CDP ExceptionDetails specification.
*/ */
_returnError(exception) { _returnError(exception) {
if (this._debuggee.executeInGlobalWithBindings("exception instanceof Error", if (
{exception}).return) { this._debuggee.executeInGlobalWithBindings("exception instanceof Error", {
const text = this._debuggee.executeInGlobalWithBindings("exception.message", exception,
{exception}).return; }).return
) {
const text = this._debuggee.executeInGlobalWithBindings(
"exception.message",
{ exception }
).return;
return { return {
exceptionDetails: { exceptionDetails: {
text, text,
@ -108,7 +126,13 @@ class ExecutionContext {
}; };
} }
async callFunctionOn(functionDeclaration, callArguments = [], returnByValue = false, awaitPromise = false, objectId = null) { async callFunctionOn(
functionDeclaration,
callArguments = [],
returnByValue = false,
awaitPromise = false,
objectId = null
) {
// Map the given objectId to a JS reference. // Map the given objectId to a JS reference.
let thisArg = null; let thisArg = null;
if (objectId) { if (objectId) {
@ -188,8 +212,12 @@ class ExecutionContext {
enumerable: descriptor.enumerable, enumerable: descriptor.enumerable,
writable: descriptor.writable, writable: descriptor.writable,
value: this._toRemoteObject(descriptor.value), value: this._toRemoteObject(descriptor.value),
get: descriptor.get ? this._toRemoteObject(descriptor.get) : undefined, get: descriptor.get
set: descriptor.set ? this._toRemoteObject(descriptor.set) : undefined, ? this._toRemoteObject(descriptor.get)
: undefined,
set: descriptor.set
? this._toRemoteObject(descriptor.set)
: undefined,
isOwn, isOwn,
}); });
@ -224,10 +252,13 @@ class ExecutionContext {
* The JSON string * The JSON string
*/ */
_serialize(obj) { _serialize(obj) {
if (typeof(obj) == "undefined") { if (typeof obj == "undefined") {
return undefined; return undefined;
} }
const result = this._debuggee.executeInGlobalWithBindings("JSON.stringify(e)", {e: obj}); const result = this._debuggee.executeInGlobalWithBindings(
"JSON.stringify(e)",
{ e: obj }
);
if (result.throw) { if (result.throw) {
throw new Error("Object is not serializable"); throw new Error("Object is not serializable");
} }
@ -247,10 +278,14 @@ class ExecutionContext {
} }
if (arg.unserializableValue) { if (arg.unserializableValue) {
switch (arg.unserializableValue) { switch (arg.unserializableValue) {
case "Infinity": return Infinity; case "Infinity":
case "-Infinity": return -Infinity; return Infinity;
case "-0": return -0; case "-Infinity":
case "NaN": return NaN; return -Infinity;
case "-0":
return -0;
case "NaN":
return NaN;
} }
} }
return this._deserialize(arg.value); return this._deserialize(arg.value);
@ -263,8 +298,10 @@ class ExecutionContext {
if (typeof obj !== "object") { if (typeof obj !== "object") {
return obj; return obj;
} }
const result = this._debuggee.executeInGlobalWithBindings("JSON.parse(obj)", const result = this._debuggee.executeInGlobalWithBindings(
{obj: JSON.stringify(obj)}); "JSON.parse(obj)",
{ obj: JSON.stringify(obj) }
);
if (result.throw) { if (result.throw) {
throw new Error("Unable to deserialize object"); throw new Error("Unable to deserialize object");
} }
@ -318,7 +355,7 @@ class ExecutionContext {
} }
const type = typeof rawObj; const type = typeof rawObj;
return {objectId, type, subtype}; return { objectId, type, subtype };
} }
// Now, handle all values that Debugger API isn't wrapping into Debugger.API. // Now, handle all values that Debugger API isn't wrapping into Debugger.API.
@ -331,19 +368,20 @@ class ExecutionContext {
if (type == "symbol" || type == "bigint") { if (type == "symbol" || type == "bigint") {
const objectId = uuid(); const objectId = uuid();
this._remoteObjects.set(objectId, debuggerObj); this._remoteObjects.set(objectId, debuggerObj);
return {objectId, type}; return { objectId, type };
} }
// A few primitive type can't be serialized and CDP has special case for them // A few primitive type can't be serialized and CDP has special case for them
let unserializableValue = undefined; let unserializableValue = undefined;
if (Object.is(debuggerObj, NaN)) if (Object.is(debuggerObj, NaN)) {
unserializableValue = "NaN"; unserializableValue = "NaN";
else if (Object.is(debuggerObj, -0)) } else if (Object.is(debuggerObj, -0)) {
unserializableValue = "-0"; unserializableValue = "-0";
else if (Object.is(debuggerObj, Infinity)) } else if (Object.is(debuggerObj, Infinity)) {
unserializableValue = "Infinity"; unserializableValue = "Infinity";
else if (Object.is(debuggerObj, -Infinity)) } else if (Object.is(debuggerObj, -Infinity)) {
unserializableValue = "-Infinity"; unserializableValue = "-Infinity";
}
if (unserializableValue) { if (unserializableValue) {
return { return {
unserializableValue, unserializableValue,

View file

@ -6,14 +6,19 @@
var EXPORTED_SYMBOLS = ["Browser"]; var EXPORTED_SYMBOLS = ["Browser"];
const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm"); const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
const {Domain} = ChromeUtils.import("chrome://remote/content/domains/Domain.jsm"); const { Domain } = ChromeUtils.import(
"chrome://remote/content/domains/Domain.jsm"
);
class Browser extends Domain { class Browser extends Domain {
getVersion() { getVersion() {
const { isHeadless } = Cc["@mozilla.org/gfx/info;1"].getService(Ci.nsIGfxInfo); const { isHeadless } = Cc["@mozilla.org/gfx/info;1"].getService(
const { userAgent } = Cc["@mozilla.org/network/protocol;1?name=http"] Ci.nsIGfxInfo
.getService(Ci.nsIHttpProtocolHandler); );
const { userAgent } = Cc[
"@mozilla.org/network/protocol;1?name=http"
].getService(Ci.nsIHttpProtocolHandler);
return { return {
protocolVersion: "1", protocolVersion: "1",
product: (isHeadless ? "Headless " : "") + "Firefox", product: (isHeadless ? "Headless " : "") + "Firefox",

View file

@ -6,8 +6,10 @@
var EXPORTED_SYMBOLS = ["Input"]; var EXPORTED_SYMBOLS = ["Input"];
const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm"); const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
const {Domain} = ChromeUtils.import("chrome://remote/content/domains/Domain.jsm"); const { Domain } = ChromeUtils.import(
"chrome://remote/content/domains/Domain.jsm"
);
class Input extends Domain { class Input extends Domain {
// commands // commands
@ -28,12 +30,7 @@ class Input extends Domain {
* - windowsVirtualKeyCode * - windowsVirtualKeyCode
*/ */
async dispatchKeyEvent(options) { async dispatchKeyEvent(options) {
const { const { key, modifiers, type, windowsVirtualKeyCode } = options;
key,
modifiers,
type,
windowsVirtualKeyCode,
} = options;
let domType; let domType;
if (type == "keyDown" || type == "rawKeyDown") { if (type == "keyDown" || type == "rawKeyDown") {
@ -54,8 +51,10 @@ class Input extends Domain {
const EventUtils = this._getEventUtils(browserWindow); const EventUtils = this._getEventUtils(browserWindow);
const onEvent = new Promise(r => { const onEvent = new Promise(r => {
browserWindow.addEventListener(domType, r, browserWindow.addEventListener(domType, r, {
{mozSystemGroup: true, once: true}); mozSystemGroup: true,
once: true,
});
}); });
if (type == "char") { if (type == "char") {
@ -65,21 +64,23 @@ class Input extends Domain {
} else { } else {
// Non printable keys should be prefixed with `KEY_` // Non printable keys should be prefixed with `KEY_`
const eventUtilsKey = key.length == 1 ? key : "KEY_" + key; const eventUtilsKey = key.length == 1 ? key : "KEY_" + key;
EventUtils.synthesizeKey(eventUtilsKey, { EventUtils.synthesizeKey(
keyCode: windowsVirtualKeyCode, eventUtilsKey,
type: domType, {
altKey: !!(modifiers & 1), keyCode: windowsVirtualKeyCode,
ctrlKey: !!(modifiers & 2), type: domType,
metaKey: !!(modifiers & 4), altKey: !!(modifiers & 1),
shiftKey: !!(modifiers & 8), ctrlKey: !!(modifiers & 2),
}, browserWindow); metaKey: !!(modifiers & 4),
shiftKey: !!(modifiers & 8),
},
browserWindow
);
} }
await onEvent; await onEvent;
} }
async dispatchMouseEvent({ async dispatchMouseEvent({ type, button, x, y, modifiers, clickCount }) {
type, button, x, y, modifiers, clickCount,
}) {
if (type == "mousePressed") { if (type == "mousePressed") {
type = "mousedown"; type = "mousedown";
} else if (type == "mouseReleased") { } else if (type == "mouseReleased") {
@ -135,8 +136,10 @@ class Input extends Domain {
_EU_Ci: Ci, _EU_Ci: Ci,
_EU_Cc: Cc, _EU_Cc: Cc,
}; };
Services.scriptloader.loadSubScript("chrome://remote/content/external/EventUtils.js", Services.scriptloader.loadSubScript(
this._eventUtils); "chrome://remote/content/external/EventUtils.js",
this._eventUtils
);
} }
return this._eventUtils; return this._eventUtils;
} }

View file

@ -6,8 +6,12 @@
var EXPORTED_SYMBOLS = ["Network"]; var EXPORTED_SYMBOLS = ["Network"];
const {Domain} = ChromeUtils.import("chrome://remote/content/domains/Domain.jsm"); const { Domain } = ChromeUtils.import(
const {NetworkObserver} = ChromeUtils.import("chrome://remote/content/domains/parent/network/NetworkObserver.jsm"); "chrome://remote/content/domains/Domain.jsm"
);
const { NetworkObserver } = ChromeUtils.import(
"chrome://remote/content/domains/parent/network/NetworkObserver.jsm"
);
const LOAD_CAUSE_STRINGS = { const LOAD_CAUSE_STRINGS = {
[Ci.nsIContentPolicy.TYPE_INVALID]: "Invalid", [Ci.nsIContentPolicy.TYPE_INVALID]: "Invalid",
@ -120,13 +124,17 @@ function getLoadContext(httpChannel) {
let loadContext = null; let loadContext = null;
try { try {
if (httpChannel.notificationCallbacks) { if (httpChannel.notificationCallbacks) {
loadContext = httpChannel.notificationCallbacks.getInterface(Ci.nsILoadContext); loadContext = httpChannel.notificationCallbacks.getInterface(
Ci.nsILoadContext
);
} }
} catch (e) {} } catch (e) {}
try { try {
if (!loadContext && httpChannel.loadGroup) { if (!loadContext && httpChannel.loadGroup) {
loadContext = httpChannel.loadGroup.notificationCallbacks.getInterface(Ci.nsILoadContext); loadContext = httpChannel.loadGroup.notificationCallbacks.getInterface(
Ci.nsILoadContext
);
} }
} catch (e) { } } catch (e) {}
return loadContext; return loadContext;
} }

View file

@ -6,7 +6,9 @@
var EXPORTED_SYMBOLS = ["Page"]; var EXPORTED_SYMBOLS = ["Page"];
const {Domain} = ChromeUtils.import("chrome://remote/content/domains/Domain.jsm"); const { Domain } = ChromeUtils.import(
"chrome://remote/content/domains/Domain.jsm"
);
class Page extends Domain { class Page extends Domain {
// commands // commands

View file

@ -6,10 +6,18 @@
var EXPORTED_SYMBOLS = ["Target"]; var EXPORTED_SYMBOLS = ["Target"];
const {Domain} = ChromeUtils.import("chrome://remote/content/domains/Domain.jsm"); const { Domain } = ChromeUtils.import(
const {TabManager} = ChromeUtils.import("chrome://remote/content/WindowManager.jsm"); "chrome://remote/content/domains/Domain.jsm"
const {TabSession} = ChromeUtils.import("chrome://remote/content/sessions/TabSession.jsm"); );
const {ContextualIdentityService} = ChromeUtils.import("resource://gre/modules/ContextualIdentityService.jsm"); const { TabManager } = ChromeUtils.import(
"chrome://remote/content/WindowManager.jsm"
);
const { TabSession } = ChromeUtils.import(
"chrome://remote/content/sessions/TabSession.jsm"
);
const { ContextualIdentityService } = ChromeUtils.import(
"resource://gre/modules/ContextualIdentityService.jsm"
);
let sessionIds = 1; let sessionIds = 1;
let browserContextIds = 1; let browserContextIds = 1;
@ -29,7 +37,9 @@ class Target extends Domain {
} }
createBrowserContext() { createBrowserContext() {
const identity = ContextualIdentityService.create("remote-agent-" + (browserContextIds++)); const identity = ContextualIdentityService.create(
"remote-agent-" + browserContextIds++
);
return { browserContextId: identity.userContextId }; return { browserContextId: identity.userContextId };
} }
@ -75,9 +85,11 @@ class Target extends Domain {
const tab = TabManager.addTab({ userContextId: browserContextId }); const tab = TabManager.addTab({ userContextId: browserContextId });
const target = await onTarget; const target = await onTarget;
if (tab.linkedBrowser != target.browser) { if (tab.linkedBrowser != target.browser) {
throw new Error("Unexpected tab opened: " + tab.linkedBrowser.currentURI.spec); throw new Error(
"Unexpected tab opened: " + tab.linkedBrowser.currentURI.spec
);
} }
return {targetId: target.id}; return { targetId: target.id };
} }
closeTarget({ targetId }) { closeTarget({ targetId }) {
@ -93,7 +105,12 @@ class Target extends Domain {
return new Error(`Unable to find target with id '${targetId}'`); return new Error(`Unable to find target with id '${targetId}'`);
} }
const session = new TabSession(this.session.connection, target, sessionIds++, this.session); const session = new TabSession(
this.session.connection,
target,
sessionIds++,
this.session
);
this.emit("Target.attachedToTarget", { this.emit("Target.attachedToTarget", {
targetInfo: { targetInfo: {
type: "page", type: "page",

View file

@ -4,17 +4,33 @@
"use strict"; "use strict";
const {EventEmitter} = ChromeUtils.import("resource://gre/modules/EventEmitter.jsm"); const { EventEmitter } = ChromeUtils.import(
const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm"); "resource://gre/modules/EventEmitter.jsm"
const {NetUtil} = ChromeUtils.import("resource://gre/modules/NetUtil.jsm"); );
const {CommonUtils} = ChromeUtils.import("resource://services-common/utils.js"); const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
const { NetUtil } = ChromeUtils.import("resource://gre/modules/NetUtil.jsm");
const { CommonUtils } = ChromeUtils.import(
"resource://services-common/utils.js"
);
const Cm = Components.manager; const Cm = Components.manager;
const CC = Components.Constructor; const CC = Components.Constructor;
const BinaryInputStream = CC("@mozilla.org/binaryinputstream;1", "nsIBinaryInputStream", "setInputStream"); const BinaryInputStream = CC(
const BinaryOutputStream = CC("@mozilla.org/binaryoutputstream;1", "nsIBinaryOutputStream", "setOutputStream"); "@mozilla.org/binaryinputstream;1",
const StorageStream = CC("@mozilla.org/storagestream;1", "nsIStorageStream", "init"); "nsIBinaryInputStream",
"setInputStream"
);
const BinaryOutputStream = CC(
"@mozilla.org/binaryoutputstream;1",
"nsIBinaryOutputStream",
"setOutputStream"
);
const StorageStream = CC(
"@mozilla.org/storagestream;1",
"nsIStorageStream",
"init"
);
// Cap response storage with 100Mb per tracked tab. // Cap response storage with 100Mb per tracked tab.
const MAX_RESPONSE_STORAGE_SIZE = 100 * 1024 * 1024; const MAX_RESPONSE_STORAGE_SIZE = 100 * 1024 * 1024;
@ -36,7 +52,9 @@ class NetworkObserver {
constructor() { constructor() {
EventEmitter.decorate(this); EventEmitter.decorate(this);
this._browserSessionCount = new Map(); this._browserSessionCount = new Map();
this._activityDistributor = Cc["@mozilla.org/network/http-activity-distributor;1"].getService(Ci.nsIHttpActivityDistributor); this._activityDistributor = Cc[
"@mozilla.org/network/http-activity-distributor;1"
].getService(Ci.nsIHttpActivityDistributor);
this._activityDistributor.addObserver(this); this._activityDistributor.addObserver(this);
this._redirectMap = new Map(); this._redirectMap = new Map();
@ -53,8 +71,19 @@ class NetworkObserver {
}; };
// Register self as ChannelEventSink to track redirects. // Register self as ChannelEventSink to track redirects.
const registrar = Cm.QueryInterface(Ci.nsIComponentRegistrar); const registrar = Cm.QueryInterface(Ci.nsIComponentRegistrar);
registrar.registerFactory(SINK_CLASS_ID, SINK_CLASS_DESCRIPTION, SINK_CONTRACT_ID, this._channelSinkFactory); registrar.registerFactory(
Services.catMan.addCategoryEntry(SINK_CATEGORY_NAME, SINK_CONTRACT_ID, SINK_CONTRACT_ID, false, true); SINK_CLASS_ID,
SINK_CLASS_DESCRIPTION,
SINK_CONTRACT_ID,
this._channelSinkFactory
);
Services.catMan.addCategoryEntry(
SINK_CATEGORY_NAME,
SINK_CONTRACT_ID,
SINK_CONTRACT_ID,
false,
true
);
// Request interception state. // Request interception state.
this._browserSuspendedChannels = new Map(); this._browserSuspendedChannels = new Map();
@ -62,12 +91,24 @@ class NetworkObserver {
this._browserResponseStorages = new Map(); this._browserResponseStorages = new Map();
this._onRequest = this._onRequest.bind(this); this._onRequest = this._onRequest.bind(this);
this._onExamineResponse = this._onResponse.bind(this, false /* fromCache */); this._onExamineResponse = this._onResponse.bind(
this,
false /* fromCache */
);
this._onCachedResponse = this._onResponse.bind(this, true /* fromCache */); this._onCachedResponse = this._onResponse.bind(this, true /* fromCache */);
Services.obs.addObserver(this._onRequest, "http-on-modify-request"); Services.obs.addObserver(this._onRequest, "http-on-modify-request");
Services.obs.addObserver(this._onExamineResponse, "http-on-examine-response"); Services.obs.addObserver(
Services.obs.addObserver(this._onCachedResponse, "http-on-examine-cached-response"); this._onExamineResponse,
Services.obs.addObserver(this._onCachedResponse, "http-on-examine-merged-response"); "http-on-examine-response"
);
Services.obs.addObserver(
this._onCachedResponse,
"http-on-examine-cached-response"
);
Services.obs.addObserver(
this._onCachedResponse,
"http-on-examine-merged-response"
);
} }
setExtraHTTPHeaders(browser, headers) { setExtraHTTPHeaders(browser, headers) {
@ -111,7 +152,11 @@ class NetworkObserver {
} }
// 2. Set new headers. // 2. Set new headers.
for (const header of headers) { for (const header of headers) {
httpChannel.setRequestHeader(header.name, header.value, false /* merge */); httpChannel.setRequestHeader(
header.name,
header.value,
false /* merge */
);
} }
} }
suspendedChannels.delete(requestId); suspendedChannels.delete(requestId);
@ -150,14 +195,26 @@ class NetworkObserver {
} }
const httpChannel = oldChannel.QueryInterface(Ci.nsIHttpChannel); const httpChannel = oldChannel.QueryInterface(Ci.nsIHttpChannel);
const loadContext = getLoadContext(httpChannel); const loadContext = getLoadContext(httpChannel);
if (!loadContext || !this._browserSessionCount.has(loadContext.topFrameElement)) { if (
!loadContext ||
!this._browserSessionCount.has(loadContext.topFrameElement)
) {
return; return;
} }
this._redirectMap.set(newChannel, oldChannel); this._redirectMap.set(newChannel, oldChannel);
} }
observeActivity(channel, activityType, activitySubtype, timestamp, extraSizeData, extraStringData) { observeActivity(
if (activityType !== Ci.nsIHttpActivityObserver.ACTIVITY_TYPE_HTTP_TRANSACTION) { channel,
activityType,
activitySubtype,
timestamp,
extraSizeData,
extraStringData
) {
if (
activityType !== Ci.nsIHttpActivityObserver.ACTIVITY_TYPE_HTTP_TRANSACTION
) {
return; return;
} }
if (!(channel instanceof Ci.nsIHttpChannel)) { if (!(channel instanceof Ci.nsIHttpChannel)) {
@ -165,10 +222,16 @@ class NetworkObserver {
} }
const httpChannel = channel.QueryInterface(Ci.nsIHttpChannel); const httpChannel = channel.QueryInterface(Ci.nsIHttpChannel);
const loadContext = getLoadContext(httpChannel); const loadContext = getLoadContext(httpChannel);
if (!loadContext || !this._browserSessionCount.has(loadContext.topFrameElement)) { if (
!loadContext ||
!this._browserSessionCount.has(loadContext.topFrameElement)
) {
return; return;
} }
if (activitySubtype !== Ci.nsIHttpActivityObserver.ACTIVITY_SUBTYPE_TRANSACTION_CLOSE) { if (
activitySubtype !==
Ci.nsIHttpActivityObserver.ACTIVITY_SUBTYPE_TRANSACTION_CLOSE
) {
return; return;
} }
this.emit("requestfinished", httpChannel, { this.emit("requestfinished", httpChannel, {
@ -182,17 +245,30 @@ class NetworkObserver {
} }
const httpChannel = channel.QueryInterface(Ci.nsIHttpChannel); const httpChannel = channel.QueryInterface(Ci.nsIHttpChannel);
const loadContext = getLoadContext(httpChannel); const loadContext = getLoadContext(httpChannel);
if (!loadContext || !this._browserSessionCount.has(loadContext.topFrameElement)) { if (
!loadContext ||
!this._browserSessionCount.has(loadContext.topFrameElement)
) {
return; return;
} }
const extraHeaders = this._extraHTTPHeaders.get(loadContext.topFrameElement); const extraHeaders = this._extraHTTPHeaders.get(
loadContext.topFrameElement
);
if (extraHeaders) { if (extraHeaders) {
for (const header of extraHeaders) { for (const header of extraHeaders) {
httpChannel.setRequestHeader(header.name, header.value, false /* merge */); httpChannel.setRequestHeader(
header.name,
header.value,
false /* merge */
);
} }
} }
const causeType = httpChannel.loadInfo ? httpChannel.loadInfo.externalContentPolicyType : Ci.nsIContentPolicy.TYPE_OTHER; const causeType = httpChannel.loadInfo
const suspendedChannels = this._browserSuspendedChannels.get(loadContext.topFrameElement); ? httpChannel.loadInfo.externalContentPolicyType
: Ci.nsIContentPolicy.TYPE_OTHER;
const suspendedChannels = this._browserSuspendedChannels.get(
loadContext.topFrameElement
);
if (suspendedChannels) { if (suspendedChannels) {
httpChannel.suspend(); httpChannel.suspend();
suspendedChannels.set(requestId(httpChannel), httpChannel); suspendedChannels.set(requestId(httpChannel), httpChannel);
@ -218,13 +294,16 @@ class NetworkObserver {
_onResponse(fromCache, httpChannel, topic) { _onResponse(fromCache, httpChannel, topic) {
const loadContext = getLoadContext(httpChannel); const loadContext = getLoadContext(httpChannel);
if (!loadContext || !this._browserSessionCount.has(loadContext.topFrameElement)) { if (
!loadContext ||
!this._browserSessionCount.has(loadContext.topFrameElement)
) {
return; return;
} }
httpChannel.QueryInterface(Ci.nsIHttpChannelInternal); httpChannel.QueryInterface(Ci.nsIHttpChannelInternal);
const headers = []; const headers = [];
httpChannel.visitResponseHeaders({ httpChannel.visitResponseHeaders({
visitHeader: (name, value) => headers.push({name, value}), visitHeader: (name, value) => headers.push({ name, value }),
}); });
let remoteIPAddress = undefined; let remoteIPAddress = undefined;
@ -262,7 +341,13 @@ class NetworkObserver {
const value = this._browserSessionCount.get(browser) || 0; const value = this._browserSessionCount.get(browser) || 0;
this._browserSessionCount.set(browser, value + 1); this._browserSessionCount.set(browser, value + 1);
if (value === 0) { if (value === 0) {
this._browserResponseStorages.set(browser, new ResponseStorage(MAX_RESPONSE_STORAGE_SIZE, MAX_RESPONSE_STORAGE_SIZE / 10)); this._browserResponseStorages.set(
browser,
new ResponseStorage(
MAX_RESPONSE_STORAGE_SIZE,
MAX_RESPONSE_STORAGE_SIZE / 10
)
);
} }
return () => this.stopTrackingBrowserNetwork(browser); return () => this.stopTrackingBrowserNetwork(browser);
} }
@ -281,11 +366,24 @@ class NetworkObserver {
this._activityDistributor.removeObserver(this); this._activityDistributor.removeObserver(this);
const registrar = Cm.QueryInterface(Ci.nsIComponentRegistrar); const registrar = Cm.QueryInterface(Ci.nsIComponentRegistrar);
registrar.unregisterFactory(SINK_CLASS_ID, this._channelSinkFactory); registrar.unregisterFactory(SINK_CLASS_ID, this._channelSinkFactory);
Services.catMan.deleteCategoryEntry(SINK_CATEGORY_NAME, SINK_CONTRACT_ID, false); Services.catMan.deleteCategoryEntry(
SINK_CATEGORY_NAME,
SINK_CONTRACT_ID,
false
);
Services.obs.removeObserver(this._onRequest, "http-on-modify-request"); Services.obs.removeObserver(this._onRequest, "http-on-modify-request");
Services.obs.removeObserver(this._onExamineResponse, "http-on-examine-response"); Services.obs.removeObserver(
Services.obs.removeObserver(this._onCachedResponse, "http-on-examine-cached-response"); this._onExamineResponse,
Services.obs.removeObserver(this._onCachedResponse, "http-on-examine-merged-response"); "http-on-examine-response"
);
Services.obs.removeObserver(
this._onCachedResponse,
"http-on-examine-cached-response"
);
Services.obs.removeObserver(
this._onCachedResponse,
"http-on-examine-merged-response"
);
} }
} }
@ -335,8 +433,9 @@ function readRequestPostData(httpChannel) {
let text = undefined; let text = undefined;
try { try {
text = NetUtil.readInputStreamToString(iStream, iStream.available()); text = NetUtil.readInputStreamToString(iStream, iStream.available());
const converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"] const converter = Cc[
.createInstance(Ci.nsIScriptableUnicodeConverter); "@mozilla.org/intl/scriptableunicodeconverter"
].createInstance(Ci.nsIScriptableUnicodeConverter);
converter.charset = "UTF-8"; converter.charset = "UTF-8";
text = converter.ConvertToUnicode(text); text = converter.ConvertToUnicode(text);
} catch (err) { } catch (err) {
@ -356,14 +455,18 @@ function getLoadContext(httpChannel) {
let loadContext = null; let loadContext = null;
try { try {
if (httpChannel.notificationCallbacks) { if (httpChannel.notificationCallbacks) {
loadContext = httpChannel.notificationCallbacks.getInterface(Ci.nsILoadContext); loadContext = httpChannel.notificationCallbacks.getInterface(
Ci.nsILoadContext
);
} }
} catch (e) {} } catch (e) {}
try { try {
if (!loadContext && httpChannel.loadGroup) { if (!loadContext && httpChannel.loadGroup) {
loadContext = httpChannel.loadGroup.notificationCallbacks.getInterface(Ci.nsILoadContext); loadContext = httpChannel.loadGroup.notificationCallbacks.getInterface(
Ci.nsILoadContext
);
} }
} catch (e) { } } catch (e) {}
return loadContext; return loadContext;
} }
@ -374,7 +477,7 @@ function requestId(httpChannel) {
function requestHeaders(httpChannel) { function requestHeaders(httpChannel) {
const headers = []; const headers = [];
httpChannel.visitRequestHeaders({ httpChannel.visitRequestHeaders({
visitHeader: (name, value) => headers.push({name, value}), visitHeader: (name, value) => headers.push({ name, value }),
}); });
return headers; return headers;
} }
@ -405,11 +508,15 @@ class ResponseStorage {
return; return;
} }
let encodings = []; let encodings = [];
if ((httpChannel instanceof Ci.nsIEncodedChannel) && httpChannel.contentEncodings && !httpChannel.applyConversion) { if (
httpChannel instanceof Ci.nsIEncodedChannel &&
httpChannel.contentEncodings &&
!httpChannel.applyConversion
) {
const encodingHeader = httpChannel.getResponseHeader("Content-Encoding"); const encodingHeader = httpChannel.getResponseHeader("Content-Encoding");
encodings = encodingHeader.split(/\s*\t*,\s*\t*/); encodings = encodingHeader.split(/\s*\t*,\s*\t*/);
} }
this._responses.set(requestId(httpChannel), {body, encodings}); this._responses.set(requestId(httpChannel), { body, encodings });
this._totalSize += body.length; this._totalSize += body.length;
if (this._totalSize > this._maxTotalSize) { if (this._totalSize > this._maxTotalSize) {
for (let [, response] of this._responses) { for (let [, response] of this._responses) {
@ -425,17 +532,19 @@ class ResponseStorage {
getBase64EncodedResponse(requestId) { getBase64EncodedResponse(requestId) {
const response = this._responses.get(requestId); const response = this._responses.get(requestId);
if (!response) if (!response) {
throw new Error(`Request "${requestId}" is not found`); throw new Error(`Request "${requestId}" is not found`);
if (response.evicted) }
return {base64body: "", evicted: true}; if (response.evicted) {
return { base64body: "", evicted: true };
}
let result = response.body; let result = response.body;
if (response.encodings && response.encodings.length) { if (response.encodings && response.encodings.length) {
for (const encoding of response.encodings) { for (const encoding of response.encodings) {
result = CommonUtils.convertString(result, encoding, "uncompressed"); result = CommonUtils.convertString(result, encoding, "uncompressed");
} }
} }
return {base64body: btoa(result)}; return { base64body: btoa(result) };
} }
} }
@ -460,7 +569,12 @@ class ResponseBodyListener {
this._chunks.push(data); this._chunks.push(data);
oStream.writeBytes(data, aCount); oStream.writeBytes(data, aCount);
this.originalListener.onDataAvailable(aRequest, sStream.newInputStream(0), aOffset, aCount); this.originalListener.onDataAvailable(
aRequest,
sStream.newInputStream(0),
aOffset,
aCount
);
} }
onStartRequest(aRequest) { onStartRequest(aRequest) {
@ -471,25 +585,35 @@ class ResponseBodyListener {
this.originalListener.onStopRequest(aRequest, aStatusCode); this.originalListener.onStopRequest(aRequest, aStatusCode);
const body = this._chunks.join(""); const body = this._chunks.join("");
delete this._chunks; delete this._chunks;
this._networkObserver._onResponseFinished(this._browser, this._httpChannel, body); this._networkObserver._onResponseFinished(
this._browser,
this._httpChannel,
body
);
} }
} }
function getNetworkErrorStatusText(status) { function getNetworkErrorStatusText(status) {
if (!status) if (!status) {
return null; return null;
}
for (const key of Object.keys(Cr)) { for (const key of Object.keys(Cr)) {
if (Cr[key] === status) if (Cr[key] === status) {
return key; return key;
}
} }
// Security module. The following is taken from // Security module. The following is taken from
// https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/How_to_check_the_secruity_state_of_an_XMLHTTPRequest_over_SSL // https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/How_to_check_the_secruity_state_of_an_XMLHTTPRequest_over_SSL
if ((status & 0xff0000) === 0x5a0000) { if ((status & 0xff0000) === 0x5a0000) {
// NSS_SEC errors (happen below the base value because of negative vals) // NSS_SEC errors (happen below the base value because of negative vals)
if ((status & 0xffff) < Math.abs(Ci.nsINSSErrorsService.NSS_SEC_ERROR_BASE)) { if (
(status & 0xffff) <
Math.abs(Ci.nsINSSErrorsService.NSS_SEC_ERROR_BASE)
) {
// The bases are actually negative, so in our positive numeric space, we // The bases are actually negative, so in our positive numeric space, we
// need to subtract the base off our value. // need to subtract the base off our value.
const nssErr = Math.abs(Ci.nsINSSErrorsService.NSS_SEC_ERROR_BASE) - (status & 0xffff); const nssErr =
Math.abs(Ci.nsINSSErrorsService.NSS_SEC_ERROR_BASE) - (status & 0xffff);
switch (nssErr) { switch (nssErr) {
case 11: case 11:
return "SEC_ERROR_EXPIRED_CERTIFICATE"; return "SEC_ERROR_EXPIRED_CERTIFICATE";
@ -511,7 +635,8 @@ function getNetworkErrorStatusText(status) {
return "SEC_ERROR_UNKNOWN"; return "SEC_ERROR_UNKNOWN";
} }
} }
const sslErr = Math.abs(Ci.nsINSSErrorsService.NSS_SSL_ERROR_BASE) - (status & 0xffff); const sslErr =
Math.abs(Ci.nsINSSErrorsService.NSS_SSL_ERROR_BASE) - (status & 0xffff);
switch (sslErr) { switch (sslErr) {
case 3: case 3:
return "SSL_ERROR_NO_CERTIFICATE"; return "SSL_ERROR_NO_CERTIFICATE";

View file

@ -10,15 +10,23 @@ var EXPORTED_SYMBOLS = ["WebSocketServer"];
const CC = Components.Constructor; const CC = Components.Constructor;
const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm"); const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
const {Stream} = ChromeUtils.import("chrome://remote/content/server/Stream.jsm"); const { Stream } = ChromeUtils.import(
const {XPCOMUtils} = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm"); "chrome://remote/content/server/Stream.jsm"
);
const { XPCOMUtils } = ChromeUtils.import(
"resource://gre/modules/XPCOMUtils.jsm"
);
XPCOMUtils.defineLazyGetter(this, "WebSocket", () => { XPCOMUtils.defineLazyGetter(this, "WebSocket", () => {
return Services.appShell.hiddenDOMWindow.WebSocket; return Services.appShell.hiddenDOMWindow.WebSocket;
}); });
const CryptoHash = CC("@mozilla.org/security/hash;1", "nsICryptoHash", "initWithString"); const CryptoHash = CC(
"@mozilla.org/security/hash;1",
"nsICryptoHash",
"initWithString"
);
const threadManager = Cc["@mozilla.org/thread-manager;1"].getService(); const threadManager = Cc["@mozilla.org/thread-manager;1"].getService();
// limit the header size to put an upper bound on allocated memory // limit the header size to put an upper bound on allocated memory
@ -36,26 +44,32 @@ function readLine(input) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
let line = ""; let line = "";
const wait = () => { const wait = () => {
input.asyncWait(stream => { input.asyncWait(
try { stream => {
const amountToRead = HEADER_MAX_LEN - line.length; try {
line += Stream.delimitedRead(input, "\n", amountToRead); const amountToRead = HEADER_MAX_LEN - line.length;
line += Stream.delimitedRead(input, "\n", amountToRead);
if (line.endsWith("\n")) { if (line.endsWith("\n")) {
resolve(line.trimRight()); resolve(line.trimRight());
return; return;
}
if (line.length >= HEADER_MAX_LEN) {
throw new Error(
`Failed to read HTTP header longer than ${HEADER_MAX_LEN} bytes`
);
}
wait();
} catch (ex) {
reject(ex);
} }
},
if (line.length >= HEADER_MAX_LEN) { 0,
throw new Error( 0,
`Failed to read HTTP header longer than ${HEADER_MAX_LEN} bytes`); threadManager.currentThread
} );
wait();
} catch (ex) {
reject(ex);
}
}, 0, 0, threadManager.currentThread);
}; };
wait(); wait();
@ -76,15 +90,20 @@ function writeString(output, data) {
return; return;
} }
output.asyncWait(stream => { output.asyncWait(
try { stream => {
const written = output.write(data, data.length); try {
data = data.slice(written); const written = output.write(data, data.length);
wait(); data = data.slice(written);
} catch (ex) { wait();
reject(ex); } catch (ex) {
} reject(ex);
}, 0, 0, threadManager.currentThread); }
},
0,
0,
threadManager.currentThread
);
}; };
wait(); wait();
@ -120,7 +139,7 @@ const readHttpRequest = async function(input) {
} }
} }
return {requestLine, headers}; return { requestLine, headers };
}; };
/** Write HTTP response (array of strings) to async output stream. */ /** Write HTTP response (array of strings) to async output stream. */
@ -133,7 +152,7 @@ function writeHttpResponse(output, response) {
* Process the WebSocket handshake headers and return the key to be sent in * Process the WebSocket handshake headers and return the key to be sent in
* Sec-WebSocket-Accept response header. * Sec-WebSocket-Accept response header.
*/ */
function processRequest({requestLine, headers}) { function processRequest({ requestLine, headers }) {
const method = requestLine.split(" ")[0]; const method = requestLine.split(" ")[0];
if (method !== "GET") { if (method !== "GET") {
throw new Error("The handshake request must use GET method"); throw new Error("The handshake request must use GET method");
@ -145,19 +164,29 @@ function processRequest({requestLine, headers}) {
} }
const connection = headers.get("connection"); const connection = headers.get("connection");
if (!connection || !connection.split(",").map(t => t.trim()).includes("Upgrade")) { if (
!connection ||
!connection
.split(",")
.map(t => t.trim())
.includes("Upgrade")
) {
throw new Error("The handshake request has incorrect Connection header"); throw new Error("The handshake request has incorrect Connection header");
} }
const version = headers.get("sec-websocket-version"); const version = headers.get("sec-websocket-version");
if (!version || version !== "13") { if (!version || version !== "13") {
throw new Error("The handshake request must have Sec-WebSocket-Version: 13"); throw new Error(
"The handshake request must have Sec-WebSocket-Version: 13"
);
} }
// Compute the accept key // Compute the accept key
const key = headers.get("sec-websocket-key"); const key = headers.get("sec-websocket-key");
if (!key) { if (!key) {
throw new Error("The handshake request must have a Sec-WebSocket-Key header"); throw new Error(
"The handshake request must have a Sec-WebSocket-Key header"
);
} }
return { acceptKey: computeKey(key) }; return { acceptKey: computeKey(key) };
@ -178,7 +207,7 @@ function computeKey(key) {
async function serverHandshake(request, output) { async function serverHandshake(request, output) {
try { try {
// Check and extract info from the request // Check and extract info from the request
const {acceptKey} = processRequest(request); const { acceptKey } = processRequest(request);
// Send response headers // Send response headers
await writeHttpResponse(output, [ await writeHttpResponse(output, [
@ -189,7 +218,7 @@ async function serverHandshake(request, output) {
]); ]);
} catch (error) { } catch (error) {
// Send error response in case of error // Send error response in case of error
await writeHttpResponse(output, [ "HTTP/1.1 400 Bad Request" ]); await writeHttpResponse(output, ["HTTP/1.1 400 Bad Request"]);
throw error; throw error;
} }
} }
@ -205,7 +234,12 @@ async function createWebSocket(transport, input, output) {
}; };
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const socket = WebSocket.createServerWebSocket(null, [], transportProvider, ""); const socket = WebSocket.createServerWebSocket(
null,
[],
transportProvider,
""
);
socket.addEventListener("close", () => { socket.addEventListener("close", () => {
input.close(); input.close();
output.close(); output.close();
@ -233,7 +267,7 @@ async function upgrade(request, response) {
// handle response manually, allowing us to send arbitrary data // handle response manually, allowing us to send arbitrary data
response._powerSeized = true; response._powerSeized = true;
const {transport, input, output} = response._connection; const { transport, input, output } = response._connection;
const headers = new Map(); const headers = new Map();
for (let [key, values] of Object.entries(request._headers._headers)) { for (let [key, values] of Object.entries(request._headers._headers)) {
@ -248,4 +282,4 @@ async function upgrade(request, response) {
return createWebSocket(transport, input, output); return createWebSocket(transport, input, output);
} }
const WebSocketServer = {accept, upgrade}; const WebSocketServer = { accept, upgrade };

View file

@ -8,7 +8,9 @@
var EXPORTED_SYMBOLS = ["WebSocketDebuggerTransport"]; var EXPORTED_SYMBOLS = ["WebSocketDebuggerTransport"];
const {EventEmitter} = ChromeUtils.import("resource://gre/modules/EventEmitter.jsm"); const { EventEmitter } = ChromeUtils.import(
"resource://gre/modules/EventEmitter.jsm"
);
function WebSocketDebuggerTransport(socket) { function WebSocketDebuggerTransport(socket) {
EventEmitter.decorate(this); EventEmitter.decorate(this);
@ -72,7 +74,9 @@ WebSocketDebuggerTransport.prototype = {
onMessage({ data }) { onMessage({ data }) {
if (typeof data !== "string") { if (typeof data !== "string") {
throw new Error("Binary messages are not supported by WebSocket transport"); throw new Error(
"Binary messages are not supported by WebSocket transport"
);
} }
const object = JSON.parse(data); const object = JSON.parse(data);

View file

@ -6,9 +6,15 @@
var EXPORTED_SYMBOLS = ["ContentProcessSession"]; var EXPORTED_SYMBOLS = ["ContentProcessSession"];
const {ContentProcessDomains} = ChromeUtils.import("chrome://remote/content/domains/ContentProcessDomains.jsm"); const { ContentProcessDomains } = ChromeUtils.import(
const {Domains} = ChromeUtils.import("chrome://remote/content/domains/Domains.jsm"); "chrome://remote/content/domains/ContentProcessDomains.jsm"
const {UnknownMethodError} = ChromeUtils.import("chrome://remote/content/Error.jsm"); );
const { Domains } = ChromeUtils.import(
"chrome://remote/content/domains/Domains.jsm"
);
const { UnknownMethodError } = ChromeUtils.import(
"chrome://remote/content/Error.jsm"
);
class ContentProcessSession { class ContentProcessSession {
constructor(messageManager, browsingContext, content, docShell) { constructor(messageManager, browsingContext, content, docShell) {
@ -45,8 +51,8 @@ class ContentProcessSession {
// nsIMessageListener // nsIMessageListener
async receiveMessage({name, data}) { async receiveMessage({ name, data }) {
const {browsingContextId} = data; const { browsingContextId } = data;
// We may have more than one tab loaded in the same process, // We may have more than one tab loaded in the same process,
// and debug the two at the same time. We want to ensure not // and debug the two at the same time. We want to ensure not
@ -59,36 +65,36 @@ class ContentProcessSession {
} }
switch (name) { switch (name) {
case "remote:request": case "remote:request":
try { try {
const {id, domain, command, params} = data.request; const { id, domain, command, params } = data.request;
if (!this.domains.domainSupportsMethod(domain, command)) { if (!this.domains.domainSupportsMethod(domain, command)) {
throw new UnknownMethodError(domain, command); throw new UnknownMethodError(domain, command);
}
const inst = this.domains.get(domain);
const result = await inst[command](params);
this.messageManager.sendAsyncMessage("remote:result", {
browsingContextId,
id,
result,
});
} catch (e) {
this.messageManager.sendAsyncMessage("remote:error", {
browsingContextId,
id: data.request.id,
error: {
name: e.name || "exception",
message: e.message || String(e),
stack: e.stack,
},
});
} }
const inst = this.domains.get(domain); break;
const result = await inst[command](params);
this.messageManager.sendAsyncMessage("remote:result", { case "remote:destroy":
browsingContextId, this.destroy();
id, break;
result,
});
} catch (e) {
this.messageManager.sendAsyncMessage("remote:error", {
browsingContextId,
id: data.request.id,
error: {
name: e.name || "exception",
message: e.message || String(e),
stack: e.stack,
},
});
}
break;
case "remote:destroy":
this.destroy();
break;
} }
} }
} }

View file

@ -6,12 +6,15 @@
var EXPORTED_SYMBOLS = ["Session"]; var EXPORTED_SYMBOLS = ["Session"];
const {ParentProcessDomains} = ChromeUtils.import("chrome://remote/content/domains/ParentProcessDomains.jsm"); const { ParentProcessDomains } = ChromeUtils.import(
const {Domains} = ChromeUtils.import("chrome://remote/content/domains/Domains.jsm"); "chrome://remote/content/domains/ParentProcessDomains.jsm"
const { );
RemoteAgentError, const { Domains } = ChromeUtils.import(
UnknownMethodError, "chrome://remote/content/domains/Domains.jsm"
} = ChromeUtils.import("chrome://remote/content/Error.jsm"); );
const { RemoteAgentError, UnknownMethodError } = ChromeUtils.import(
"chrome://remote/content/Error.jsm"
);
/** /**
* A session represents exactly one client WebSocket connection. * A session represents exactly one client WebSocket connection.
@ -55,7 +58,7 @@ class Session {
this.domains.clear(); this.domains.clear();
} }
async onMessage({id, method, params}) { async onMessage({ id, method, params }) {
try { try {
if (typeof id == "undefined") { if (typeof id == "undefined") {
throw new TypeError("Message missing 'id' field"); throw new TypeError("Message missing 'id' field");
@ -64,7 +67,7 @@ class Session {
throw new TypeError("Message missing 'method' field"); throw new TypeError("Message missing 'method' field");
} }
const {domain, command} = Domains.splitMethod(method); const { domain, command } = Domains.splitMethod(method);
await this.execute(id, domain, command, params); await this.execute(id, domain, command, params);
} catch (e) { } catch (e) {
this.onError(id, e); this.onError(id, e);
@ -93,7 +96,7 @@ class Session {
id, id,
sessionId: this.id, sessionId: this.id,
error: { error: {
message: RemoteAgentError.format(error, {stack: true}), message: RemoteAgentError.format(error, { stack: true }),
}, },
}); });
} }

View file

@ -6,8 +6,12 @@
var EXPORTED_SYMBOLS = ["TabSession"]; var EXPORTED_SYMBOLS = ["TabSession"];
const {Domains} = ChromeUtils.import("chrome://remote/content/domains/Domains.jsm"); const { Domains } = ChromeUtils.import(
const {Session} = ChromeUtils.import("chrome://remote/content/sessions/Session.jsm"); "chrome://remote/content/domains/Domains.jsm"
);
const { Session } = ChromeUtils.import(
"chrome://remote/content/sessions/Session.jsm"
);
/** /**
* A session to communicate with a given tab * A session to communicate with a given tab
@ -36,7 +40,10 @@ class TabSession extends Session {
this.mm.addMessageListener("remote:result", this); this.mm.addMessageListener("remote:result", this);
this.mm.addMessageListener("remote:error", this); this.mm.addMessageListener("remote:error", this);
this.mm.loadFrameScript("chrome://remote/content/sessions/frame-script.js", false); this.mm.loadFrameScript(
"chrome://remote/content/sessions/frame-script.js",
false
);
} }
destructor() { destructor() {
@ -51,7 +58,7 @@ class TabSession extends Session {
this.mm.removeMessageListener("remote:error", this); this.mm.removeMessageListener("remote:error", this);
} }
async onMessage({id, method, params}) { async onMessage({ id, method, params }) {
try { try {
if (typeof id == "undefined") { if (typeof id == "undefined") {
throw new TypeError("Message missing 'id' field"); throw new TypeError("Message missing 'id' field");
@ -60,7 +67,7 @@ class TabSession extends Session {
throw new TypeError("Message missing 'method' field"); throw new TypeError("Message missing 'method' field");
} }
const {domain, command} = Domains.splitMethod(method); const { domain, command } = Domains.splitMethod(method);
if (this.domains.domainSupportsMethod(domain, command)) { if (this.domains.domainSupportsMethod(domain, command)) {
await this.execute(id, domain, command, params); await this.execute(id, domain, command, params);
} else { } else {
@ -74,7 +81,7 @@ class TabSession extends Session {
executeInChild(id, domain, command, params) { executeInChild(id, domain, command, params) {
this.mm.sendAsyncMessage("remote:request", { this.mm.sendAsyncMessage("remote:request", {
browsingContextId: this.browsingContext.id, browsingContextId: this.browsingContext.id,
request: {id, domain, command, params}, request: { id, domain, command, params },
}); });
} }
@ -117,21 +124,21 @@ class TabSession extends Session {
// nsIMessageListener // nsIMessageListener
receiveMessage({name, data}) { receiveMessage({ name, data }) {
const {id, result, event, error} = data; const { id, result, event, error } = data;
switch (name) { switch (name) {
case "remote:result": case "remote:result":
this.onResult(id, result); this.onResult(id, result);
break; break;
case "remote:event": case "remote:event":
this.onEvent(event.eventName, event.params); this.onEvent(event.eventName, event.params);
break; break;
case "remote:error": case "remote:error":
this.onError(id, error); this.onError(id, error);
break; break;
} }
} }
} }

View file

@ -4,8 +4,9 @@
"use strict"; "use strict";
const {ContentProcessSession} = const { ContentProcessSession } = ChromeUtils.import(
ChromeUtils.import("chrome://remote/content/sessions/ContentProcessSession.jsm"); "chrome://remote/content/sessions/ContentProcessSession.jsm"
);
/* global content, docShell */ /* global content, docShell */
new ContentProcessSession(this, docShell.browsingContext, content, docShell); new ContentProcessSession(this, docShell.browsingContext, content, docShell);

View file

@ -6,11 +6,19 @@
var EXPORTED_SYMBOLS = ["MainProcessTarget"]; var EXPORTED_SYMBOLS = ["MainProcessTarget"];
const {Target} = ChromeUtils.import("chrome://remote/content/targets/Target.jsm"); const { Target } = ChromeUtils.import(
const {Session} = ChromeUtils.import("chrome://remote/content/sessions/Session.jsm"); "chrome://remote/content/targets/Target.jsm"
const {RemoteAgent} = ChromeUtils.import("chrome://remote/content/RemoteAgent.jsm"); );
const { Session } = ChromeUtils.import(
"chrome://remote/content/sessions/Session.jsm"
);
const { RemoteAgent } = ChromeUtils.import(
"chrome://remote/content/RemoteAgent.jsm"
);
const UUIDGen = Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUIDGenerator); const UUIDGen = Cc["@mozilla.org/uuid-generator;1"].getService(
Ci.nsIUUIDGenerator
);
/** /**
* The main process Target. * The main process Target.
@ -26,14 +34,16 @@ class MainProcessTarget extends Target {
super(targets, Session); super(targets, Session);
this.type = "browser"; this.type = "browser";
this.id = UUIDGen.generateUUID().toString().slice(1, -1); this.id = UUIDGen.generateUUID()
.toString()
.slice(1, -1);
// Define the HTTP path to query this target // Define the HTTP path to query this target
this.path = `/devtools/browser/${this.id}`; this.path = `/devtools/browser/${this.id}`;
} }
get wsDebuggerURL() { get wsDebuggerURL() {
const {host, port} = RemoteAgent; const { host, port } = RemoteAgent;
return `ws://${host}:${port}${this.path}`; return `ws://${host}:${port}${this.path}`;
} }

View file

@ -6,14 +6,26 @@
var EXPORTED_SYMBOLS = ["TabTarget"]; var EXPORTED_SYMBOLS = ["TabTarget"];
const {Target} = ChromeUtils.import("chrome://remote/content/targets/Target.jsm"); const { Target } = ChromeUtils.import(
const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm"); "chrome://remote/content/targets/Target.jsm"
const {TabSession} = ChromeUtils.import("chrome://remote/content/sessions/TabSession.jsm"); );
const {XPCOMUtils} = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm"); const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
const {RemoteAgent} = ChromeUtils.import("chrome://remote/content/RemoteAgent.jsm"); const { TabSession } = ChromeUtils.import(
"chrome://remote/content/sessions/TabSession.jsm"
);
const { XPCOMUtils } = ChromeUtils.import(
"resource://gre/modules/XPCOMUtils.jsm"
);
const { RemoteAgent } = ChromeUtils.import(
"chrome://remote/content/RemoteAgent.jsm"
);
XPCOMUtils.defineLazyServiceGetter(this, "Favicons", XPCOMUtils.defineLazyServiceGetter(
"@mozilla.org/browser/favicon-service;1", "nsIFaviconService"); this,
"Favicons",
"@mozilla.org/browser/favicon-service;1",
"nsIFaviconService"
);
/** /**
* Target for a local tab or a remoted frame. * Target for a local tab or a remoted frame.
@ -110,7 +122,7 @@ class TabTarget extends Target {
} }
get wsDebuggerURL() { get wsDebuggerURL() {
const {host, port} = RemoteAgent; const { host, port } = RemoteAgent;
return `ws://${host}:${port}${this.path}`; return `ws://${host}:${port}${this.path}`;
} }
@ -147,9 +159,6 @@ class TabTarget extends Target {
// XPCOM // XPCOM
get QueryInterface() { get QueryInterface() {
return ChromeUtils.generateQI([ return ChromeUtils.generateQI([Ci.nsIHttpRequestHandler, Ci.nsIObserver]);
Ci.nsIHttpRequestHandler,
Ci.nsIObserver,
]);
} }
} }

View file

@ -6,9 +6,15 @@
var EXPORTED_SYMBOLS = ["Target"]; var EXPORTED_SYMBOLS = ["Target"];
const {Connection} = ChromeUtils.import("chrome://remote/content/Connection.jsm"); const { Connection } = ChromeUtils.import(
const {WebSocketDebuggerTransport} = ChromeUtils.import("chrome://remote/content/server/WebSocketTransport.jsm"); "chrome://remote/content/Connection.jsm"
const {WebSocketServer} = ChromeUtils.import("chrome://remote/content/server/WebSocket.jsm"); );
const { WebSocketDebuggerTransport } = ChromeUtils.import(
"chrome://remote/content/server/WebSocketTransport.jsm"
);
const { WebSocketServer } = ChromeUtils.import(
"chrome://remote/content/server/WebSocket.jsm"
);
/** /**
* Base class for all the Targets. * Base class for all the Targets.
@ -45,8 +51,6 @@ class Target {
// XPCOM // XPCOM
get QueryInterface() { get QueryInterface() {
return ChromeUtils.generateQI([ return ChromeUtils.generateQI([Ci.nsIHttpRequestHandler]);
Ci.nsIHttpRequestHandler,
]);
} }
} }

View file

@ -6,10 +6,18 @@
var EXPORTED_SYMBOLS = ["Targets"]; var EXPORTED_SYMBOLS = ["Targets"];
const {EventEmitter} = ChromeUtils.import("resource://gre/modules/EventEmitter.jsm"); const { EventEmitter } = ChromeUtils.import(
const {MessagePromise} = ChromeUtils.import("chrome://remote/content/Sync.jsm"); "resource://gre/modules/EventEmitter.jsm"
const {TabTarget} = ChromeUtils.import("chrome://remote/content/targets/TabTarget.jsm"); );
const {MainProcessTarget} = ChromeUtils.import("chrome://remote/content/targets/MainProcessTarget.jsm"); const { MessagePromise } = ChromeUtils.import(
"chrome://remote/content/Sync.jsm"
);
const { TabTarget } = ChromeUtils.import(
"chrome://remote/content/targets/TabTarget.jsm"
);
const { MainProcessTarget } = ChromeUtils.import(
"chrome://remote/content/targets/MainProcessTarget.jsm"
);
class Targets { class Targets {
constructor() { constructor() {
@ -91,7 +99,7 @@ class Targets {
return this.mainProcessTarget; return this.mainProcessTarget;
} }
* [Symbol.iterator]() { *[Symbol.iterator]() {
for (const target of this._targets.values()) { for (const target of this._targets.values()) {
yield target; yield target;
} }

View file

@ -28,22 +28,32 @@ add_task(async function testCDP() {
}); });
ok(true, "CDP client has been instantiated"); ok(true, "CDP client has been instantiated");
const {Browser, Log, Page} = client; const { Browser, Log, Page } = client;
ok("Browser" in client, "Browser domain is available"); ok("Browser" in client, "Browser domain is available");
ok("Log" in client, "Log domain is available"); ok("Log" in client, "Log domain is available");
ok("Page" in client, "Page domain is available"); ok("Page" in client, "Page domain is available");
const version = await Browser.getVersion(); const version = await Browser.getVersion();
const { isHeadless } = Cc["@mozilla.org/gfx/info;1"].getService(Ci.nsIGfxInfo); const { isHeadless } = Cc["@mozilla.org/gfx/info;1"].getService(
is(version.product, isHeadless ? "Headless Firefox" : "Firefox", "Browser.getVersion works and depends on headless mode"); Ci.nsIGfxInfo
is(version.userAgent, window.navigator.userAgent, "Browser.getVersion().userAgent is correct"); );
is(
version.product,
isHeadless ? "Headless Firefox" : "Firefox",
"Browser.getVersion works and depends on headless mode"
);
is(
version.userAgent,
window.navigator.userAgent,
"Browser.getVersion().userAgent is correct"
);
// receive console.log messages and print them // receive console.log messages and print them
Log.enable(); Log.enable();
ok(true, "Log domain has been enabled"); ok(true, "Log domain has been enabled");
Log.entryAdded(({entry}) => { Log.entryAdded(({ entry }) => {
const {timestamp, level, text, args} = entry; const { timestamp, level, text, args } = entry;
const msg = text || args.join(" "); const msg = text || args.join(" ");
console.log(`${new Date(timestamp)}\t${level.toUpperCase()}\t${msg}`); console.log(`${new Date(timestamp)}\t${level.toUpperCase()}\t${msg}`);
}); });
@ -55,7 +65,10 @@ add_task(async function testCDP() {
const frameStoppedLoading = Page.frameStoppedLoading(); const frameStoppedLoading = Page.frameStoppedLoading();
const navigatedWithinDocument = Page.navigatedWithinDocument(); const navigatedWithinDocument = Page.navigatedWithinDocument();
const loadEventFired = Page.loadEventFired(); const loadEventFired = Page.loadEventFired();
await Page.navigate({url: "data:text/html;charset=utf-8,test-page<script>console.log('foo');</script><script>'</script>"}); await Page.navigate({
url:
"data:text/html;charset=utf-8,test-page<script>console.log('foo');</script><script>'</script>",
});
ok(true, "A new page has been loaded"); ok(true, "A new page has been loaded");
await loadEventFired; await loadEventFired;

View file

@ -9,13 +9,13 @@ const TEST_URI = "data:text/html;charset=utf-8,<input type=text>";
// Map of key codes used in this test. // Map of key codes used in this test.
const KEYCODES = { const KEYCODES = {
"a": 65, a: 65,
"Backspace": 8, Backspace: 8,
"h": 72, h: 72,
"H": 72, H: 72,
"AltLeft": 18, AltLeft: 18,
"ArrowLeft": 37, ArrowLeft: 37,
"ArrowRight": 39, ArrowRight: 39,
}; };
// Modifier for move forward shortcut is CTRL+RightArrow on Linux/Windows, ALT+RightArrow // Modifier for move forward shortcut is CTRL+RightArrow on Linux/Windows, ALT+RightArrow
@ -72,7 +72,6 @@ add_task(async function() {
await sendArrowKey(Input, "ArrowLeft"); await sendArrowKey(Input, "ArrowLeft");
await checkInputContent("hH", 2); await checkInputContent("hH", 2);
info("Write 'a'"); info("Write 'a'");
await sendTextKey(Input, "a"); await sendTextKey(Input, "a");
await checkInputContent("hHa", 3); await checkInputContent("hHa", 3);
@ -144,15 +143,25 @@ function dispatchKeyEvent(Input, key, type, modifiers = 0) {
} }
async function checkInputContent(expectedValue, expectedCaret) { async function checkInputContent(expectedValue, expectedCaret) {
const { value, caret } = const { value, caret } = await ContentTask.spawn(
await ContentTask.spawn(gBrowser.selectedBrowser, null, function() { gBrowser.selectedBrowser,
null,
function() {
const input = content.document.querySelector("input"); const input = content.document.querySelector("input");
return { value: input.value, caret: input.selectionStart }; return { value: input.value, caret: input.selectionStart };
}); }
);
is(value, expectedValue, `The input value is correct ("${value}"="${expectedValue}")`); is(
is(caret, expectedCaret, value,
`The input caret has the correct index ("${caret}"="${expectedCaret}")`); expectedValue,
`The input value is correct ("${value}"="${expectedValue}")`
);
is(
caret,
expectedCaret,
`The input caret has the correct index ("${caret}"="${expectedCaret}")`
);
} }
async function sendBackspace(Input, expected) { async function sendBackspace(Input, expected) {
@ -182,16 +191,25 @@ async function sendBackspace(Input, expected) {
* await waitForInputEvent(); * await waitForInputEvent();
*/ */
function addInputEventListener(eventName) { function addInputEventListener(eventName) {
return ContentTask.spawn(gBrowser.selectedBrowser, eventName, async (_eventName) => { return ContentTask.spawn(
const input = content.document.querySelector("input"); gBrowser.selectedBrowser,
this.__onInputEvent = eventName,
new Promise(r => input.addEventListener(_eventName, r, { once: true })); async _eventName => {
}); const input = content.document.querySelector("input");
this.__onInputEvent = new Promise(r =>
input.addEventListener(_eventName, r, { once: true })
);
}
);
} }
/** /**
* See documentation for addInputEventListener. * See documentation for addInputEventListener.
*/ */
function waitForInputEvent() { function waitForInputEvent() {
return ContentTask.spawn(gBrowser.selectedBrowser, null, () => this.__onInputEvent); return ContentTask.spawn(
gBrowser.selectedBrowser,
null,
() => this.__onInputEvent
);
} }

View file

@ -12,26 +12,31 @@ add_task(async function() {
await RemoteAgent.listen(Services.io.newURI("http://localhost:9222")); await RemoteAgent.listen(Services.io.newURI("http://localhost:9222"));
const { mainProcessTarget } = RemoteAgent.targets; const { mainProcessTarget } = RemoteAgent.targets;
ok(mainProcessTarget, ok(
"The main process target is instantiated after the call to `listen`"); mainProcessTarget,
"The main process target is instantiated after the call to `listen`"
);
const targetURL = mainProcessTarget.wsDebuggerURL; const targetURL = mainProcessTarget.wsDebuggerURL;
const CDP = await getCDP(); const CDP = await getCDP();
const client = await CDP({"target": targetURL}); const client = await CDP({ target: targetURL });
ok(true, "CDP client has been instantiated"); ok(true, "CDP client has been instantiated");
const {Browser, Target} = client; const { Browser, Target } = client;
ok(Browser, "The main process target exposes Browser domain"); ok(Browser, "The main process target exposes Browser domain");
ok(Target, "The main process target exposes Target domain"); ok(Target, "The main process target exposes Target domain");
const version = await Browser.getVersion(); const version = await Browser.getVersion();
is(version.product, "Firefox", "Browser.getVersion works"); is(version.product, "Firefox", "Browser.getVersion works");
const {webSocketDebuggerUrl} = await CDP.Version(); const { webSocketDebuggerUrl } = await CDP.Version();
is(webSocketDebuggerUrl, targetURL, is(
"Version endpoint refers to the same Main process target"); webSocketDebuggerUrl,
targetURL,
"Version endpoint refers to the same Main process target"
);
await RemoteAgent.close(); await RemoteAgent.close();
}); });

View file

@ -6,13 +6,15 @@
// Test the Network.requestWillBeSent event // Test the Network.requestWillBeSent event
const TEST_URI = "data:text/html;charset=utf-8,default-test-page"; const TEST_URI = "data:text/html;charset=utf-8,default-test-page";
const PAGE_URI = "http://example.com/browser/remote/test/browser/doc_network_requestWillBeSent.html"; const PAGE_URI =
const JS_URI = "http://example.com/browser/remote/test/browser/file_network_requestWillBeSent.js"; "http://example.com/browser/remote/test/browser/doc_network_requestWillBeSent.html";
const JS_URI =
"http://example.com/browser/remote/test/browser/file_network_requestWillBeSent.js";
add_task(async function() { add_task(async function() {
const {client} = await setupTestForUri(TEST_URI); const { client } = await setupTestForUri(TEST_URI);
const {Page, Network} = client; const { Page, Network } = client;
await Network.enable(); await Network.enable();
ok(true, "Network domain has been enabled"); ok(true, "Network domain has been enabled");
@ -25,7 +27,11 @@ add_task(async function() {
case 1: case 1:
is(event.request.url, PAGE_URI, "Got the page request"); is(event.request.url, PAGE_URI, "Got the page request");
is(event.type, "Document", "The page request has 'Document' type"); is(event.type, "Document", "The page request has 'Document' type");
is(event.requestId, event.loaderId, "The page request has requestId = loaderId (puppeteer assumes that to detect the page start request)"); is(
event.requestId,
event.loaderId,
"The page request has requestId = loaderId (puppeteer assumes that to detect the page start request)"
);
break; break;
case 2: case 2:
is(event.request.url, JS_URI, "Got the JS request"); is(event.request.url, JS_URI, "Got the JS request");

View file

@ -11,11 +11,16 @@ add_task(async function testBringToFrontUpdatesSelectedTab() {
is(gBrowser.selectedTab, tab, "Selected tab is the target tab"); is(gBrowser.selectedTab, tab, "Selected tab is the target tab");
info("Open another tab that should become the front tab"); info("Open another tab that should become the front tab");
const otherTab = await BrowserTestUtils.openNewForegroundTab(gBrowser, OTHER_URI); const otherTab = await BrowserTestUtils.openNewForegroundTab(
gBrowser,
OTHER_URI
);
is(gBrowser.selectedTab, otherTab, "Selected tab is now the new tab"); is(gBrowser.selectedTab, otherTab, "Selected tab is now the new tab");
const { Page } = client; const { Page } = client;
info("Call Page.bringToFront() and check that the test tab becomes the selected tab"); info(
"Call Page.bringToFront() and check that the test tab becomes the selected tab"
);
await Page.bringToFront(); await Page.bringToFront();
is(gBrowser.selectedTab, tab, "Selected tab is the target tab again"); is(gBrowser.selectedTab, tab, "Selected tab is the target tab again");
is(tab.ownerGlobal, getFocusedNavigator(), "The initial window is focused"); is(tab.ownerGlobal, getFocusedNavigator(), "The initial window is focused");
@ -39,9 +44,15 @@ add_task(async function testBringToFrontUpdatesFocusedWindow() {
is(otherWindow, getFocusedNavigator(), "The new window is focused"); is(otherWindow, getFocusedNavigator(), "The new window is focused");
const { Page } = client; const { Page } = client;
info("Call Page.bringToFront() and check that the tab window is focused again"); info(
"Call Page.bringToFront() and check that the tab window is focused again"
);
await Page.bringToFront(); await Page.bringToFront();
is(tab.ownerGlobal, getFocusedNavigator(), "The initial window is focused again"); is(
tab.ownerGlobal,
getFocusedNavigator(),
"The initial window is focused again"
);
await client.close(); await client.close();
ok(true, "The client is closed"); ok(true, "The client is closed");

View file

@ -11,9 +11,9 @@ const promises = new Set();
const resolutions = new Map(); const resolutions = new Map();
add_task(async function() { add_task(async function() {
const {client} = await setupTestForUri(TEST_URI); const { client } = await setupTestForUri(TEST_URI);
const {Page} = client; const { Page } = client;
// turn on navigation related events, such as DOMContentLoaded et al. // turn on navigation related events, such as DOMContentLoaded et al.
await Page.enable(); await Page.enable();
@ -45,13 +45,17 @@ add_task(async function() {
info("Test Page.navigate"); info("Test Page.navigate");
recordPromises(); recordPromises();
const url = "http://example.com/browser/remote/test/browser/doc_page_frameNavigated.html"; const url =
"http://example.com/browser/remote/test/browser/doc_page_frameNavigated.html";
const { frameId } = await Page.navigate({ url }); const { frameId } = await Page.navigate({ url });
ok(true, "A new page has been loaded"); ok(true, "A new page has been loaded");
ok(frameId, "Page.navigate returned a frameId"); ok(frameId, "Page.navigate returned a frameId");
is(frameId, frameTree.frame.id, "The Page.navigate's frameId is the same than " + is(
"getFrameTree's one"); frameId,
frameTree.frame.id,
"The Page.navigate's frameId is the same than " + "getFrameTree's one"
);
await assertNavigationEvents({ url, frameId }); await assertNavigationEvents({ url, frameId });
@ -68,7 +72,11 @@ add_task(async function() {
const randomId2 = await getTestTabRandomId(); const randomId2 = await getTestTabRandomId();
ok(!!randomId2, "Test tab has a valid randomId"); ok(!!randomId2, "Test tab has a valid randomId");
isnot(randomId2, randomId1, "Test tab randomId has been updated after reload"); isnot(
randomId2,
randomId1,
"Test tab randomId has been updated after reload"
);
info("Test Page.navigate with the same URL still reloads the current page"); info("Test Page.navigate with the same URL still reloads the current page");
recordPromises(); recordPromises();
@ -80,7 +88,11 @@ add_task(async function() {
const randomId3 = await getTestTabRandomId(); const randomId3 = await getTestTabRandomId();
ok(!!randomId3, "Test tab has a valid randomId"); ok(!!randomId3, "Test tab has a valid randomId");
isnot(randomId3, randomId2, "Test tab randomId has been updated after reload"); isnot(
randomId3,
randomId2,
"Test tab randomId has been updated after reload"
);
await client.close(); await client.close();
ok(true, "The client is closed"); ok(true, "The client is closed");
@ -102,25 +114,44 @@ async function assertNavigationEvents({ url, frameId }) {
"navigatedWithinDocument", "navigatedWithinDocument",
"frameStoppedLoading", "frameStoppedLoading",
]; ];
Assert.deepEqual([...resolutions.keys()], Assert.deepEqual(
expectedResolutions, [...resolutions.keys()],
"Received various Page navigation events in the expected order"); expectedResolutions,
"Received various Page navigation events in the expected order"
);
// Now assert the data exposed by each of these events // Now assert the data exposed by each of these events
const frameNavigated = resolutions.get("frameNavigated"); const frameNavigated = resolutions.get("frameNavigated");
ok(!frameNavigated.frame.parentId, "frameNavigated is for the top level document and" + ok(
" has a null parentId"); !frameNavigated.frame.parentId,
"frameNavigated is for the top level document and" + " has a null parentId"
);
is(frameNavigated.frame.id, frameId, "frameNavigated id is the right one"); is(frameNavigated.frame.id, frameId, "frameNavigated id is the right one");
is(frameNavigated.frame.name, undefined, "frameNavigated name isn't implemented yet"); is(
frameNavigated.frame.name,
undefined,
"frameNavigated name isn't implemented yet"
);
is(frameNavigated.frame.url, url, "frameNavigated url is the right one"); is(frameNavigated.frame.url, url, "frameNavigated url is the right one");
const navigatedWithinDocument = resolutions.get("navigatedWithinDocument"); const navigatedWithinDocument = resolutions.get("navigatedWithinDocument");
is(navigatedWithinDocument.frameId, frameId, "navigatedWithinDocument frameId is " + is(
"the same one"); navigatedWithinDocument.frameId,
is(navigatedWithinDocument.url, url, "navigatedWithinDocument url is the same one"); frameId,
"navigatedWithinDocument frameId is " + "the same one"
);
is(
navigatedWithinDocument.url,
url,
"navigatedWithinDocument url is the same one"
);
const frameStoppedLoading = resolutions.get("frameStoppedLoading"); const frameStoppedLoading = resolutions.get("frameStoppedLoading");
is(frameStoppedLoading.frameId, frameId, "frameStoppedLoading frameId is the same one"); is(
frameStoppedLoading.frameId,
frameId,
"frameStoppedLoading frameId is the same one"
);
promises.clear(); promises.clear();
resolutions.clear(); resolutions.clear();

View file

@ -10,8 +10,8 @@
const TEST_URI = "data:text/html;charset=utf-8,default-test-page"; const TEST_URI = "data:text/html;charset=utf-8,default-test-page";
add_task(async function testCDP() { add_task(async function testCDP() {
const {client} = await setupTestForUri(TEST_URI); const { client } = await setupTestForUri(TEST_URI);
const {Page, Runtime} = client; const { Page, Runtime } = client;
const events = []; const events = [];
function assertReceivedEvents(expected, message) { function assertReceivedEvents(expected, message) {
@ -43,49 +43,84 @@ add_task(async function testCDP() {
ok(context.auxData.isDefault, "The execution context is the default one"); ok(context.auxData.isDefault, "The execution context is the default one");
ok(!!context.auxData.frameId, "The execution context has a frame id set"); ok(!!context.auxData.frameId, "The execution context has a frame id set");
assertReceivedEvents(["executionContextCreated"], "Received only executionContextCreated event after Runtime.enable call"); assertReceivedEvents(
["executionContextCreated"],
"Received only executionContextCreated event after Runtime.enable call"
);
const { frameTree } = await Page.getFrameTree(); const { frameTree } = await Page.getFrameTree();
ok(!!frameTree.frame, "getFrameTree exposes one frame"); ok(!!frameTree.frame, "getFrameTree exposes one frame");
is(frameTree.childFrames.length, 0, "getFrameTree reports no child frame"); is(frameTree.childFrames.length, 0, "getFrameTree reports no child frame");
ok(!!frameTree.frame.id, "getFrameTree's frame has an id"); ok(!!frameTree.frame.id, "getFrameTree's frame has an id");
is(frameTree.frame.url, TEST_URI, "getFrameTree's frame has the right url"); is(frameTree.frame.url, TEST_URI, "getFrameTree's frame has the right url");
is(frameTree.frame.id, context.auxData.frameId, "getFrameTree and executionContextCreated refers about the same frame Id"); is(
frameTree.frame.id,
context.auxData.frameId,
"getFrameTree and executionContextCreated refers about the same frame Id"
);
const onFrameNavigated = Page.frameNavigated(); const onFrameNavigated = Page.frameNavigated();
const onExecutionContextDestroyed = Runtime.executionContextDestroyed(); const onExecutionContextDestroyed = Runtime.executionContextDestroyed();
const onExecutionContextCreated2 = Runtime.executionContextCreated(); const onExecutionContextCreated2 = Runtime.executionContextCreated();
const url = "data:text/html;charset=utf-8,test-page"; const url = "data:text/html;charset=utf-8,test-page";
const { frameId } = await Page.navigate({ url }); const { frameId } = await Page.navigate({ url });
ok(true, "A new page has been loaded"); ok(true, "A new page has been loaded");
ok(frameId, "Page.navigate returned a frameId"); ok(frameId, "Page.navigate returned a frameId");
is(frameId, frameTree.frame.id, "The Page.navigate's frameId is the same than " + is(
"getFrameTree's one"); frameId,
frameTree.frame.id,
"The Page.navigate's frameId is the same than " + "getFrameTree's one"
);
const frameNavigated = await onFrameNavigated; const frameNavigated = await onFrameNavigated;
ok(!frameNavigated.frame.parentId, "frameNavigated is for the top level document and" + ok(
" has a null parentId"); !frameNavigated.frame.parentId,
is(frameNavigated.frame.id, frameId, "frameNavigated id is the same than the one " + "frameNavigated is for the top level document and" + " has a null parentId"
"returned by Page.navigate"); );
is(frameNavigated.frame.name, undefined, "frameNavigated name isn't implemented yet"); is(
is(frameNavigated.frame.url, url, "frameNavigated url is the same being given to " + frameNavigated.frame.id,
"Page.navigate"); frameId,
"frameNavigated id is the same than the one " + "returned by Page.navigate"
);
is(
frameNavigated.frame.name,
undefined,
"frameNavigated name isn't implemented yet"
);
is(
frameNavigated.frame.url,
url,
"frameNavigated url is the same being given to " + "Page.navigate"
);
const { executionContextId } = await onExecutionContextDestroyed; const { executionContextId } = await onExecutionContextDestroyed;
ok(executionContextId, "The destroyed event reports an id"); ok(executionContextId, "The destroyed event reports an id");
is(executionContextId, context.id, "The destroyed event is for the first reported execution context"); is(
executionContextId,
context.id,
"The destroyed event is for the first reported execution context"
);
({ context } = await onExecutionContextCreated2); ({ context } = await onExecutionContextCreated2);
ok(!!context.id, "The execution context has an id"); ok(!!context.id, "The execution context has an id");
ok(context.auxData.isDefault, "The execution context is the default one"); ok(context.auxData.isDefault, "The execution context is the default one");
is(context.auxData.frameId, frameId, "The execution context frame id is the same " + is(
"the one returned by Page.navigate"); context.auxData.frameId,
frameId,
"The execution context frame id is the same " +
"the one returned by Page.navigate"
);
isnot(executionContextId, context.id, "The destroyed id is different from the " + isnot(
"created one"); executionContextId,
context.id,
"The destroyed id is different from the " + "created one"
);
assertReceivedEvents(["executionContextDestroyed", "frameNavigated", "executionContextCreated"], assertReceivedEvents(
"Received frameNavigated between the two execution context events during navigation to another URL"); ["executionContextDestroyed", "frameNavigated", "executionContextCreated"],
"Received frameNavigated between the two execution context events during navigation to another URL"
);
await client.close(); await client.close();
ok(true, "The client is closed"); ok(true, "The client is closed");

View file

@ -9,7 +9,7 @@
const TEST_URI = "data:text/html;charset=utf-8,default-test-page"; const TEST_URI = "data:text/html;charset=utf-8,default-test-page";
add_task(async function() { add_task(async function() {
const {client} = await setupTestForUri(TEST_URI); const { client } = await setupTestForUri(TEST_URI);
const firstContext = await testRuntimeEnable(client); const firstContext = await testRuntimeEnable(client);
const contextId = firstContext.id; const contextId = firstContext.id;
@ -43,7 +43,10 @@ async function testRuntimeEnable({ Runtime }) {
async function testObjectReferences({ Runtime }, contextId) { async function testObjectReferences({ Runtime }, contextId) {
// First create a JS object remotely via Runtime.evaluate // First create a JS object remotely via Runtime.evaluate
const { result } = await Runtime.evaluate({ contextId, expression: "({ foo: 1 })" }); const { result } = await Runtime.evaluate({
contextId,
expression: "({ foo: 1 })",
});
is(result.type, "object", "The type is correct"); is(result.type, "object", "The type is correct");
is(result.subtype, null, "The subtype is null for objects"); is(result.subtype, null, "The subtype is null for objects");
ok(!!result.objectId, "Got an object id"); ok(!!result.objectId, "Got an object id");
@ -57,7 +60,11 @@ async function testObjectReferences({ Runtime }, contextId) {
}); });
is(result2.type, "number", "The type is correct"); is(result2.type, "number", "The type is correct");
is(result2.subtype, null, "The subtype is null for numbers"); is(result2.subtype, null, "The subtype is null for numbers");
is(result2.value, 2, "Updated the existing object and returned the incremented value"); is(
result2.value,
2,
"Updated the existing object and returned the incremented value"
);
// Finally, try to pass this JS object and get it back. Ensure that it returns // Finally, try to pass this JS object and get it back. Ensure that it returns
// the same object id. Also increment the attribute again. // the same object id. Also increment the attribute again.
@ -83,7 +90,11 @@ async function testObjectReferences({ Runtime }, contextId) {
}); });
is(result4.type, "number", "The type is correct"); is(result4.type, "number", "The type is correct");
is(result4.subtype, null, "The subtype is null for numbers"); is(result4.subtype, null, "The subtype is null for numbers");
is(result4.value, 3, "Updated the existing object and returned the incremented value"); is(
result4.value,
3,
"Updated the existing object and returned the incremented value"
);
} }
async function testExceptions({ Runtime }, executionContextId) { async function testExceptions({ Runtime }, executionContextId) {
@ -92,28 +103,36 @@ async function testExceptions({ Runtime }, executionContextId) {
executionContextId, executionContextId,
functionDeclaration: "doesNotExists()", functionDeclaration: "doesNotExists()",
}); });
is(exceptionDetails.text, "doesNotExists is not defined", "Exception message is passed to the client"); is(
exceptionDetails.text,
"doesNotExists is not defined",
"Exception message is passed to the client"
);
// Test error when calling the function // Test error when calling the function
({ exceptionDetails } = await Runtime.callFunctionOn({ ({ exceptionDetails } = await Runtime.callFunctionOn({
executionContextId, executionContextId,
functionDeclaration: "() => doesNotExists()", functionDeclaration: "() => doesNotExists()",
})); }));
is(exceptionDetails.text, "doesNotExists is not defined", "Exception message is passed to the client"); is(
exceptionDetails.text,
"doesNotExists is not defined",
"Exception message is passed to the client"
);
} }
async function testReturnByValue({ Runtime }, executionContextId) { async function testReturnByValue({ Runtime }, executionContextId) {
const values = [ const values = [
42, 42,
"42", "42",
42.00, 42.0,
true, true,
false, false,
null, null,
{ foo: true }, { foo: true },
{ foo: { bar: 42, str: "str", array: [1, 2, 3] } }, { foo: { bar: 42, str: "str", array: [1, 2, 3] } },
[ 42, "42", true ], [42, "42", true],
[ { foo: true } ], [{ foo: true }],
]; ];
for (const value of values) { for (const value of values) {
const { result } = await Runtime.callFunctionOn({ const { result } = await Runtime.callFunctionOn({
@ -121,7 +140,11 @@ async function testReturnByValue({ Runtime }, executionContextId) {
functionDeclaration: "() => (" + JSON.stringify(value) + ")", functionDeclaration: "() => (" + JSON.stringify(value) + ")",
returnByValue: true, returnByValue: true,
}); });
Assert.deepEqual(result.value, value, "The returned value is the same than the input value"); Assert.deepEqual(
result.value,
value,
"The returned value is the same than the input value"
);
} }
// Test undefined individually as JSON.stringify doesn't return a string // Test undefined individually as JSON.stringify doesn't return a string
@ -150,7 +173,11 @@ async function testAwaitPromise({ Runtime }, executionContextId) {
functionDeclaration: "() => Promise.reject(42)", functionDeclaration: "() => Promise.reject(42)",
awaitPromise: true, awaitPromise: true,
}); });
is(exceptionDetails.exception.value, 42, "The result is the promise's rejection"); is(
exceptionDetails.exception.value,
42,
"The result is the promise's rejection"
);
// Then check delayed promise resolution // Then check delayed promise resolution
({ result } = await Runtime.callFunctionOn({ ({ result } = await Runtime.callFunctionOn({
@ -165,10 +192,15 @@ async function testAwaitPromise({ Runtime }, executionContextId) {
// And delayed promise rejection // And delayed promise rejection
({ exceptionDetails } = await Runtime.callFunctionOn({ ({ exceptionDetails } = await Runtime.callFunctionOn({
executionContextId, executionContextId,
functionDeclaration: "() => new Promise((_,r) => setTimeout(() => r(42), 0))", functionDeclaration:
"() => new Promise((_,r) => setTimeout(() => r(42), 0))",
awaitPromise: true, awaitPromise: true,
})); }));
is(exceptionDetails.exception.value, 42, "The result is the promise's rejection"); is(
exceptionDetails.exception.value,
42,
"The result is the promise's rejection"
);
// Finally assert promise resolution without awaitPromise // Finally assert promise resolution without awaitPromise
({ result } = await Runtime.callFunctionOn({ ({ result } = await Runtime.callFunctionOn({
@ -195,7 +227,10 @@ async function testAwaitPromise({ Runtime }, executionContextId) {
async function testObjectId({ Runtime }, contextId) { async function testObjectId({ Runtime }, contextId) {
// First create an object via Runtime.evaluate // First create an object via Runtime.evaluate
const { result } = await Runtime.evaluate({ contextId, expression: "({ foo: 42 })" }); const { result } = await Runtime.evaluate({
contextId,
expression: "({ foo: 42 })",
});
is(result.type, "object", "The type is correct"); is(result.type, "object", "The type is correct");
is(result.subtype, null, "The subtype is null for objects"); is(result.subtype, null, "The subtype is null for objects");
ok(!!result.objectId, "Got an object id"); ok(!!result.objectId, "Got an object id");
@ -208,5 +243,9 @@ async function testObjectId({ Runtime }, contextId) {
}); });
is(result2.type, "number", "The type is correct"); is(result2.type, "number", "The type is correct");
is(result2.subtype, null, "The subtype is null for numbers"); is(result2.subtype, null, "The subtype is null for numbers");
is(result2.value, 42, "We have a good proof that the function was ran against the target object"); is(
result2.value,
42,
"We have a good proof that the function was ran against the target object"
);
} }

View file

@ -66,7 +66,7 @@ add_task(async function() {
// First evaluate the expression via Runtime.evaluate in order to generate the // First evaluate the expression via Runtime.evaluate in order to generate the
// CDP's `RemoteObject` for the given expression. A previous test already // CDP's `RemoteObject` for the given expression. A previous test already
// asserted the returned value of Runtime.evaluate, so we can trust this. // asserted the returned value of Runtime.evaluate, so we can trust this.
const { result } = await Runtime.evaluate({ contextId, expression }); const { result } = await Runtime.evaluate({ contextId, expression });
// We then pass this RemoteObject as an argument to Runtime.callFunctionOn. // We then pass this RemoteObject as an argument to Runtime.callFunctionOn.
return Runtime.callFunctionOn({ return Runtime.callFunctionOn({
@ -76,7 +76,11 @@ add_task(async function() {
}); });
} }
for (const fun of [runtimeEvaluate, callFunctionOn, callFunctionOnArguments]) { for (const fun of [
runtimeEvaluate,
callFunctionOn,
callFunctionOnArguments,
]) {
info("Test " + fun.name); info("Test " + fun.name);
await testPrimitiveTypes(fun); await testPrimitiveTypes(fun);
await testUnserializable(fun); await testUnserializable(fun);
@ -114,8 +118,15 @@ async function testRuntimeEnable({ Runtime }) {
} }
async function testEvaluate({ Runtime }, contextId) { async function testEvaluate({ Runtime }, contextId) {
const { result } = await Runtime.evaluate({ contextId, expression: "location.href" }); const { result } = await Runtime.evaluate({
is(result.value, TEST_URI, "Runtime.evaluate works and is against the test page"); contextId,
expression: "location.href",
});
is(
result.value,
TEST_URI,
"Runtime.evaluate works and is against the test page"
);
} }
async function testEvaluateInvalidContextId({ Runtime }, contextId) { async function testEvaluateInvalidContextId({ Runtime }, contextId) {
@ -123,23 +134,40 @@ async function testEvaluateInvalidContextId({ Runtime }, contextId) {
await Runtime.evaluate({ contextId: -1, expression: "" }); await Runtime.evaluate({ contextId: -1, expression: "" });
ok(false, "Evaluate shouldn't pass"); ok(false, "Evaluate shouldn't pass");
} catch (e) { } catch (e) {
ok(e.message.includes("Unable to find execution context with id: -1"), ok(
"Throws with the expected error message"); e.message.includes("Unable to find execution context with id: -1"),
"Throws with the expected error message"
);
} }
} }
async function testCallFunctionOn({ Runtime }, executionContextId) { async function testCallFunctionOn({ Runtime }, executionContextId) {
const { result } = await Runtime.callFunctionOn({ executionContextId, functionDeclaration: "() => location.href" }); const { result } = await Runtime.callFunctionOn({
is(result.value, TEST_URI, "Runtime.callFunctionOn works and is against the test page"); executionContextId,
functionDeclaration: "() => location.href",
});
is(
result.value,
TEST_URI,
"Runtime.callFunctionOn works and is against the test page"
);
} }
async function testCallFunctionOnInvalidContextId({ Runtime }, executionContextId) { async function testCallFunctionOnInvalidContextId(
{ Runtime },
executionContextId
) {
try { try {
await Runtime.callFunctionOn({ executionContextId: -1, functionDeclaration: "" }); await Runtime.callFunctionOn({
executionContextId: -1,
functionDeclaration: "",
});
ok(false, "callFunctionOn shouldn't pass"); ok(false, "callFunctionOn shouldn't pass");
} catch (e) { } catch (e) {
ok(e.message.includes("Unable to find execution context with id: -1"), ok(
"Throws with the expected error message"); e.message.includes("Unable to find execution context with id: -1"),
"Throws with the expected error message"
);
} }
} }
@ -148,7 +176,7 @@ async function testPrimitiveTypes(testFunction) {
for (const expression of expressions) { for (const expression of expressions) {
const { result } = await testFunction(JSON.stringify(expression)); const { result } = await testFunction(JSON.stringify(expression));
is(result.value, expression, `Evaluating primitive '${expression}' works`); is(result.value, expression, `Evaluating primitive '${expression}' works`);
is(result.type, typeof(expression), `${expression} type is correct`); is(result.type, typeof expression, `${expression} type is correct`);
} }
// undefined doesn't work with JSON.stringify, so test it independently // undefined doesn't work with JSON.stringify, so test it independently
@ -169,7 +197,11 @@ async function testUnserializable(testFunction) {
const expressions = ["NaN", "-0", "Infinity", "-Infinity"]; const expressions = ["NaN", "-0", "Infinity", "-Infinity"];
for (const expression of expressions) { for (const expression of expressions) {
const { result } = await testFunction(expression); const { result } = await testFunction(expression);
is(result.unserializableValue, expression, `Evaluating unserializable '${expression}' works`); is(
result.unserializableValue,
expression,
`Evaluating unserializable '${expression}' works`
);
} }
} }
@ -190,29 +222,48 @@ async function testObjectTypes(testFunction) {
{ expression: "new Date()", type: "object", subtype: "date" }, { expression: "new Date()", type: "object", subtype: "date" },
{ expression: "document", type: "object", subtype: "node" }, { expression: "document", type: "object", subtype: "node" },
{ expression: "document.documentElement", type: "object", subtype: "node" }, { expression: "document.documentElement", type: "object", subtype: "node" },
{ expression: "document.createElement('div')", type: "object", subtype: "node" }, {
expression: "document.createElement('div')",
type: "object",
subtype: "node",
},
]; ];
for (const { expression, type, subtype } of expressions) { for (const { expression, type, subtype } of expressions) {
const { result } = await testFunction(expression); const { result } = await testFunction(expression);
is(result.subtype, subtype, `Evaluating '${expression}' has the expected subtype`); is(
result.subtype,
subtype,
`Evaluating '${expression}' has the expected subtype`
);
is(result.type, type, "The type is correct"); is(result.type, type, "The type is correct");
ok(!!result.objectId, "Got an object id"); ok(!!result.objectId, "Got an object id");
} }
} }
async function testThrowError(testFunction) { async function testThrowError(testFunction) {
const { exceptionDetails } = await testFunction("throw new Error('foo')", true); const { exceptionDetails } = await testFunction(
"throw new Error('foo')",
true
);
is(exceptionDetails.text, "foo", "Exception message is passed to the client"); is(exceptionDetails.text, "foo", "Exception message is passed to the client");
} }
async function testThrowValue(testFunction) { async function testThrowValue(testFunction) {
const { exceptionDetails } = await testFunction("throw 'foo'", true); const { exceptionDetails } = await testFunction("throw 'foo'", true);
is(exceptionDetails.exception.type, "string", "Exception type is correct"); is(exceptionDetails.exception.type, "string", "Exception type is correct");
is(exceptionDetails.exception.value, "foo", "Exception value is passed as a RemoteObject"); is(
exceptionDetails.exception.value,
"foo",
"Exception value is passed as a RemoteObject"
);
} }
async function testJSError(testFunction) { async function testJSError(testFunction) {
const { exceptionDetails } = await testFunction("doesNotExists()", true); const { exceptionDetails } = await testFunction("doesNotExists()", true);
is(exceptionDetails.text, "doesNotExists is not defined", "Exception message is passed to the client"); is(
exceptionDetails.text,
"doesNotExists is not defined",
"Exception message is passed to the client"
);
} }

View file

@ -8,7 +8,7 @@
const TEST_URI = "data:text/html;charset=utf-8,default-test-page"; const TEST_URI = "data:text/html;charset=utf-8,default-test-page";
add_task(async function() { add_task(async function() {
const {client} = await setupTestForUri(TEST_URI); const { client } = await setupTestForUri(TEST_URI);
const firstContext = await testRuntimeEnable(client); const firstContext = await testRuntimeEnable(client);
await testEvaluate(client, firstContext); await testEvaluate(client, firstContext);
@ -42,8 +42,15 @@ async function testRuntimeEnable({ Runtime }) {
async function testEvaluate({ Runtime }, previousContext) { async function testEvaluate({ Runtime }, previousContext) {
const contextId = previousContext.id; const contextId = previousContext.id;
const { result } = await Runtime.evaluate({ contextId, expression: "location.href" }); const { result } = await Runtime.evaluate({
is(result.value, TEST_URI, "Runtime.evaluate works and is against the test page"); contextId,
expression: "location.href",
});
is(
result.value,
TEST_URI,
"Runtime.evaluate works and is against the test page"
);
} }
async function testNavigate({ Runtime, Page }, previousContext) { async function testNavigate({ Runtime, Page }, previousContext) {
@ -55,20 +62,39 @@ async function testNavigate({ Runtime, Page }, previousContext) {
const url = "data:text/html;charset=utf-8,test-page"; const url = "data:text/html;charset=utf-8,test-page";
const { frameId } = await Page.navigate({ url }); const { frameId } = await Page.navigate({ url });
ok(true, "A new page has been loaded"); ok(true, "A new page has been loaded");
is(frameId, previousContext.auxData.frameId, "Page.navigate returns the same frameId than executionContextCreated"); is(
frameId,
previousContext.auxData.frameId,
"Page.navigate returns the same frameId than executionContextCreated"
);
const { executionContextId } = await executionContextDestroyed; const { executionContextId } = await executionContextDestroyed;
is(executionContextId, previousContext.id, "The destroyed event reports the previous context id"); is(
executionContextId,
previousContext.id,
"The destroyed event reports the previous context id"
);
const { context } = await executionContextCreated; const { context } = await executionContextCreated;
ok(!!context.id, "The execution context has an id"); ok(!!context.id, "The execution context has an id");
isnot(previousContext.id, context.id, "The new execution context has a different id"); isnot(
previousContext.id,
context.id,
"The new execution context has a different id"
);
ok(context.auxData.isDefault, "The execution context is the default one"); ok(context.auxData.isDefault, "The execution context is the default one");
is(context.auxData.frameId, frameId, "The execution context frame id is the same " + is(
"than the one returned by Page.navigate"); context.auxData.frameId,
frameId,
"The execution context frame id is the same " +
"than the one returned by Page.navigate"
);
isnot(executionContextId, context.id, "The destroyed id is different from the " + isnot(
"created one"); executionContextId,
context.id,
"The destroyed id is different from the " + "created one"
);
return context; return context;
} }
@ -85,15 +111,34 @@ async function testNavigateBack({ Runtime }, firstContext, previousContext) {
gBrowser.selectedBrowser.goBack(); gBrowser.selectedBrowser.goBack();
const { context } = await executionContextCreated; const { context } = await executionContextCreated;
is(context.id, firstContext.id, "The new execution context should be the same than the first one"); is(
context.id,
firstContext.id,
"The new execution context should be the same than the first one"
);
ok(context.auxData.isDefault, "The execution context is the default one"); ok(context.auxData.isDefault, "The execution context is the default one");
is(context.auxData.frameId, firstContext.auxData.frameId, "The execution context frame id is always the same"); is(
context.auxData.frameId,
firstContext.auxData.frameId,
"The execution context frame id is always the same"
);
const { executionContextId } = await executionContextDestroyed; const { executionContextId } = await executionContextDestroyed;
is(executionContextId, previousContext.id, "The destroyed event reports the previous context id"); is(
executionContextId,
previousContext.id,
"The destroyed event reports the previous context id"
);
const { result } = await Runtime.evaluate({ contextId: context.id, expression: "location.href" }); const { result } = await Runtime.evaluate({
is(result.value, TEST_URI, "Runtime.evaluate works and is against the page we just navigated to"); contextId: context.id,
expression: "location.href",
});
is(
result.value,
TEST_URI,
"Runtime.evaluate works and is against the page we just navigated to"
);
} }
async function testNavigateViaLocation({ Runtime }, previousContext) { async function testNavigateViaLocation({ Runtime }, previousContext) {
@ -103,19 +148,33 @@ async function testNavigateViaLocation({ Runtime }, previousContext) {
const executionContextCreated = Runtime.executionContextCreated(); const executionContextCreated = Runtime.executionContextCreated();
const url2 = "data:text/html;charset=utf-8,test-page-2"; const url2 = "data:text/html;charset=utf-8,test-page-2";
await Runtime.evaluate({ contextId: previousContext.id, expression: `window.location = '${url2}';` }); await Runtime.evaluate({
contextId: previousContext.id,
expression: `window.location = '${url2}';`,
});
const { executionContextId } = await executionContextDestroyed; const { executionContextId } = await executionContextDestroyed;
is(executionContextId, previousContext.id, "The destroyed event reports the previous context id"); is(
executionContextId,
previousContext.id,
"The destroyed event reports the previous context id"
);
const { context } = await executionContextCreated; const { context } = await executionContextCreated;
ok(!!context.id, "The execution context has an id"); ok(!!context.id, "The execution context has an id");
ok(context.auxData.isDefault, "The execution context is the default one"); ok(context.auxData.isDefault, "The execution context is the default one");
is(context.auxData.frameId, previousContext.auxData.frameId, "The execution context frame id is the same " + is(
"the one returned by Page.navigate"); context.auxData.frameId,
previousContext.auxData.frameId,
"The execution context frame id is the same " +
"the one returned by Page.navigate"
);
isnot(executionContextId, context.id, "The destroyed id is different from the " + isnot(
"created one"); executionContextId,
context.id,
"The destroyed id is different from the " + "created one"
);
return context; return context;
} }
@ -129,14 +188,24 @@ async function testReload({ Runtime, Page }, previousContext) {
await Page.reload(); await Page.reload();
const { executionContextId } = await executionContextDestroyed; const { executionContextId } = await executionContextDestroyed;
is(executionContextId, previousContext.id, "The destroyed event reports the previous context id"); is(
executionContextId,
previousContext.id,
"The destroyed event reports the previous context id"
);
const { context } = await executionContextCreated; const { context } = await executionContextCreated;
ok(!!context.id, "The execution context has an id"); ok(!!context.id, "The execution context has an id");
ok(context.auxData.isDefault, "The execution context is the default one"); ok(context.auxData.isDefault, "The execution context is the default one");
is(context.auxData.frameId, previousContext.auxData.frameId, "The execution context " + is(
"frame id is the same one"); context.auxData.frameId,
previousContext.auxData.frameId,
"The execution context " + "frame id is the same one"
);
isnot(executionContextId, context.id, "The destroyed id is different from the " + isnot(
"created one"); executionContextId,
context.id,
"The destroyed id is different from the " + "created one"
);
} }

View file

@ -8,7 +8,7 @@
const TEST_URI = "data:text/html;charset=utf-8,default-test-page"; const TEST_URI = "data:text/html;charset=utf-8,default-test-page";
add_task(async function() { add_task(async function() {
const {client} = await setupTestForUri(TEST_URI); const { client } = await setupTestForUri(TEST_URI);
const firstContext = await testRuntimeEnable(client); const firstContext = await testRuntimeEnable(client);
const contextId = firstContext.id; const contextId = firstContext.id;
@ -41,13 +41,23 @@ async function testRuntimeEnable({ Runtime }) {
} }
async function testGetOwnSimpleProperties({ Runtime }, contextId) { async function testGetOwnSimpleProperties({ Runtime }, contextId) {
const { result } = await Runtime.evaluate({ contextId, expression: "({ bool: true, fun() {}, int: 1, object: {}, string: 'foo' })" }); const { result } = await Runtime.evaluate({
contextId,
expression: "({ bool: true, fun() {}, int: 1, object: {}, string: 'foo' })",
});
is(result.subtype, null, "JS Object have no subtype"); is(result.subtype, null, "JS Object have no subtype");
is(result.type, "object", "The type is correct"); is(result.type, "object", "The type is correct");
ok(!!result.objectId, "Got an object id"); ok(!!result.objectId, "Got an object id");
const { result: result2 } = await Runtime.getProperties({ objectId: result.objectId, ownProperties: true }); const { result: result2 } = await Runtime.getProperties({
is(result2.length, 5, "ownProperties=true allows to iterate only over direct object properties (i.e. ignore prototype)"); objectId: result.objectId,
ownProperties: true,
});
is(
result2.length,
5,
"ownProperties=true allows to iterate only over direct object properties (i.e. ignore prototype)"
);
result2.sort((a, b) => a.name > b.name); result2.sort((a, b) => a.name > b.name);
is(result2[0].name, "bool"); is(result2[0].name, "bool");
is(result2[0].configurable, true); is(result2[0].configurable, true);
@ -91,35 +101,55 @@ async function testGetOwnSimpleProperties({ Runtime }, contextId) {
} }
async function testGetPrototypeProperties({ Runtime }, contextId) { async function testGetPrototypeProperties({ Runtime }, contextId) {
const { result } = await Runtime.evaluate({ contextId, expression: "({ foo: 42 })" }); const { result } = await Runtime.evaluate({
contextId,
expression: "({ foo: 42 })",
});
is(result.subtype, null, "JS Object have no subtype"); is(result.subtype, null, "JS Object have no subtype");
is(result.type, "object", "The type is correct"); is(result.type, "object", "The type is correct");
ok(!!result.objectId, "Got an object id"); ok(!!result.objectId, "Got an object id");
const { result: result2 } = await Runtime.getProperties({ objectId: result.objectId, ownProperties: false }); const { result: result2 } = await Runtime.getProperties({
objectId: result.objectId,
ownProperties: false,
});
ok(result2.length > 1, "We have more properties than just the object one"); ok(result2.length > 1, "We have more properties than just the object one");
const foo = result2.find(p => p.name == "foo"); const foo = result2.find(p => p.name == "foo");
ok(foo, "The object property is described"); ok(foo, "The object property is described");
ok(foo.isOwn, "and is reported as 'own' property"); ok(foo.isOwn, "and is reported as 'own' property");
const toString = result2.find(p => p.name == "toString"); const toString = result2.find(p => p.name == "toString");
ok(toString, "Function from Object's prototype are also described like toString"); ok(
toString,
"Function from Object's prototype are also described like toString"
);
ok(!toString.isOwn, "but are reported as not being an 'own' property"); ok(!toString.isOwn, "but are reported as not being an 'own' property");
} }
async function testGetGetterSetterProperties({ Runtime }, contextId) { async function testGetGetterSetterProperties({ Runtime }, contextId) {
const { result } = await Runtime.evaluate({ contextId, expression: "({ get prop() { return this.x; }, set prop(v) { this.x = v; } })" }); const { result } = await Runtime.evaluate({
contextId,
expression:
"({ get prop() { return this.x; }, set prop(v) { this.x = v; } })",
});
is(result.subtype, null, "JS Object have no subtype"); is(result.subtype, null, "JS Object have no subtype");
is(result.type, "object", "The type is correct"); is(result.type, "object", "The type is correct");
ok(!!result.objectId, "Got an object id"); ok(!!result.objectId, "Got an object id");
const { result: result2 } = await Runtime.getProperties({ objectId: result.objectId, ownProperties: true }); const { result: result2 } = await Runtime.getProperties({
objectId: result.objectId,
ownProperties: true,
});
is(result2.length, 1); is(result2.length, 1);
is(result2[0].name, "prop"); is(result2[0].name, "prop");
is(result2[0].configurable, true); is(result2[0].configurable, true);
is(result2[0].enumerable, true); is(result2[0].enumerable, true);
is(result2[0].writable, undefined, "writable is only set for data properties"); is(
result2[0].writable,
undefined,
"writable is only set for data properties"
);
is(result2[0].get.type, "function"); is(result2[0].get.type, "function");
ok(!!result2[0].get.objectId); ok(!!result2[0].get.objectId);
@ -131,7 +161,10 @@ async function testGetGetterSetterProperties({ Runtime }, contextId) {
const { result: result3 } = await Runtime.callFunctionOn({ const { result: result3 } = await Runtime.callFunctionOn({
executionContextId: contextId, executionContextId: contextId,
functionDeclaration: "(set, get) => { set(42); return get(); }", functionDeclaration: "(set, get) => { set(42); return get(); }",
arguments: [{ objectId: result2[0].set.objectId }, { objectId: result2[0].get.objectId }], arguments: [
{ objectId: result2[0].set.objectId },
{ objectId: result2[0].get.objectId },
],
}); });
is(result3.type, "number", "The type is correct"); is(result3.type, "number", "The type is correct");
is(result3.subtype, null, "The subtype is null for numbers"); is(result3.subtype, null, "The subtype is null for numbers");
@ -139,12 +172,18 @@ async function testGetGetterSetterProperties({ Runtime }, contextId) {
} }
async function testGetCustomProperty({ Runtime }, contextId) { async function testGetCustomProperty({ Runtime }, contextId) {
const { result } = await Runtime.evaluate({ contextId, expression: `const obj = {}; Object.defineProperty(obj, "prop", { value: 42 }); obj` }); const { result } = await Runtime.evaluate({
contextId,
expression: `const obj = {}; Object.defineProperty(obj, "prop", { value: 42 }); obj`,
});
is(result.subtype, null, "JS Object have no subtype"); is(result.subtype, null, "JS Object have no subtype");
is(result.type, "object", "The type is correct"); is(result.type, "object", "The type is correct");
ok(!!result.objectId, "Got an object id"); ok(!!result.objectId, "Got an object id");
const { result: result2 } = await Runtime.getProperties({ objectId: result.objectId, ownProperties: true }); const { result: result2 } = await Runtime.getProperties({
objectId: result.objectId,
ownProperties: true,
});
is(result2.length, 1, "We only get the one object's property"); is(result2.length, 1, "We only get the one object's property");
is(result2[0].name, "prop"); is(result2[0].name, "prop");
is(result2[0].configurable, false); is(result2[0].configurable, false);

View file

@ -8,7 +8,7 @@
const TEST_URI = "data:text/html;charset=utf-8,default-test-page"; const TEST_URI = "data:text/html;charset=utf-8,default-test-page";
add_task(async function() { add_task(async function() {
const {client} = await setupTestForUri(TEST_URI); const { client } = await setupTestForUri(TEST_URI);
const firstContext = await testRuntimeEnable(client); const firstContext = await testRuntimeEnable(client);
const contextId = firstContext.id; const contextId = firstContext.id;
@ -38,7 +38,10 @@ async function testRuntimeEnable({ Runtime }) {
} }
async function testObjectRelease({ Runtime }, contextId) { async function testObjectRelease({ Runtime }, contextId) {
const { result } = await Runtime.evaluate({ contextId, expression: "({ foo: 42 })" }); const { result } = await Runtime.evaluate({
contextId,
expression: "({ foo: 42 })",
});
is(result.subtype, null, "JS Object have no subtype"); is(result.subtype, null, "JS Object have no subtype");
is(result.type, "object", "The type is correct"); is(result.type, "object", "The type is correct");
ok(!!result.objectId, "Got an object id"); ok(!!result.objectId, "Got an object id");
@ -71,7 +74,10 @@ async function testObjectRelease({ Runtime }, contextId) {
}); });
ok(false, "callFunctionOn with a released object as argument should throw"); ok(false, "callFunctionOn with a released object as argument should throw");
} catch (e) { } catch (e) {
ok(e.message.includes("Cannot find object with ID:"), "callFunctionOn throws on released argument"); ok(
e.message.includes("Cannot find object with ID:"),
"callFunctionOn throws on released argument"
);
} }
try { try {
await Runtime.callFunctionOn({ await Runtime.callFunctionOn({
@ -80,6 +86,9 @@ async function testObjectRelease({ Runtime }, contextId) {
}); });
ok(false, "callFunctionOn with a released object as target should throw"); ok(false, "callFunctionOn with a released object as target should throw");
} catch (e) { } catch (e) {
ok(e.message.includes("Unable to get the context for object with id"), "callFunctionOn throws on released target"); ok(
e.message.includes("Unable to get the context for object with id"),
"callFunctionOn throws on released target"
);
} }
} }

View file

@ -6,8 +6,8 @@
add_task(async function() { add_task(async function() {
await RemoteAgent.listen(Services.io.newURI("http://localhost:9222")); await RemoteAgent.listen(Services.io.newURI("http://localhost:9222"));
const CDP = await getCDP(); const CDP = await getCDP();
const {webSocketDebuggerUrl} = await CDP.Version(); const { webSocketDebuggerUrl } = await CDP.Version();
const client = await CDP({"target": webSocketDebuggerUrl}); const client = await CDP({ target: webSocketDebuggerUrl });
try { try {
await client.send("Hoobaflooba"); await client.send("Hoobaflooba");

View file

@ -24,12 +24,18 @@ add_task(async function() {
await BrowserTestUtils.browserLoaded(tab.linkedBrowser); await BrowserTestUtils.browserLoaded(tab.linkedBrowser);
let targets = await getTargets(CDP); let targets = await getTargets(CDP);
ok(targets.some(target => target.url == TEST_URI), "Found the tab in target list"); ok(
targets.some(target => target.url == TEST_URI),
"Found the tab in target list"
);
BrowserTestUtils.removeTab(tab); BrowserTestUtils.removeTab(tab);
targets = await getTargets(CDP); targets = await getTargets(CDP);
ok(!targets.some(target => target.url == TEST_URI), "Tab has been removed from the target list"); ok(
!targets.some(target => target.url == TEST_URI),
"Tab has been removed from the target list"
);
await RemoteAgent.close(); await RemoteAgent.close();
}); });

View file

@ -13,18 +13,21 @@ add_task(async function() {
const CDP = await getCDP(); const CDP = await getCDP();
// Connect to the server // Connect to the server
const {webSocketDebuggerUrl} = await CDP.Version(); const { webSocketDebuggerUrl } = await CDP.Version();
const client = await CDP({"target": webSocketDebuggerUrl}); const client = await CDP({ target: webSocketDebuggerUrl });
ok(true, "CDP client has been instantiated"); ok(true, "CDP client has been instantiated");
const {Target} = client; const { Target } = client;
ok("Target" in client, "Target domain is available"); ok("Target" in client, "Target domain is available");
const onTargetsCreated = new Promise(resolve => { const onTargetsCreated = new Promise(resolve => {
let gotTabTarget = false, gotMainTarget = false; let gotTabTarget = false,
gotMainTarget = false;
const unsubscribe = Target.targetCreated(event => { const unsubscribe = Target.targetCreated(event => {
if (event.targetInfo.type == "page" && if (
event.targetInfo.url == gBrowser.selectedBrowser.currentURI.spec) { event.targetInfo.type == "page" &&
event.targetInfo.url == gBrowser.selectedBrowser.currentURI.spec
) {
info("Got the current tab target"); info("Got the current tab target");
gotTabTarget = true; gotTabTarget = true;
} }
@ -48,21 +51,33 @@ add_task(async function() {
// Create a new target so that the test runs against a fresh new tab // Create a new target so that the test runs against a fresh new tab
const targetCreated = Target.targetCreated(); const targetCreated = Target.targetCreated();
const {targetId} = await Target.createTarget(); const { targetId } = await Target.createTarget();
ok(true, `Target created: ${targetId}`); ok(true, `Target created: ${targetId}`);
ok(!!targetId, "createTarget returns a non-empty target id"); ok(!!targetId, "createTarget returns a non-empty target id");
const {targetInfo} = await targetCreated; const { targetInfo } = await targetCreated;
is(targetId, targetInfo.targetId, "createTarget and targetCreated refers to the same target id"); is(
targetId,
targetInfo.targetId,
"createTarget and targetCreated refers to the same target id"
);
is(targetInfo.type, "page", "The target is a page"); is(targetInfo.type, "page", "The target is a page");
const attachedToTarget = Target.attachedToTarget(); const attachedToTarget = Target.attachedToTarget();
const {sessionId} = await Target.attachToTarget({ targetId }); const { sessionId } = await Target.attachToTarget({ targetId });
ok(true, "Target attached"); ok(true, "Target attached");
const attachedEvent = await attachedToTarget; const attachedEvent = await attachedToTarget;
ok(true, "Received Target.attachToTarget event"); ok(true, "Received Target.attachToTarget event");
is(attachedEvent.sessionId, sessionId, "attachedToTarget and attachToTarget returns the same session id"); is(
is(attachedEvent.targetInfo.type, "page", "attachedToTarget creates a tab by default"); attachedEvent.sessionId,
sessionId,
"attachedToTarget and attachToTarget returns the same session id"
);
is(
attachedEvent.targetInfo.type,
"page",
"attachedToTarget creates a tab by default"
);
const onResponse = Target.receivedMessageFromTarget(); const onResponse = Target.receivedMessageFromTarget();
const id = 1; const id = 1;
@ -78,7 +93,10 @@ add_task(async function() {
is(response.sessionId, sessionId, "The response is from the same session"); is(response.sessionId, sessionId, "The response is from the same session");
const responseMessage = JSON.parse(response.message); const responseMessage = JSON.parse(response.message);
is(responseMessage.id, id, "The response is from the same session"); is(responseMessage.id, id, "The response is from the same session");
ok(!!responseMessage.result.frameId, "received the `frameId` out of `Page.navigate` request"); ok(
!!responseMessage.result.frameId,
"received the `frameId` out of `Page.navigate` request"
);
await client.close(); await client.close();
ok(true, "The client is closed"); ok(true, "The client is closed");

View file

@ -13,11 +13,11 @@ add_task(async function() {
const CDP = await getCDP(); const CDP = await getCDP();
// Connect to the server // Connect to the server
const {webSocketDebuggerUrl} = await CDP.Version(); const { webSocketDebuggerUrl } = await CDP.Version();
const client = await CDP({"target": webSocketDebuggerUrl}); const client = await CDP({ target: webSocketDebuggerUrl });
ok(true, "CDP client has been instantiated"); ok(true, "CDP client has been instantiated");
const {Target} = client; const { Target } = client;
ok("Target" in client, "Target domain is available"); ok("Target" in client, "Target domain is available");
// Wait for all Target.targetCreated event. One for each tab, plus the one // Wait for all Target.targetCreated event. One for each tab, plus the one
@ -34,16 +34,24 @@ add_task(async function() {
Target.setDiscoverTargets({ discover: true }); Target.setDiscoverTargets({ discover: true });
await targetsCreated; await targetsCreated;
const {browserContextId} = await Target.createBrowserContext(); const { browserContextId } = await Target.createBrowserContext();
const targetCreated = Target.targetCreated(); const targetCreated = Target.targetCreated();
const {targetId} = await Target.createTarget({ browserContextId }); const { targetId } = await Target.createTarget({ browserContextId });
ok(true, `Target created: ${targetId}`); ok(true, `Target created: ${targetId}`);
ok(!!targetId, "createTarget returns a non-empty target id"); ok(!!targetId, "createTarget returns a non-empty target id");
const {targetInfo} = await targetCreated; const { targetInfo } = await targetCreated;
is(targetId, targetInfo.targetId, "createTarget and targetCreated refers to the same target id"); is(
is(browserContextId, targetInfo.browserContextId, "the created target is reported to be of the same browser context"); targetId,
targetInfo.targetId,
"createTarget and targetCreated refers to the same target id"
);
is(
browserContextId,
targetInfo.browserContextId,
"the created target is reported to be of the same browser context"
);
is(targetInfo.type, "page", "The target is a page"); is(targetInfo.type, "page", "The target is a page");
// Releasing the browser context is going to remove the tab opened when calling createTarget // Releasing the browser context is going to remove the tab opened when calling createTarget

View file

@ -32,7 +32,7 @@ add_task(async function() {
info("Create a new tab and wait for the target to be created"); info("Create a new tab and wait for the target to be created");
const otherTargetCreated = Target.targetCreated(); const otherTargetCreated = Target.targetCreated();
const tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URI); const tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URI);
const {targetInfo} = await otherTargetCreated; const { targetInfo } = await otherTargetCreated;
is(targetInfo.type, "page"); is(targetInfo.type, "page");
const onTabClose = BrowserTestUtils.waitForEvent(tab, "TabClose"); const onTabClose = BrowserTestUtils.waitForEvent(tab, "TabClose");

View file

@ -3,8 +3,12 @@
"use strict"; "use strict";
const {RemoteAgent} = ChromeUtils.import("chrome://remote/content/RemoteAgent.jsm"); const { RemoteAgent } = ChromeUtils.import(
const {RemoteAgentError} = ChromeUtils.import("chrome://remote/content/Error.jsm"); "chrome://remote/content/RemoteAgent.jsm"
);
const { RemoteAgentError } = ChromeUtils.import(
"chrome://remote/content/Error.jsm"
);
/** /**
* Override `add_task` in order to translate chrome-remote-interface exceptions * Override `add_task` in order to translate chrome-remote-interface exceptions
@ -27,7 +31,8 @@ this.add_task = function(test) {
}); });
}; };
const CRI_URI = "http://example.com/browser/remote/test/browser/chrome-remote-interface.js"; const CRI_URI =
"http://example.com/browser/remote/test/browser/chrome-remote-interface.js";
/** /**
* Create a test document in an invisible window. * Create a test document in an invisible window.
@ -59,7 +64,7 @@ async function getCDP() {
script.setAttribute("src", CRI_URI); script.setAttribute("src", CRI_URI);
document.documentElement.appendChild(script); document.documentElement.appendChild(script);
await new Promise(resolve => { await new Promise(resolve => {
script.addEventListener("load", resolve, {once: true}); script.addEventListener("load", resolve, { once: true });
}); });
const window = document.defaultView.wrappedJSObject; const window = document.defaultView.wrappedJSObject;
@ -68,7 +73,7 @@ async function getCDP() {
// library in order to do the cross-domain http request, which, // library in order to do the cross-domain http request, which,
// in a regular Web page, is impossible. // in a regular Web page, is impossible.
window.criRequest = (options, callback) => { window.criRequest = (options, callback) => {
const {host, port, path} = options; const { host, port, path } = options;
const url = `http://${host}:${port}${path}`; const url = `http://${host}:${port}${path}`;
const xhr = new XMLHttpRequest(); const xhr = new XMLHttpRequest();
xhr.open("GET", url, true); xhr.open("GET", url, true);

View file

@ -13,12 +13,14 @@
const puppeteer = require("puppeteer"); const puppeteer = require("puppeteer");
console.log("Calling puppeteer.connect"); console.log("Calling puppeteer.connect");
puppeteer.connect({ browserURL: "http://localhost:9000"}).then(async browser => { puppeteer
console.log("Connect success!"); .connect({ browserURL: "http://localhost:9000" })
.then(async browser => {
console.log("Connect success!");
const page = await browser.newPage(); const page = await browser.newPage();
console.log("page", !!page); console.log("page", !!page);
await page.goto("https://www.mozilla.org/"); await page.goto("https://www.mozilla.org/");
return browser.close(); return browser.close();
}); });

View file

@ -3,8 +3,12 @@
"use strict"; "use strict";
const {Domain} = ChromeUtils.import("chrome://remote/content/domains/Domain.jsm"); const { Domain } = ChromeUtils.import(
const {Domains} = ChromeUtils.import("chrome://remote/content/domains/Domains.jsm"); "chrome://remote/content/domains/Domain.jsm"
);
const { Domains } = ChromeUtils.import(
"chrome://remote/content/domains/Domains.jsm"
);
class MockSession { class MockSession {
onEvent() {} onEvent() {}
@ -20,7 +24,7 @@ add_test(function test_Domains_constructor() {
add_test(function test_Domains_domainSupportsMethod() { add_test(function test_Domains_domainSupportsMethod() {
const modules = { const modules = {
"Foo": class extends Domain { Foo: class extends Domain {
bar() {} bar() {}
}, },
}; };
@ -35,7 +39,7 @@ add_test(function test_Domains_domainSupportsMethod() {
add_test(function test_Domains_get_invalidModule() { add_test(function test_Domains_get_invalidModule() {
Assert.throws(() => { Assert.throws(() => {
const domains = new Domains(noopSession, {Foo: undefined}); const domains = new Domains(noopSession, { Foo: undefined });
domains.get("Foo"); domains.get("Foo");
}, /UnknownMethodError/); }, /UnknownMethodError/);
@ -44,7 +48,7 @@ add_test(function test_Domains_get_invalidModule() {
add_test(function test_Domains_get_missingConstructor() { add_test(function test_Domains_get_missingConstructor() {
Assert.throws(() => { Assert.throws(() => {
const domains = new Domains(noopSession, {Foo: {}}); const domains = new Domains(noopSession, { Foo: {} });
domains.get("Foo"); domains.get("Foo");
}, /TypeError/); }, /TypeError/);
@ -53,7 +57,7 @@ add_test(function test_Domains_get_missingConstructor() {
add_test(function test_Domain_get_superClassNotDomain() { add_test(function test_Domain_get_superClassNotDomain() {
Assert.throws(() => { Assert.throws(() => {
const domains = new Domain(noopSession, {Foo: class {}}); const domains = new Domain(noopSession, { Foo: class {} });
domains.get("Foo"); domains.get("Foo");
}, /TypeError/); }, /TypeError/);
@ -77,7 +81,7 @@ add_test(function test_Domains_get_constructs() {
} }
const session = new Session(); const session = new Session();
const domains = new Domains(session, {Foo}); const domains = new Domains(session, { Foo });
const foo = domains.get("Foo"); const foo = domains.get("Foo");
ok(constructed); ok(constructed);
@ -92,7 +96,7 @@ add_test(function test_Domains_get_constructs() {
add_test(function test_Domains_size() { add_test(function test_Domains_size() {
class Foo extends Domain {} class Foo extends Domain {}
const domains = new Domains(noopSession, {Foo}); const domains = new Domains(noopSession, { Foo });
equal(domains.size, 0); equal(domains.size, 0);
domains.get("Foo"); domains.get("Foo");
@ -109,7 +113,7 @@ add_test(function test_Domains_clear() {
} }
} }
const domains = new Domains(noopSession, {Foo}); const domains = new Domains(noopSession, { Foo });
equal(domains.size, 0); equal(domains.size, 0);
domains.get("Foo"); domains.get("Foo");
@ -124,12 +128,20 @@ add_test(function test_Domains_clear() {
add_test(function test_Domains_splitMethod() { add_test(function test_Domains_splitMethod() {
for (const t of [42, null, true, {}, [], undefined]) { for (const t of [42, null, true, {}, [], undefined]) {
Assert.throws(() => Domains.splitMethod(t), /TypeError/, `${typeof t} throws`); Assert.throws(
() => Domains.splitMethod(t),
/TypeError/,
`${typeof t} throws`
);
} }
for (const s of ["", ".", "foo.", ".bar", "foo.bar.baz"]) { for (const s of ["", ".", "foo.", ".bar", "foo.bar.baz"]) {
Assert.throws(() => Domains.splitMethod(s), /Invalid method format: ".*"/, `"${s}" throws`); Assert.throws(
() => Domains.splitMethod(s),
/Invalid method format: ".*"/,
`"${s}" throws`
);
} }
deepEqual(Domains.splitMethod("foo.bar"), {domain: "foo", command: "bar"}); deepEqual(Domains.splitMethod("foo.bar"), { domain: "foo", command: "bar" });
run_next_test(); run_next_test();
}); });

View file

@ -37,16 +37,19 @@ add_test(function test_RemoteAgentError_notify() {
add_test(function test_RemoteAgentError_toString() { add_test(function test_RemoteAgentError_toString() {
const e = new RemoteAgentError("message"); const e = new RemoteAgentError("message");
equal(e.toString(), RemoteAgentError.format(e)); equal(e.toString(), RemoteAgentError.format(e));
equal(e.toString({stack: true}), RemoteAgentError.format(e, {stack: true})); equal(
e.toString({ stack: true }),
RemoteAgentError.format(e, { stack: true })
);
run_next_test(); run_next_test();
}); });
add_test(function test_RemoteAgentError_format() { add_test(function test_RemoteAgentError_format() {
const {format} = RemoteAgentError; const { format } = RemoteAgentError;
equal(format({name: "HippoError"}), "HippoError"); equal(format({ name: "HippoError" }), "HippoError");
equal(format({name: "HorseError", message: "neigh"}), "HorseError: neigh"); equal(format({ name: "HorseError", message: "neigh" }), "HorseError: neigh");
const dog = { const dog = {
name: "DogError", name: "DogError",
@ -54,11 +57,13 @@ add_test(function test_RemoteAgentError_format() {
stack: " one\ntwo\nthree ", stack: " one\ntwo\nthree ",
}; };
equal(format(dog), "DogError: woof"); equal(format(dog), "DogError: woof");
equal(format(dog, {stack: true}), equal(
`DogError: woof: format(dog, { stack: true }),
`DogError: woof:
one one
two two
three`); three`
);
const cat = { const cat = {
name: "CatError", name: "CatError",
@ -67,23 +72,27 @@ add_test(function test_RemoteAgentError_format() {
cause: dog, cause: dog,
}; };
equal(format(cat), "CatError: meow"); equal(format(cat), "CatError: meow");
equal(format(cat, {stack: true}), equal(
`CatError: meow: format(cat, { stack: true }),
`CatError: meow:
four four
five five
six six
caused by: DogError: woof: caused by: DogError: woof:
one one
two two
three`); three`
);
run_next_test(); run_next_test();
}); });
add_test(function test_RemoteAgentError_fromJSON() { add_test(function test_RemoteAgentError_fromJSON() {
const cdpErr = {message: `TypeError: foo: const cdpErr = {
message: `TypeError: foo:
bar bar
baz`}; baz`,
};
const err = RemoteAgentError.fromJSON(cdpErr); const err = RemoteAgentError.fromJSON(cdpErr);
equal(err.message, "TypeError: foo"); equal(err.message, "TypeError: foo");
@ -101,6 +110,10 @@ add_test(function test_UnsupportedError() {
add_test(function test_UnknownMethodError() { add_test(function test_UnknownMethodError() {
ok(new UnknownMethodError() instanceof RemoteAgentError); ok(new UnknownMethodError() instanceof RemoteAgentError);
ok(new UnknownMethodError("domain").message.endsWith("domain")); ok(new UnknownMethodError("domain").message.endsWith("domain"));
ok(new UnknownMethodError("domain", "command").message.endsWith("domain.command")); ok(
new UnknownMethodError("domain", "command").message.endsWith(
"domain.command"
)
);
run_next_test(); run_next_test();
}); });

View file

@ -3,7 +3,9 @@
"use strict"; "use strict";
const {Session} = ChromeUtils.import("chrome://remote/content/sessions/Session.jsm"); const { Session } = ChromeUtils.import(
"chrome://remote/content/sessions/Session.jsm"
);
const connection = { const connection = {
registerSession: () => {}, registerSession: () => {},
@ -13,11 +15,10 @@ const connection = {
}; };
class MockTarget { class MockTarget {
constructor() { constructor() {}
}
get browsingContext() { get browsingContext() {
return {id: 42}; return { id: 42 };
} }
get mm() { get mm() {