/* 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/. */
// Generated by /devtools/shared/webconsole/GenerateReservedWordsJS.py
loader.lazyRequireGetter( this, "RESERVED_JS_KEYWORDS", "resource://devtools/shared/webconsole/reserved-js-words.js"
);
// Overwrite implemented listeners for workers so that we don't attempt // to load an unsupported module. if (isWorker) {
loader.lazyRequireGetter( this,
["ConsoleAPIListener", "ConsoleServiceListener"], "resource://devtools/server/actors/webconsole/worker-listeners.js", true
);
} else {
loader.lazyRequireGetter( this, "ConsoleAPIListener", "resource://devtools/server/actors/webconsole/listeners/console-api.js", true
);
loader.lazyRequireGetter( this, "ConsoleServiceListener", "resource://devtools/server/actors/webconsole/listeners/console-service.js", true
);
loader.lazyRequireGetter( this, "ConsoleReflowListener", "resource://devtools/server/actors/webconsole/listeners/console-reflow.js", true
);
loader.lazyRequireGetter( this, "DocumentEventsListener", "resource://devtools/server/actors/webconsole/listeners/document-events.js", true
);
}
loader.lazyRequireGetter( this, "ObjectUtils", "resource://devtools/server/actors/object/utils.js"
);
function isObject(value) { return Object(value) === value;
}
/** * The WebConsoleActor implements capabilities needed for the Web Console * feature. * * @constructor * @param object connection * The connection to the client, DevToolsServerConnection. * @param object [targetActor] * Optional, the parent actor.
*/ class WebConsoleActor extends Actor {
constructor(connection, targetActor) { super(connection, webconsoleSpec);
/** * This is used by the ObjectActor to keep track of the depth of grip() calls. * @private * @type number
*/
_gripDepth = null;
/** * Holds a set of all currently registered listeners. * * @private * @type Set
*/
_listeners = null;
/** * The global we work with (this can be a Window, a Worker global or even a Sandbox * for processes and addons). * * @type nsIDOMWindow, WorkerGlobalScope or Sandbox
*/
get global() { if (this.targetActor.isRootActor) { returnthis._getWindowForBrowserConsole();
} returnthis.targetActor.targetGlobal;
}
/** * Get a window to use for the browser console. * * (note that is is also used for browser toolbox and webextension * i.e. all targets flagged with isRootActor=true) * * @private * @return nsIDOMWindow * The window to use, or null if no window could be found.
*/
_getWindowForBrowserConsole() { // Check if our last used chrome window is still live.
let window = this._lastChromeWindow && this._lastChromeWindow.get(); // If not, look for a new one. // In case of WebExtension reload of the background page, the last // chrome window might be a dead wrapper, from which we can't check for window.closed. if (!window || Cu.isDeadWrapper(window) || window.closed) {
window = this.targetActor.window; if (!window) { // Try to find the Browser Console window to use instead.
window = Services.wm.getMostRecentWindow("devtools:webconsole"); // We prefer the normal chrome window over the console window, // so we'll look for those windows in order to replace our reference. const onChromeWindowOpened = () => { // We'll look for this window when someone next requests window()
Services.obs.removeObserver(onChromeWindowOpened, "domwindowopened"); this._lastChromeWindow = null;
};
Services.obs.addObserver(onChromeWindowOpened, "domwindowopened");
}
this._handleNewWindow(window);
}
return window;
}
/** * Store a newly found window on the actor to be used in the future. * * @private * @param nsIDOMWindow window * The window to store on the actor (can be null).
*/
_handleNewWindow(window) { if (window) { if (this._hadChromeWindow) {
Services.console.logStringMessage("Webconsole context has changed");
} this._lastChromeWindow = Cu.getWeakReference(window); this._hadChromeWindow = true;
} else { this._lastChromeWindow = null;
}
}
/** * Whether we've been using a window before. * * @private * @type boolean
*/
_hadChromeWindow = false;
/** * A weak reference to the last chrome window we used to work with. * * @private * @type nsIWeakReference
*/
_lastChromeWindow = null;
// The evalGlobal is used at the scope for JS evaluation.
_evalGlobal = null;
get evalGlobal() { returnthis._evalGlobal || this.global;
}
set evalGlobal(global) { this._evalGlobal = global;
/** * Flag used to track if we are listening for events from the progress * listener of the target actor. We use the progress listener to clear * this.evalGlobal on page navigation. * * @private * @type boolean
*/
_progressListenerActive = false;
/** * Create a grip for the given value. * * @param mixed value * @return object
*/
createValueGrip(value) { return createValueGrip( this.targetActor.threadActor,
value, this.targetActor.objectsPool
);
}
/** * Make a debuggee value for the given value. * * @param mixed value * The value you want to get a debuggee value for. * @param boolean useObjectGlobal * If |true| the object global is determined and added as a debuggee, * otherwise |this.global| is used when makeDebuggeeValue() is invoked. * @return object * Debuggee value for |value|.
*/
makeDebuggeeValue(value, useObjectGlobal) { if (useObjectGlobal && isObject(value)) { try { const global = Cu.getGlobalForObject(value); const dbgGlobal = this.dbg.makeGlobalObjectReference(global); return dbgGlobal.makeDebuggeeValue(value);
} catch (ex) { // The above can throw an exception if value is not an actual object // or 'Object in compartment marked as invisible to Debugger'
}
} const dbgGlobal = this.dbg.makeGlobalObjectReference(this.global); return dbgGlobal.makeDebuggeeValue(value);
}
/** * Create a grip for the given string. * * @param string string * The string you want to create the grip for. * @param object pool * A Pool where the new actor instance is added. * @return object * A LongStringActor object that wraps the given string.
*/
longStringGrip(string, pool) { const actor = new LongStringActor(this.conn, string);
pool.manage(actor); return actor.form();
}
/** * Create a long string grip if needed for the given string. * * @private * @param string string * The string you want to create a long string grip for. * @return string|object * A string is returned if |string| is not a long string. * A LongStringActor grip is returned if |string| is a long string.
*/
_createStringGrip(string) { if (string && stringIsLong(string)) { returnthis.longStringGrip(string, this);
} return string;
}
/** * Returns the latest web console input evaluation. * This is undefined if no evaluations have been completed. * * @return object
*/
getLastConsoleInputEvaluation() { returnthis._lastConsoleInputEvaluation;
}
/** * Preprocess a debugger object (e.g. return the `boundTargetFunction` * debugger object if the given debugger object is a bound function). * * This method is called by both the `inspect` binding implemented * for the webconsole and the one implemented for the devtools API * `browser.devtools.inspectedWindow.eval`.
*/
preprocessDebuggerObject(dbgObj) { // Returns the bound target function on a bound function. if (dbgObj?.isBoundFunction && dbgObj?.boundTargetFunction) { return dbgObj.boundTargetFunction;
}
return dbgObj;
}
/** * This helper is used by the WebExtensionInspectedWindowActor to * inspect an object in the developer toolbox. * * NOTE: shared parts related to preprocess the debugger object (between * this function and the `inspect` webconsole command defined in * "devtools/server/actor/webconsole/utils.js") should be added to * the webconsole actors' `preprocessDebuggerObject` method.
*/
inspectObject(dbgObj, inspectFromAnnotation) {
dbgObj = this.preprocessDebuggerObject(dbgObj); this.emit("inspectObject", {
objectActor: this.createValueGrip(dbgObj),
inspectFromAnnotation,
});
}
// Request handlers for known packet types.
/** * Handler for the "startListeners" request. * * @param array listeners * An array of events to start sent by the Web Console client. * @return object * The response object which holds the startedListeners array.
*/ // eslint-disable-next-line complexity
async startListeners(listeners) { const startedListeners = []; const global = !this.targetActor.isRootActor ? this.global : null; const isTargetActorContentProcess = this.targetActor.targetType === Targets.TYPES.PROCESS;
for (const event of listeners) { switch (event) { case"PageError": // Workers don't support this message type yet if (isWorker) { break;
} if (!this.consoleServiceListener) { this.consoleServiceListener = new ConsoleServiceListener(
global, this.onConsoleServiceMessage,
{
matchExactWindow: this.targetActor.ignoreSubFrames,
}
); this.consoleServiceListener.init();
}
startedListeners.push(event); break; case"ConsoleAPI": if (!this.consoleAPIListener) { // Create the consoleAPIListener // (and apply the filtering options defined in the parent actor). this.consoleAPIListener = new ConsoleAPIListener(
global, this.onConsoleAPICall,
{
matchExactWindow: this.targetActor.ignoreSubFrames,
}
); this.consoleAPIListener.init();
}
startedListeners.push(event); break; case"NetworkActivity": // Workers don't support this message type if (isWorker) { break;
} // Bug 1807650 removed this in favor of the new Watcher/Resources APIs const errorMessage = "NetworkActivity is no longer supported. " + "Instead use Watcher actor's watchResources and listen to NETWORK_EVENT resource";
dump(errorMessage + "\n"); thrownew Error(errorMessage); case"FileActivity": // Workers don't support this message type if (isWorker) { break;
} if (this.global instanceof Ci.nsIDOMWindow) { if (!this.consoleFileActivityListener) { this.consoleFileActivityListener = new ConsoleFileActivityListener(this.global, this);
} this.consoleFileActivityListener.startMonitor();
startedListeners.push(event);
} break; case"ReflowActivity": // Workers don't support this message type if (isWorker) { break;
} if (!this.consoleReflowListener) { this.consoleReflowListener = new ConsoleReflowListener( this.global, this
);
}
startedListeners.push(event); break; case"DocumentEvents": // Workers don't support this message type if (isWorker || isTargetActorContentProcess) { break;
} if (!this.documentEventsListener) { this.documentEventsListener = new DocumentEventsListener( this.targetActor
);
this.documentEventsListener.on("dom-loading", data => this.onDocumentEvent("dom-loading", data)
); this.documentEventsListener.on("dom-interactive", data => this.onDocumentEvent("dom-interactive", data)
); this.documentEventsListener.on("dom-complete", data => this.onDocumentEvent("dom-complete", data)
);
// Update the live list of running listeners
startedListeners.forEach(this._listeners.add, this._listeners);
return {
startedListeners,
};
}
/** * Handler for the "stopListeners" request. * * @param array listeners * An array of events to stop sent by the Web Console client. * @return object * The response packet to send to the client: holds the * stoppedListeners array.
*/
stopListeners(listeners) { const stoppedListeners = [];
// If no specific listeners are requested to be detached, we stop all // listeners. const eventsToDetach = listeners || [ "PageError", "ConsoleAPI", "FileActivity", "ReflowActivity", "DocumentEvents",
];
for (const event of eventsToDetach) { switch (event) { case"PageError": if (this.consoleServiceListener) { this.consoleServiceListener.destroy(); this.consoleServiceListener = null;
}
stoppedListeners.push(event); break; case"ConsoleAPI": if (this.consoleAPIListener) { this.consoleAPIListener.destroy(); this.consoleAPIListener = null;
}
stoppedListeners.push(event); break; case"FileActivity": if (this.consoleFileActivityListener) { this.consoleFileActivityListener.stopMonitor(); this.consoleFileActivityListener = null;
}
stoppedListeners.push(event); break; case"ReflowActivity": if (this.consoleReflowListener) { this.consoleReflowListener.destroy(); this.consoleReflowListener = null;
}
stoppedListeners.push(event); break; case"DocumentEvents": if (this.documentEventsListener) { this.documentEventsListener.destroy(); this.documentEventsListener = null;
}
stoppedListeners.push(event); break;
}
}
// Update the live list of running listeners
stoppedListeners.forEach(this._listeners.delete, this._listeners);
return { stoppedListeners };
}
/** * Handler for the "getCachedMessages" request. This method sends the cached * error messages and the window.console API calls to the client. * * @param array messageTypes * An array of message types sent by the Web Console client. * @return object * The response packet to send to the client: it holds the cached * messages array.
*/
getCachedMessages(messageTypes) { if (!messageTypes) { return {
error: "missingParameter",
message: "The messageTypes parameter is missing.",
};
}
for (const type of messageTypes) { switch (type) { case"ConsoleAPI": { if (!this.consoleAPIListener) { break;
}
// this.global might not be a window (can be a worker global or a Sandbox), // and in such case performance isn't defined const winStartTime = this.global?.performance?.timing?.navigationStart;
const cache = this.consoleAPIListener.getCachedMessages(
!this.targetActor.isRootActor
);
cache.forEach(cachedMessage => { // Filter out messages that came from a ServiceWorker but happened // before the page was requested. if (
cachedMessage.innerID === "ServiceWorker" &&
winStartTime > cachedMessage.timeStamp
) { return;
}
/** * Handler for the "evaluateJSAsync" request. This method evaluates a given * JavaScript string with an associated `resultID`. * * The result will be returned later as an unsolicited `evaluationResult`, * that can be associated back to this request via the `resultID` field. * * @param object request * The JSON request object received from the Web Console client. * @return object * The response packet to send to with the unique id in the * `resultID` field.
*/
async evaluateJSAsync(request) { const startTime = ChromeUtils.dateNow(); // Use a timestamp instead of a UUID as this code is used by workers, which // don't have access to the UUID XPCOM component. // Also use a counter in order to prevent mixing up response when calling // at the exact same time. const resultID = startTime + "-" + this._evalCounter++;
// Execute the evaluation in the next event loop in order to immediately // reply with the resultID. // // The console input should be evaluated with micro task level != 0, // so that microtask checkpoint isn't performed while evaluating it.
DevToolsUtils.executeSoonWithMicroTask(async () => { try { // Execute the script that may pause.
let response = await this.evaluateJS(request); // Wait for any potential returned Promise.
response = await this._maybeWaitForResponseResult(response);
// Set the timestamp only now, so any messages logged in the expression (e.g. console.log) // can be appended before the result message (unlike the evaluation result, other // console resources are throttled before being handled by the webconsole client, // which might cause some ordering issue). // Use ChromeUtils.dateNow() as it gives us a higher precision than Date.now().
response.timestamp = ChromeUtils.dateNow(); // Finally, emit an unsolicited evaluationResult packet with the evaluation result. this.emit("evaluationResult", {
type: "evaluationResult",
resultID,
startTime,
...response,
});
} catch (e) { const message = `Encountered error while waiting for Helper Result: ${e}\n${e.stack}`;
DevToolsUtils.reportException("evaluateJSAsync", Error(message));
}
}); return { resultID };
}
/** * In order to support async evaluations (e.g. top-level await, …), * we have to be able to handle promises. This method handles waiting for the promise, * and then returns the result. * * @private * @param object response * The response packet to send to with the unique id in the * `resultID` field, and potentially a promise in the `helperResult` or in the * `awaitResult` field. * * @return object * The updated response object.
*/
async _maybeWaitForResponseResult(response) { if (!response?.awaitResult) { return response;
}
let result; try {
result = await response.awaitResult;
// `createValueGrip` expect a debuggee value, while here we have the raw object. // We need to call `makeDebuggeeValue` on it to make it work. const dbgResult = this.makeDebuggeeValue(result);
response.result = this.createValueGrip(dbgResult);
} catch (e) { // The promise was rejected. We let the engine handle this as it will report a // `uncaught exception` error.
response.topLevelAwaitRejected = true;
}
// Remove the promise from the response object. delete response.awaitResult;
return response;
}
/** * Handler for the "evaluateJS" request. This method evaluates the given * JavaScript string and sends back the result. * * @param object request * The JSON request object received from the Web Console client. * @return object * The evaluation response packet.
*/
evaluateJS(request) { const input = request.text;
const evalOptions = {
frameActor: request.frameActor,
url: request.url,
innerWindowID: request.innerWindowID,
selectedNodeActor: request.selectedNodeActor,
selectedObjectActor: request.selectedObjectActor,
eager: request.eager,
bindings: request.bindings,
lineNumber: request.lineNumber, // This flag is set to true in most cases as we consider most evaluations as internal and: // * prevent any breakpoint from being triggerred when evaluating the JS input // * prevent spawning Debugger.Source for the evaluated JS and showing it in Debugger UI // This is only set to false when evaluating the console input.
disableBreaks: !!request.disableBreaks, // Optional flag, to be set to true when Console Commands should override local symbols with // the same name. Like if the page defines `$`, the evaluated string will use the `$` implemented // by the console command instead of the page's function.
preferConsoleCommandsOverLocalSymbols:
!!request.preferConsoleCommandsOverLocalSymbols,
};
const { mapped } = request;
// Set a flag on the thread actor which indicates an evaluation is being // done for the client. This is used to disable all types of breakpoints for all sources // via `disabledBreaks`. When this flag is used, `reportExceptionsWhenBreaksAreDisabled` // allows to still pause on exceptions. this.targetActor.threadActor.insideClientEvaluation = evalOptions;
returnnew Promise((resolve, reject) => { // Queue up a task to run in the next tick so any microtask created by the evaluated // expression has the time to be run. // e.g. in : // ``` // const promiseThenCb = result => "result: " + result; // new Promise(res => res("hello")).then(promiseThenCb) // ``` // we want`promiseThenCb` to have run before handling the result.
DevToolsUtils.executeSoon(() => { try { const result = this.prepareEvaluationResult(
evalInfo,
input,
request.eager,
mapped,
request.evalInTracer
);
resolve(result);
} catch (err) {
reject(err);
}
});
});
}
if (exceptionStack) { // Set the frame based on the topmost stack frame for the exception. const {
filename: source,
sourceId,
lineNumber: line,
columnNumber: column,
} = exceptionStack[0];
frame = { source, sourceId, line, column };
errorMessage = String(error); if (typeof error === "object" && error !== null) { try {
errorMessage = DevToolsUtils.callPropertyOnObject(
error, "toString"
);
} catch (e) { // If the debuggee is not allowed to access the "toString" property // of the error object, calling this property from the debuggee's // compartment will fail. The debugger should show the error object // as it is seen by the debuggee, so this behavior is correct. // // Unfortunately, we have at least one test that assumes calling the // "toString" property of an error object will succeed if the // debugger is allowed to access it, regardless of whether the // debuggee is allowed to access it or not. // // To accomodate these tests, if calling the "toString" property // from the debuggee compartment fails, we rewrap the error object // in the debugger's compartment, and then call the "toString" // property from there. if (typeof error.unsafeDereference === "function") { const rawError = error.unsafeDereference();
errorMessage = rawError ? rawError.toString() : "";
}
}
}
// It is possible that we won't have permission to unwrap an // object and retrieve its errorMessageName. try {
errorDocURL = ErrorDocs.GetURL(error);
errorMessageName = error.errorMessageName;
} catch (ex) { // ignored
}
try { const line = error.errorLineNumber; const column = error.errorColumnNumber;
if (typeof line === "number" && typeof column === "number") { // Set frame only if we have line/column numbers.
frame = {
source: "debugger eval code",
line,
column,
};
}
} catch (ex) { // ignored
}
try { const notes = error.errorNotes; if (notes?.length) {
errorNotes = []; for (const note of notes) {
errorNotes.push({
messageBody: this._createStringGrip(note.message),
frame: {
source: note.fileName,
line: note.lineNumber,
column: note.columnNumber,
},
});
}
}
} catch (ex) { // ignored
}
}
} // If a value is encountered that the devtools server doesn't support yet, // the console should remain functional.
let resultGrip; if (!awaitResult) { try { const objectActor = this.targetActor.threadActor.getThreadLifetimeObject(result); if (evalInTracer) { const tracerActor = this.targetActor.getTargetScopedActor("tracer");
resultGrip = tracerActor.createValueGrip(result);
} elseif (objectActor) {
resultGrip = this.targetActor.threadActor.createValueGrip(result);
} else {
resultGrip = this.createValueGrip(result);
}
} catch (e) {
errorMessage = e;
}
}
// Don't update _lastConsoleInputEvaluation in eager evaluation, as it would interfere // with the $_ command. if (!eager) { if (!awaitResult) { this._lastConsoleInputEvaluation = result;
} else { // If we evaluated a top-level await expression, we want to assign its result to the // _lastConsoleInputEvaluation only when the promise resolves, and only if it // resolves. If the promise rejects, we don't re-assign _lastConsoleInputEvaluation, // it will keep its previous value.
const p = awaitResult.then(res => { this._lastConsoleInputEvaluation = this.makeDebuggeeValue(res);
});
// If the top level await was already rejected (e.g. `await Promise.reject("bleh")`), // catch the resulting promise of awaitResult.then. // If we don't do that, the new Promise will also be rejected, and since it's // unhandled, it will generate an error. // We don't want to do that for pending promise (e.g. `await new Promise((res, rej) => setTimeout(rej,250))`), // as the the Promise rejection will be considered as handled, and the "Uncaught (in promise)" // message wouldn't be emitted. const { state } = ObjectUtils.getPromiseState(evalResult.return); if (state === "rejected") {
p.catch(() => {});
}
}
}
/** * The Autocomplete request handler. * * @param string text * The request message - what input to autocomplete. * @param number cursor * The cursor position at the moment of starting autocomplete. * @param string frameActor * The frameactor id of the current paused frame. * @param string selectedNodeActor * The actor id of the currently selected node. * @param array authorizedEvaluations * Array of the properties access which can be executed by the engine. * @return object * The response message - matched properties.
*/
autocomplete(
text,
cursor,
frameActorId,
selectedNodeActor,
authorizedEvaluations,
expressionVars = []
) {
let dbgObject = null;
let environment = null;
let matches = [];
let matchProp;
let isElementAccess;
const reqText = text.substr(0, cursor);
if (isCommand(reqText)) {
matchProp = reqText;
matches = WebConsoleCommandsManager.getAllColonCommandNames()
.filter(c => `:${c}`.startsWith(reqText))
.map(c => `:${c}`);
} else { // This is the case of the paused debugger if (frameActorId) { const frameActor = this.conn.getActor(frameActorId); try { // Need to try/catch since accessing frame.environment // can throw "Debugger.Frame is not live" const frame = frameActor.frame;
environment = frame.environment;
} catch (e) {
DevToolsUtils.reportException( "autocomplete",
Error("The frame actor was not found: " + frameActorId)
);
}
} else {
dbgObject = this.dbg.addDebuggee(this.evalGlobal);
}
// We consider '$' as alphanumeric because it is used in the names of some // helper functions; we also consider whitespace as alphanum since it should not // be seen as break in the evaled string. const lastNonAlphaIsDot = /[.][a-zA-Z0-9$\s]*$/.test(reqText);
// We only return commands and keywords when we are not dealing with a property or // element access. if (matchProp && !lastNonAlphaIsDot && !isElementAccess) { const colonOnlyCommands =
WebConsoleCommandsManager.getColonOnlyCommandNames(); for (const name of WebConsoleCommandsManager.getAllCommandNames()) { // Filter out commands like `screenshot` as it is inaccessible without the `:` prefix if (
!colonOnlyCommands.includes(name) &&
name.startsWith(result.matchProp)
) {
matches.add(name);
}
}
for (const keyword of RESERVED_JS_KEYWORDS) { if (keyword.startsWith(result.matchProp)) {
matches.add(keyword);
}
}
}
// Sort the results in order to display lowercased item first (e.g. we want to // display `document` then `Document` as we loosely match the user input if the // first letter was lowercase). const firstMeaningfulCharIndex = isElementAccess ? 1 : 0;
matches = Array.from(matches).sort((a, b) => { const aFirstMeaningfulChar = a[firstMeaningfulCharIndex]; const bFirstMeaningfulChar = b[firstMeaningfulCharIndex]; const lA =
aFirstMeaningfulChar.toLocaleLowerCase() === aFirstMeaningfulChar; const lB =
bFirstMeaningfulChar.toLocaleLowerCase() === bFirstMeaningfulChar; if (lA === lB) { if (a === matchProp) { return -1;
} if (b === matchProp) { return 1;
} return a.localeCompare(b);
} return lA ? -1 : 1;
});
}
/** * The "clearMessagesCacheAsync" request handler.
*/
clearMessagesCacheAsync() { if (isWorker) { // Defined on WorkerScope
clearConsoleEvents(); return;
}
if (this.targetActor.isRootActor || !this.global) { // If were dealing with the root actor (e.g. the browser console), we want // to remove all cached messages, not only the ones specific to a window.
Services.console.reset();
} elseif (this.targetActor.ignoreSubFrames) {
Services.console.resetWindow(windowId);
} else {
WebConsoleUtils.getInnerWindowIDsForFrames(this.global).forEach(id =>
Services.console.resetWindow(id)
);
}
}
// End of request handlers.
// Event handlers for various listeners.
/** * Handler for messages received from the ConsoleServiceListener. This method * sends the nsIConsoleMessage to the remote Web Console client. * * @param nsIConsoleMessage message * The message we need to send to the client.
*/
onConsoleServiceMessage(message) { if (message instanceof Ci.nsIScriptError) { this.emit("pageError", {
pageError: this.preparePageErrorForRemote(message),
});
} else { this.emit("logMessage", {
message: this._createStringGrip(message.message),
timeStamp: message.microSecondTimeStamp / 1000,
});
}
}
getActorIdForInternalSourceId(id) { const actor = this.targetActor.sourcesManager.getSourceActorByInternalSourceId(id); return actor ? actor.actorID : null;
}
/** * Prepare a SavedFrame stack to be sent to the client. * * @param SavedFrame errorStack * Stack for an error we need to send to the client. * @return object * The object you can send to the remote client.
*/
prepareStackForRemote(errorStack) { // Convert stack objects to the JSON attributes expected by client code // Bug 1348885: If the global from which this error came from has been // nuked, stack is going to be a dead wrapper. if (!errorStack || (Cu && Cu.isDeadWrapper(errorStack))) { returnnull;
} const stack = [];
let s = errorStack; while (s) {
stack.push({
filename: s.source,
sourceId: this.getActorIdForInternalSourceId(s.sourceId),
lineNumber: s.line,
columnNumber: s.column,
functionName: s.functionDisplayName,
asyncCause: s.asyncCause ? s.asyncCause : undefined,
});
s = s.parent || s.asyncParent;
} return stack;
}
/** * Prepare an nsIScriptError to be sent to the client. * * @param nsIScriptError pageError * The page error we need to send to the client. * @return object * The object you can send to the remote client.
*/
preparePageErrorForRemote(pageError) { const stack = this.prepareStackForRemote(pageError.stack);
let notesArray = null; const notes = pageError.notes; if (notes?.length) {
notesArray = []; for (let i = 0, len = notes.length; i < len; i++) { const note = notes.queryElementAt(i, Ci.nsIScriptErrorNote);
notesArray.push({
messageBody: this._createStringGrip(note.errorMessage),
frame: {
source: note.sourceName,
sourceId: this.getActorIdForInternalSourceId(note.sourceId),
line: note.lineNumber,
column: note.columnNumber,
},
});
}
}
// If there is no location information in the error but we have a stack, // fill in the location with the first frame on the stack.
let { sourceName, sourceId, lineNumber, columnNumber } = pageError; if (!sourceName && !sourceId && !lineNumber && !columnNumber && stack) {
sourceName = stack[0].filename;
sourceId = stack[0].sourceId;
lineNumber = stack[0].lineNumber;
columnNumber = stack[0].columnNumber;
}
// If the pageError does have an exception object, we want to return the grip for it, // but only if we do manage to get the grip, as we're checking the property on the // client to render things differently. if (pageError.hasException) { try { const obj = this.makeDebuggeeValue(pageError.exception, true); if (obj?.class !== "DeadObject") {
result.exception = this.createValueGrip(obj);
result.hasException = true;
}
} catch (e) {}
}
return result;
}
/** * Handler for window.console API calls received from the ConsoleAPIListener. * This method sends the object to the remote Web Console client. * * @see ConsoleAPIListener * @param object message * The console API call we need to send to the remote client. * @param object extraProperties * an object whose properties will be folded in the packet that is emitted.
*/
onConsoleAPICall(message, extraProperties = {}) { this.emit("consoleAPICall", {
message: this.prepareConsoleMessageForRemote(message),
...extraProperties,
});
}
/** * Handler for the DocumentEventsListener. * * @see DocumentEventsListener * @param {String} name * The document event name that either of followings. * - dom-loading * - dom-interactive * - dom-complete * @param {Number} time * The time that the event is fired. * @param {Boolean} hasNativeConsoleAPI * Tells if the window.console object is native or overwritten by script in the page. * Only passed when `name` is "dom-complete" (see devtools/server/actors/webconsole/listeners/document-events.js).
*/
onDocumentEvent(name, { time, hasNativeConsoleAPI }) { this.emit("documentEvent", {
name,
time,
hasNativeConsoleAPI,
});
}
/** * Handler for file activity. This method sends the file request information * to the remote Web Console client. * * @see ConsoleFileActivityListener * @param string fileURI * The requested file URI.
*/
onFileActivity(fileURI) { this.emit("fileActivity", {
uri: fileURI,
});
}
// End of event handlers for various listeners.
/** * Prepare a message from the console API to be sent to the remote Web Console * instance. * * @param object message * The original message received from the console storage listener. * @param boolean aUseObjectGlobal * If |true| the object global is determined and added as a debuggee, * otherwise |this.global| is used when makeDebuggeeValue() is invoked. * @return object * The object that can be sent to the remote client.
*/
prepareConsoleMessageForRemote(message, useObjectGlobal = true) { const result = {
arguments: message.arguments
? message.arguments.map(obj => { const dbgObj = this.makeDebuggeeValue(obj, useObjectGlobal); returnthis.createValueGrip(dbgObj);
})
: [],
chromeContext: message.chromeContext,
columnNumber: message.columnNumber,
filename: message.filename,
level: message.level,
lineNumber: message.lineNumber, // messages emitted from Console.sys.mjs don't have a microSecondTimeStamp property
timeStamp: message.microSecondTimeStamp
? message.microSecondTimeStamp / 1000
: message.timeStamp,
sourceId: this.getActorIdForInternalSourceId(message.sourceId),
category: message.category || "webdev",
innerWindowID: message.innerID,
};
// It only make sense to include the following properties in the message when they have // a meaningful value. Otherwise we simply don't include them so we save cycles in JSActor communication. if (message.counter) {
result.counter = message.counter;
} if (message.private) {
result.private = message.private;
} if (message.prefix) {
result.prefix = message.prefix;
}
if (message.timer) {
result.timer = message.timer;
}
if (message.level === "table") { const tableItems = this._getConsoleTableMessageItems(result); if (tableItems) {
result.arguments[0].ownProperties = tableItems;
result.arguments[0].preview = null;
}
// Only return the 2 first params.
result.arguments = result.arguments.slice(0, 2);
}
return result;
}
/** * Return the properties needed to display the appropriate table for a given * console.table call. * This function does a little more than creating an ObjectActor for the first * parameter of the message. When layout out the console table in the output, we want * to be able to look into sub-properties so the table can have a different layout ( * for arrays of arrays, objects with objects properties, arrays of objects, …). * So here we need to retrieve the properties of the first parameter, and also all the * sub-properties we might need. * * @param {Object} result: The console.table message. * @returns {Object} An object containing the properties of the first argument of the * console.table call.
*/
_getConsoleTableMessageItems(result) { if (
!result ||
!Array.isArray(result.arguments) ||
!result.arguments.length
) { returnnull;
}
// Retrieve the properties (or entries for Set/Map) of the console table first arg. const iterator = needEntries
? tableItemActor.enumEntries()
: tableItemActor.enumProperties({
ignoreNonIndexedProperties,
}); const { ownProperties } = iterator.all();
// The iterator returns a descriptor for each property, wherein the value could be // in one of those sub-property. const descriptorKeys = ["safeGetterValues", "getterValue", "value"];
// We need to load sub-properties as well to render the table in a nice way. const actor =
grip && this.targetActor.objectsPool.getActorByID(grip.actor); if (actor) { const res = actor
.enumProperties({
ignoreNonIndexedProperties: isArray(grip),
})
.all(); if (res?.ownProperties) {
desc[key].ownProperties = res.ownProperties;
}
}
}
});
}
});
return ownProperties;
}
/** * The "will-navigate" progress listener. This is used to clear the current * eval scope.
*/
_onWillNavigate({ isTopLevel }) { if (isTopLevel) { this._evalGlobal = null;
EventEmitter.off(this.targetActor, "will-navigate", this._onWillNavigate); this._progressListenerActive = false;
}
}
/** * This listener is called when we switch to another frame, * mostly to unregister previous listeners and start listening on the new document.
*/
_onChangedToplevelDocument() { // Convert the Set to an Array const listeners = [...this._listeners];
// Unregister existing listener on the previous document // (pass a copy of the array as it will shift from it) this.stopListeners(listeners.slice());
// This method is called after this.global is changed, // so we register new listener on this new global this.startListeners(listeners);
// Also reset the cached top level chrome window being targeted this._lastChromeWindow = null;
}
}
exports.WebConsoleActor = WebConsoleActor;
¤ Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.0.50Bemerkung:
(vorverarbeitet)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.