256 lines
7.9 KiB
JavaScript
256 lines
7.9 KiB
JavaScript
/* 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/. */
|
|
|
|
import { WindowGlobalBiDiModule } from "chrome://remote/content/webdriver-bidi/modules/WindowGlobalBiDiModule.sys.mjs";
|
|
|
|
const lazy = {};
|
|
|
|
ChromeUtils.defineESModuleGetters(lazy, {
|
|
ConsoleAPIListener:
|
|
"chrome://remote/content/shared/listeners/ConsoleAPIListener.sys.mjs",
|
|
ConsoleListener:
|
|
"chrome://remote/content/shared/listeners/ConsoleListener.sys.mjs",
|
|
isChromeFrame: "chrome://remote/content/shared/Stack.sys.mjs",
|
|
OwnershipModel: "chrome://remote/content/webdriver-bidi/RemoteValue.sys.mjs",
|
|
setDefaultSerializationOptions:
|
|
"chrome://remote/content/webdriver-bidi/RemoteValue.sys.mjs",
|
|
});
|
|
|
|
class LogModule extends WindowGlobalBiDiModule {
|
|
#consoleAPIListener;
|
|
#consoleMessageListener;
|
|
#subscribedEvents;
|
|
|
|
constructor(messageHandler) {
|
|
super(messageHandler);
|
|
|
|
// Create the console-api listener and listen on "message" events.
|
|
this.#consoleAPIListener = new lazy.ConsoleAPIListener(
|
|
this.messageHandler.innerWindowId
|
|
);
|
|
this.#consoleAPIListener.on("message", this.#onConsoleAPIMessage);
|
|
|
|
// Create the console listener and listen on error messages.
|
|
this.#consoleMessageListener = new lazy.ConsoleListener(
|
|
this.messageHandler.innerWindowId
|
|
);
|
|
this.#consoleMessageListener.on("error", this.#onJavaScriptError);
|
|
|
|
// Set of event names which have active subscriptions.
|
|
this.#subscribedEvents = new Set();
|
|
}
|
|
|
|
destroy() {
|
|
this.#consoleAPIListener.off("message", this.#onConsoleAPIMessage);
|
|
this.#consoleAPIListener.destroy();
|
|
this.#consoleMessageListener.off("error", this.#onJavaScriptError);
|
|
this.#consoleMessageListener.destroy();
|
|
|
|
this.#subscribedEvents = null;
|
|
}
|
|
|
|
#buildSource(realm) {
|
|
return {
|
|
realm: realm.id,
|
|
context: this.messageHandler.context,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Map the internal stacktrace representation to a WebDriver BiDi
|
|
* compatible one.
|
|
*
|
|
* Currently chrome frames will be filtered out until chrome scope
|
|
* is supported (bug 1722679).
|
|
*
|
|
* @param {Array<StackFrame>=} stackTrace
|
|
* Stack frames to process.
|
|
*
|
|
* @returns {object=} Object, containing the list of frames as `callFrames`.
|
|
*/
|
|
#buildStackTrace(stackTrace) {
|
|
if (stackTrace == undefined) {
|
|
return undefined;
|
|
}
|
|
|
|
const callFrames = stackTrace
|
|
.filter(frame => !lazy.isChromeFrame(frame))
|
|
.map(frame => {
|
|
return {
|
|
columnNumber: frame.columnNumber - 1,
|
|
functionName: frame.functionName,
|
|
lineNumber: frame.lineNumber - 1,
|
|
url: frame.filename,
|
|
};
|
|
});
|
|
|
|
return { callFrames };
|
|
}
|
|
|
|
#getLogEntryLevelFromConsoleMethod(method) {
|
|
switch (method) {
|
|
case "assert":
|
|
case "error":
|
|
return "error";
|
|
case "debug":
|
|
case "trace":
|
|
return "debug";
|
|
case "warn":
|
|
return "warn";
|
|
default:
|
|
return "info";
|
|
}
|
|
}
|
|
|
|
#onConsoleAPIMessage = (eventName, data = {}) => {
|
|
const {
|
|
// `arguments` cannot be used as variable name in functions
|
|
arguments: messageArguments,
|
|
// `level` corresponds to the console method used
|
|
level: method,
|
|
stacktrace,
|
|
timeStamp,
|
|
} = data;
|
|
|
|
// Step numbers below refer to the specifications at
|
|
// https://w3c.github.io/webdriver-bidi/#event-log-entryAdded
|
|
|
|
// Translate the console message method to a log.LogEntry level
|
|
const logEntrylevel = this.#getLogEntryLevelFromConsoleMethod(method);
|
|
|
|
// Use the message's timeStamp or fallback on the current time value.
|
|
const timestamp = timeStamp || Date.now();
|
|
|
|
// Start assembling the text representation of the message.
|
|
let text = "";
|
|
|
|
// Formatters have already been applied at this points.
|
|
// message.arguments corresponds to the "formatted args" from the
|
|
// specifications.
|
|
|
|
// 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 = messageArguments || [];
|
|
text += args.map(String).join(" ");
|
|
|
|
const defaultRealm = this.messageHandler.getRealm();
|
|
const serializedArgs = [];
|
|
const seenNodeIds = new Map();
|
|
|
|
// Serialize each arg as remote value.
|
|
for (const arg of args) {
|
|
// Note that we can pass a default realm for now since realms are only
|
|
// involved when creating object references, which will not happen with
|
|
// OwnershipModel.None. This will be revisited in Bug 1742589.
|
|
serializedArgs.push(
|
|
this.serialize(
|
|
Cu.waiveXrays(arg),
|
|
lazy.setDefaultSerializationOptions(),
|
|
lazy.OwnershipModel.None,
|
|
defaultRealm,
|
|
{ seenNodeIds }
|
|
)
|
|
);
|
|
}
|
|
|
|
// Set source to an object which contains realm and browsing context.
|
|
// TODO: Bug 1742589. Use an actual realm from which the event came from.
|
|
const source = this.#buildSource(defaultRealm);
|
|
|
|
// Set stack trace only for certain methods.
|
|
let stackTrace;
|
|
if (["assert", "error", "trace", "warn"].includes(method)) {
|
|
stackTrace = this.#buildStackTrace(stacktrace);
|
|
}
|
|
|
|
// Build the ConsoleLogEntry
|
|
const entry = {
|
|
type: "console",
|
|
method,
|
|
source,
|
|
args: serializedArgs,
|
|
level: logEntrylevel,
|
|
text,
|
|
timestamp,
|
|
stackTrace,
|
|
_extraData: { seenNodeIds },
|
|
};
|
|
|
|
// TODO: Those steps relate to:
|
|
// - emitting associated BrowsingContext. See log.entryAdded full support
|
|
// in https://bugzilla.mozilla.org/show_bug.cgi?id=1724669#c0
|
|
// - handling cases where session doesn't exist or the event is not
|
|
// monitored. The implementation differs from the spec here because we
|
|
// only react to events if there is a session & if the session subscribed
|
|
// to those events.
|
|
|
|
this.emitEvent("log.entryAdded", entry);
|
|
};
|
|
|
|
#onJavaScriptError = (eventName, data = {}) => {
|
|
const { level, message, stacktrace, timeStamp } = data;
|
|
const defaultRealm = this.messageHandler.getRealm();
|
|
|
|
// Build the JavascriptLogEntry
|
|
const entry = {
|
|
type: "javascript",
|
|
level,
|
|
// TODO: Bug 1742589. Use an actual realm from which the event came from.
|
|
source: this.#buildSource(defaultRealm),
|
|
text: message,
|
|
timestamp: timeStamp || Date.now(),
|
|
stackTrace: this.#buildStackTrace(stacktrace),
|
|
};
|
|
|
|
this.emitEvent("log.entryAdded", entry);
|
|
};
|
|
|
|
#subscribeEvent(event) {
|
|
if (event === "log.entryAdded") {
|
|
this.#consoleAPIListener.startListening();
|
|
this.#consoleMessageListener.startListening();
|
|
this.#subscribedEvents.add(event);
|
|
}
|
|
}
|
|
|
|
#unsubscribeEvent(event) {
|
|
if (event === "log.entryAdded") {
|
|
this.#consoleAPIListener.stopListening();
|
|
this.#consoleMessageListener.stopListening();
|
|
this.#subscribedEvents.delete(event);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Internal commands
|
|
*/
|
|
|
|
_applySessionData(params) {
|
|
// TODO: Bug 1775231. Move this logic to a shared module or an abstract
|
|
// class.
|
|
const { category } = params;
|
|
if (category === "event") {
|
|
const filteredSessionData = params.sessionData.filter(item =>
|
|
this.messageHandler.matchesContext(item.contextDescriptor)
|
|
);
|
|
for (const event of this.#subscribedEvents.values()) {
|
|
const hasSessionItem = filteredSessionData.some(
|
|
item => item.value === event
|
|
);
|
|
// If there are no session items for this context, we should unsubscribe from the event.
|
|
if (!hasSessionItem) {
|
|
this.#unsubscribeEvent(event);
|
|
}
|
|
}
|
|
|
|
// Subscribe to all events, which have an item in SessionData.
|
|
for (const { value } of filteredSessionData) {
|
|
this.#subscribeEvent(value);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
export const log = LogModule;
|