зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1694145 - [webdriver-bidi] Extract console api observer to dedicated listener class r=webdriver-reviewers,whimboo
Differential Revision: https://phabricator.services.mozilla.com/D132150
This commit is contained in:
Родитель
5d296cef69
Коммит
a9e76a12f8
|
@ -20,6 +20,7 @@ remote.jar:
|
|||
content/shared/TabManager.jsm (shared/TabManager.jsm)
|
||||
content/shared/WebSocketConnection.jsm (shared/WebSocketConnection.jsm)
|
||||
content/shared/WindowManager.jsm (shared/WindowManager.jsm)
|
||||
content/shared/listeners/ConsoleAPIListener.jsm (shared/listeners/ConsoleAPIListener.jsm)
|
||||
content/shared/listeners/ConsoleListener.jsm (shared/listeners/ConsoleListener.jsm)
|
||||
|
||||
# shared modules (messagehandler architecture)
|
||||
|
|
|
@ -0,0 +1,103 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
const EXPORTED_SYMBOLS = ["ConsoleAPIListener"];
|
||||
|
||||
const { XPCOMUtils } = ChromeUtils.import(
|
||||
"resource://gre/modules/XPCOMUtils.jsm"
|
||||
);
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetters(this, {
|
||||
EventEmitter: "resource://gre/modules/EventEmitter.jsm",
|
||||
Services: "resource://gre/modules/Services.jsm",
|
||||
});
|
||||
|
||||
/**
|
||||
* The ConsoleAPIListener can be used to listen for messages coming from console
|
||||
* API usage in a given windowGlobal, eg. console.log, console.error, ...
|
||||
*
|
||||
* Example:
|
||||
* ```
|
||||
* const listener = new ConsoleAPIListener(innerWindowId);
|
||||
* listener.on("message", onConsoleAPIMessage);
|
||||
* listener.startListening();
|
||||
*
|
||||
* const onConsoleAPIMessage = (eventName, data = {}) => {
|
||||
* const { arguments: msgArguments, level, rawMessage, timeStamp } = data;
|
||||
* ...
|
||||
* };
|
||||
* ```
|
||||
*
|
||||
* @emits message
|
||||
* The ConsoleAPIListener emits "message" events, with the following object as
|
||||
* payload:
|
||||
* - `message` property pointing to the wrappedJSObject of the raw message
|
||||
* - `rawMessage` property pointing to the raw message
|
||||
*/
|
||||
class ConsoleAPIListener {
|
||||
#innerWindowId;
|
||||
#listening;
|
||||
|
||||
/**
|
||||
* Create a new ConsolerListener instance.
|
||||
*
|
||||
* @param {Number} innerWindowId
|
||||
* The inner window id to filter the messages for.
|
||||
*/
|
||||
constructor(innerWindowId) {
|
||||
EventEmitter.decorate(this);
|
||||
|
||||
this.#listening = false;
|
||||
this.#innerWindowId = innerWindowId;
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.stopListening();
|
||||
}
|
||||
|
||||
startListening() {
|
||||
if (this.#listening) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Bug 1731574: Retrieve cached messages first before registering the
|
||||
// listener to avoid duplicated messages.
|
||||
Services.obs.addObserver(
|
||||
this.#onConsoleAPIMessage,
|
||||
"console-api-log-event"
|
||||
);
|
||||
this.#listening = true;
|
||||
}
|
||||
|
||||
stopListening() {
|
||||
if (!this.#listening) {
|
||||
return;
|
||||
}
|
||||
|
||||
Services.obs.removeObserver(
|
||||
this.#onConsoleAPIMessage,
|
||||
"console-api-log-event"
|
||||
);
|
||||
this.#listening = false;
|
||||
}
|
||||
|
||||
#onConsoleAPIMessage = message => {
|
||||
const messageObject = message.wrappedJSObject;
|
||||
|
||||
if (messageObject.innerID !== this.#innerWindowId) {
|
||||
// If the message doesn't match the innerWindowId of the current context
|
||||
// ignore it.
|
||||
return;
|
||||
}
|
||||
|
||||
this.emit("message", {
|
||||
arguments: messageObject.arguments,
|
||||
level: messageObject.level,
|
||||
rawMessage: message,
|
||||
timeStamp: messageObject.timeStamp,
|
||||
});
|
||||
};
|
||||
}
|
|
@ -118,7 +118,7 @@ class ConsoleListener {
|
|||
level,
|
||||
message: message.errorMessage,
|
||||
rawMessage: message,
|
||||
timestamp: message.timestamp || Date.now(),
|
||||
timeStamp: message.timeStamp,
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
@ -4,5 +4,6 @@ subsuite = remote
|
|||
prefs =
|
||||
remote.messagehandler.modulecache.useBrowserTestRoot=true
|
||||
|
||||
[browser_ConsoleAPIListener.js]
|
||||
[browser_ConsoleListener.js]
|
||||
|
||||
|
|
|
@ -0,0 +1,90 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
const TESTS = [
|
||||
{ method: "log", args: ["log1"] },
|
||||
{ method: "log", args: ["log2", "log3"] },
|
||||
{ method: "log", args: [[1, 2, 3], { someProperty: "someValue" }] },
|
||||
{ method: "warn", args: ["warn1"] },
|
||||
{ method: "error", args: ["error1"] },
|
||||
{ method: "info", args: ["info1"] },
|
||||
{ method: "debug", args: ["debug1"] },
|
||||
{ method: "trace", args: ["trace1"] },
|
||||
];
|
||||
|
||||
add_task(async function test_method_and_arguments() {
|
||||
for (const { method, args } of TESTS) {
|
||||
info(`Test ConsoleApiListener for ${JSON.stringify({ method, args })}`);
|
||||
|
||||
const listenerId = await listenToConsoleAPIMessage();
|
||||
await useConsoleInContent(method, args);
|
||||
const consoleMessage = await getConsoleAPIMessage(listenerId);
|
||||
is(consoleMessage.level, method, "Message event has the expected level");
|
||||
ok(
|
||||
Number.isInteger(consoleMessage.timeStamp),
|
||||
"Message event has a valid timestamp"
|
||||
);
|
||||
|
||||
is(
|
||||
consoleMessage.arguments.length,
|
||||
args.length,
|
||||
"Message event has the expected number of arguments"
|
||||
);
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
Assert.deepEqual(
|
||||
consoleMessage.arguments[i],
|
||||
args[i],
|
||||
`Message event has the expected argument at index ${i}`
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
function useConsoleInContent(method, args) {
|
||||
info(`Call console API: console.${method}("${args.join('", "')}");`);
|
||||
return SpecialPowers.spawn(
|
||||
gBrowser.selectedBrowser,
|
||||
[method, args],
|
||||
(_method, _args) => {
|
||||
content.console[_method].apply(content.console, _args);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function listenToConsoleAPIMessage() {
|
||||
info("Listen to a console api message in content");
|
||||
return SpecialPowers.spawn(gBrowser.selectedBrowser, [], async () => {
|
||||
const innerWindowId = content.windowGlobalChild.innerWindowId;
|
||||
const { ConsoleAPIListener } = ChromeUtils.import(
|
||||
"chrome://remote/content/shared/listeners/ConsoleAPIListener.jsm"
|
||||
);
|
||||
const consoleAPIListener = new ConsoleAPIListener(innerWindowId);
|
||||
const onMessage = consoleAPIListener.once("message");
|
||||
consoleAPIListener.startListening();
|
||||
|
||||
const listenerId = Math.random();
|
||||
content[listenerId] = { consoleAPIListener, onMessage };
|
||||
return listenerId;
|
||||
});
|
||||
}
|
||||
|
||||
function getConsoleAPIMessage(listenerId) {
|
||||
info("Retrieve the message event captured for listener: " + listenerId);
|
||||
return SpecialPowers.spawn(
|
||||
gBrowser.selectedBrowser,
|
||||
[listenerId],
|
||||
async _listenerId => {
|
||||
const { consoleAPIListener, onMessage } = content[_listenerId];
|
||||
const message = await onMessage;
|
||||
consoleAPIListener.destroy();
|
||||
// Note: we cannot return message directly here as it contains a
|
||||
// `rawMessage` object which cannot be serialized by SpecialPowers.
|
||||
return {
|
||||
arguments: message.arguments,
|
||||
level: message.level,
|
||||
timeStamp: message.timeStamp,
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
|
@ -37,10 +37,10 @@ async function logMessage(options = {}) {
|
|||
listener = (eventName, data) => {
|
||||
is(eventName, level, "Expected event has been fired");
|
||||
|
||||
const { level: currentLevel, message, timestamp } = data;
|
||||
const { level: currentLevel, message, timeStamp } = data;
|
||||
is(typeof currentLevel, "string", "level is of type string");
|
||||
is(typeof message, "string", "message is of type string");
|
||||
is(typeof timestamp, "number", "timestamp is of type number");
|
||||
is(typeof timeStamp, "number", "timeStamp is of type number");
|
||||
|
||||
resolve(data);
|
||||
};
|
||||
|
|
|
@ -11,8 +11,8 @@ const { XPCOMUtils } = ChromeUtils.import(
|
|||
);
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetters(this, {
|
||||
Services: "resource://gre/modules/Services.jsm",
|
||||
|
||||
ConsoleAPIListener:
|
||||
"chrome://remote/content/shared/listeners/ConsoleAPIListener.jsm",
|
||||
ConsoleListener:
|
||||
"chrome://remote/content/shared/listeners/ConsoleListener.jsm",
|
||||
Module: "chrome://remote/content/shared/messagehandler/Module.jsm",
|
||||
|
@ -20,11 +20,18 @@ XPCOMUtils.defineLazyModuleGetters(this, {
|
|||
});
|
||||
|
||||
class Log extends Module {
|
||||
#consoleAPIListener;
|
||||
#consoleMessageListener;
|
||||
|
||||
constructor(messageHandler) {
|
||||
super(messageHandler);
|
||||
|
||||
// Create the console-api listener and listen on "message" events.
|
||||
this.#consoleAPIListener = new ConsoleAPIListener(
|
||||
this.messageHandler.innerWindowId
|
||||
);
|
||||
this.#consoleAPIListener.on("message", this.#onConsoleAPIMessage);
|
||||
|
||||
// Create the console listener and listen on error messages.
|
||||
this.#consoleMessageListener = new ConsoleListener(
|
||||
this.messageHandler.innerWindowId
|
||||
|
@ -33,13 +40,10 @@ class Log extends Module {
|
|||
}
|
||||
|
||||
destroy() {
|
||||
this.#consoleAPIListener.off("message", this.#onConsoleAPIMessage);
|
||||
this.#consoleAPIListener.destroy();
|
||||
this.#consoleMessageListener.off("error", this.#onJavaScriptError);
|
||||
this.#consoleMessageListener.destroy();
|
||||
|
||||
Services.obs.removeObserver(
|
||||
this.#onConsoleAPILogEvent,
|
||||
"console-api-log-event"
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -58,46 +62,36 @@ class Log extends Module {
|
|||
|
||||
_subscribeEvent(event) {
|
||||
if (event === "log.entryAdded") {
|
||||
this.#consoleAPIListener.startListening();
|
||||
this.#consoleMessageListener.startListening();
|
||||
|
||||
Services.obs.addObserver(
|
||||
this.#onConsoleAPILogEvent,
|
||||
"console-api-log-event"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#onConsoleAPILogEvent = message => {
|
||||
const messageObject = message.wrappedJSObject;
|
||||
|
||||
if (messageObject.innerID !== this.messageHandler.innerWindowId) {
|
||||
// If the message doesn't match the innerWindowId of the current context
|
||||
// ignore it.
|
||||
return;
|
||||
}
|
||||
#onConsoleAPIMessage = (eventName, data = {}) => {
|
||||
const { message } = data;
|
||||
|
||||
// Step numbers below refer to the specifications at
|
||||
// https://w3c.github.io/webdriver-bidi/#event-log-entryAdded
|
||||
|
||||
// 1. The console method used to create the messageObject is stored in the
|
||||
// 1. The console method used to create the message is stored in the
|
||||
// `level` property. Translate it to a log.LogEntry level
|
||||
const method = messageObject.level;
|
||||
const method = message.level;
|
||||
const level = this._getLogEntryLevelFromConsoleMethod(method);
|
||||
|
||||
// 2. Use the message's timeStamp or fallback on the current time value.
|
||||
const timestamp = messageObject.timeStamp || Date.now();
|
||||
const timestamp = message.timeStamp || Date.now();
|
||||
|
||||
// 3. Start assembling the text representation of the message.
|
||||
let text = "";
|
||||
|
||||
// 4. Formatters have already been applied at this points.
|
||||
// messageObject.arguments corresponds to the "formatted args" from the
|
||||
// message.arguments corresponds to the "formatted args" from the
|
||||
// specifications.
|
||||
|
||||
// 5. Concatenate all formatted arguments in text
|
||||
// TODO: For m1 we only support string arguments, so we rely on the builtin
|
||||
// toString for each argument which will be available in message.arguments.
|
||||
const args = messageObject.arguments || [];
|
||||
const args = message.arguments || [];
|
||||
text += args.map(String).join(" ");
|
||||
|
||||
// Step 6 and 7: Serialize each arg as remote value.
|
||||
|
@ -133,13 +127,13 @@ class Log extends Module {
|
|||
};
|
||||
|
||||
#onJavaScriptError = (eventName, data = {}) => {
|
||||
const { level, message, timestamp } = data;
|
||||
const { level, message, timeStamp } = data;
|
||||
|
||||
const entry = {
|
||||
type: "javascript",
|
||||
level,
|
||||
text: message,
|
||||
timestamp,
|
||||
timestamp: timeStamp || Date.now(),
|
||||
// TODO: Bug 1731553
|
||||
stackTrace: undefined,
|
||||
};
|
||||
|
|
Загрузка…
Ссылка в новой задаче