참고소스 수정본
This commit is contained in:
@@ -0,0 +1,605 @@
|
||||
/* 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";
|
||||
// Note: this file should be loadabale with eval() into worker environment.
|
||||
// Avoid Components.*, ChromeUtils and global const variables.
|
||||
|
||||
if (!this.Debugger) {
|
||||
// Worker has a Debugger defined already.
|
||||
const {addDebuggerToGlobal} = ChromeUtils.importESModule("resource://gre/modules/jsdebugger.sys.mjs");
|
||||
addDebuggerToGlobal(Components.utils.getGlobalForObject(globalThis));
|
||||
}
|
||||
|
||||
let lastId = 0;
|
||||
function generateId() {
|
||||
return 'id-' + (++lastId);
|
||||
}
|
||||
|
||||
const consoleLevelToProtocolType = {
|
||||
'dir': 'dir',
|
||||
'log': 'log',
|
||||
'debug': 'debug',
|
||||
'info': 'info',
|
||||
'error': 'error',
|
||||
'warn': 'warning',
|
||||
'dirxml': 'dirxml',
|
||||
'table': 'table',
|
||||
'trace': 'trace',
|
||||
'clear': 'clear',
|
||||
'group': 'startGroup',
|
||||
'groupCollapsed': 'startGroupCollapsed',
|
||||
'groupEnd': 'endGroup',
|
||||
'assert': 'assert',
|
||||
'profile': 'profile',
|
||||
'profileEnd': 'profileEnd',
|
||||
'count': 'count',
|
||||
'countReset': 'countReset',
|
||||
'time': null,
|
||||
'timeLog': 'timeLog',
|
||||
'timeEnd': 'timeEnd',
|
||||
'timeStamp': 'timeStamp',
|
||||
};
|
||||
|
||||
const disallowedMessageCategories = new Set([
|
||||
'XPConnect JavaScript',
|
||||
'component javascript',
|
||||
'chrome javascript',
|
||||
'chrome registration',
|
||||
'XBL',
|
||||
'XBL Prototype Handler',
|
||||
'XBL Content Sink',
|
||||
'xbl javascript',
|
||||
]);
|
||||
|
||||
class Runtime {
|
||||
constructor(isWorker = false) {
|
||||
this._debugger = new Debugger();
|
||||
this._pendingPromises = new Map();
|
||||
this._executionContexts = new Map();
|
||||
this._windowToExecutionContext = new Map();
|
||||
this._eventListeners = [];
|
||||
if (isWorker) {
|
||||
this._registerWorkerConsoleHandler();
|
||||
} else {
|
||||
this._registerConsoleServiceListener(Services);
|
||||
this._registerConsoleAPIListener(Services);
|
||||
}
|
||||
// We can't use event listener here to be compatible with Worker Global Context.
|
||||
// Use plain callbacks instead.
|
||||
this.events = {
|
||||
onConsoleMessage: createEvent(),
|
||||
onRuntimeError: createEvent(),
|
||||
onErrorFromWorker: createEvent(),
|
||||
onExecutionContextCreated: createEvent(),
|
||||
onExecutionContextDestroyed: createEvent(),
|
||||
onBindingCalled: createEvent(),
|
||||
};
|
||||
}
|
||||
|
||||
executionContexts() {
|
||||
return [...this._executionContexts.values()];
|
||||
}
|
||||
|
||||
async evaluate({executionContextId, expression, returnByValue}) {
|
||||
const executionContext = this.findExecutionContext(executionContextId);
|
||||
if (!executionContext)
|
||||
throw new Error('Failed to find execution context with id = ' + executionContextId);
|
||||
const exceptionDetails = {};
|
||||
let result = await executionContext.evaluateScript(expression, exceptionDetails);
|
||||
if (!result)
|
||||
return {exceptionDetails};
|
||||
if (returnByValue)
|
||||
result = executionContext.ensureSerializedToValue(result);
|
||||
return {result};
|
||||
}
|
||||
|
||||
async callFunction({executionContextId, functionDeclaration, args, returnByValue}) {
|
||||
const executionContext = this.findExecutionContext(executionContextId);
|
||||
if (!executionContext)
|
||||
throw new Error('Failed to find execution context with id = ' + executionContextId);
|
||||
const exceptionDetails = {};
|
||||
let result = await executionContext.evaluateFunction(functionDeclaration, args, exceptionDetails);
|
||||
if (!result)
|
||||
return {exceptionDetails};
|
||||
if (returnByValue)
|
||||
result = executionContext.ensureSerializedToValue(result);
|
||||
return {result};
|
||||
}
|
||||
|
||||
async getObjectProperties({executionContextId, objectId}) {
|
||||
const executionContext = this.findExecutionContext(executionContextId);
|
||||
if (!executionContext)
|
||||
throw new Error('Failed to find execution context with id = ' + executionContextId);
|
||||
return {properties: executionContext.getObjectProperties(objectId)};
|
||||
}
|
||||
|
||||
async disposeObject({executionContextId, objectId}) {
|
||||
const executionContext = this.findExecutionContext(executionContextId);
|
||||
if (!executionContext)
|
||||
throw new Error('Failed to find execution context with id = ' + executionContextId);
|
||||
return executionContext.disposeObject(objectId);
|
||||
}
|
||||
|
||||
_registerConsoleServiceListener(Services) {
|
||||
const Ci = Components.interfaces;
|
||||
const consoleServiceListener = {
|
||||
QueryInterface: ChromeUtils.generateQI([Ci.nsIConsoleListener]),
|
||||
|
||||
observe: message => {
|
||||
if (!(message instanceof Ci.nsIScriptError) || !message.outerWindowID ||
|
||||
!message.category || disallowedMessageCategories.has(message.category)) {
|
||||
return;
|
||||
}
|
||||
const errorWindow = Services.wm.getOuterWindowWithId(message.outerWindowID);
|
||||
// Note: error locations are one-based, while console locations are zero-based in Firefox.
|
||||
// We want to report all of them as zero-based.
|
||||
const errorLocation = {
|
||||
lineNumber: message.lineNumber - 1,
|
||||
columnNumber: message.columnNumber - 1,
|
||||
url: message.sourceName,
|
||||
};
|
||||
if (message.category === 'Web Worker' && message.logLevel === Ci.nsIConsoleMessage.error) {
|
||||
emitEvent(this.events.onErrorFromWorker, errorWindow, message.message, '' + message.stack, errorLocation);
|
||||
return;
|
||||
}
|
||||
const executionContext = this._windowToExecutionContext.get(errorWindow);
|
||||
if (!executionContext) {
|
||||
return;
|
||||
}
|
||||
const typeNames = {
|
||||
[Ci.nsIConsoleMessage.debug]: 'debug',
|
||||
[Ci.nsIConsoleMessage.info]: 'info',
|
||||
[Ci.nsIConsoleMessage.warn]: 'warn',
|
||||
[Ci.nsIConsoleMessage.error]: 'error',
|
||||
};
|
||||
if (!message.hasException) {
|
||||
emitEvent(this.events.onConsoleMessage, {
|
||||
args: [{
|
||||
value: message.message,
|
||||
}],
|
||||
type: typeNames[message.logLevel],
|
||||
executionContextId: executionContext.id(),
|
||||
location: {
|
||||
lineNumber: message.lineNumber,
|
||||
columnNumber: message.columnNumber,
|
||||
url: message.sourceName,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
emitEvent(this.events.onRuntimeError, {
|
||||
executionContext,
|
||||
message: message.errorMessage,
|
||||
stack: message.stack ? message.stack.toString() : '',
|
||||
location: errorLocation,
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
Services.console.registerListener(consoleServiceListener);
|
||||
this._eventListeners.push(() => Services.console.unregisterListener(consoleServiceListener));
|
||||
}
|
||||
|
||||
_registerConsoleAPIListener(Services) {
|
||||
const Ci = Components.interfaces;
|
||||
const Cc = Components.classes;
|
||||
const ConsoleAPIStorage = Cc["@mozilla.org/consoleAPI-storage;1"].getService(Ci.nsIConsoleAPIStorage);
|
||||
const onMessage = ({ wrappedJSObject }) => {
|
||||
const executionContext = Array.from(this._executionContexts.values()).find(context => {
|
||||
// There is no easy way to determine isolated world context and we normally don't write
|
||||
// objects to console from utility worlds so we always return main world context here.
|
||||
if (context._isIsolatedWorldContext())
|
||||
return false;
|
||||
const domWindow = context._domWindow;
|
||||
try {
|
||||
// `windowGlobalChild` might be dead already; accessing it will throw an error, message in a console,
|
||||
// and infinite recursion.
|
||||
return domWindow && domWindow.windowGlobalChild.innerWindowId === wrappedJSObject.innerID;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
if (!executionContext)
|
||||
return;
|
||||
this._onConsoleMessage(executionContext, wrappedJSObject);
|
||||
}
|
||||
ConsoleAPIStorage.addLogEventListener(
|
||||
onMessage,
|
||||
Cc["@mozilla.org/systemprincipal;1"].createInstance(Ci.nsIPrincipal)
|
||||
);
|
||||
this._eventListeners.push(() => ConsoleAPIStorage.removeLogEventListener(onMessage));
|
||||
}
|
||||
|
||||
_registerWorkerConsoleHandler() {
|
||||
setConsoleEventHandler(message => {
|
||||
const executionContext = Array.from(this._executionContexts.values())[0];
|
||||
this._onConsoleMessage(executionContext, message);
|
||||
});
|
||||
this._eventListeners.push(() => setConsoleEventHandler(null));
|
||||
}
|
||||
|
||||
_onConsoleMessage(executionContext, message) {
|
||||
const type = consoleLevelToProtocolType[message.level];
|
||||
if (!type)
|
||||
return;
|
||||
const args = message.arguments.map(arg => executionContext.rawValueToRemoteObject(arg));
|
||||
emitEvent(this.events.onConsoleMessage, {
|
||||
args,
|
||||
type,
|
||||
executionContextId: executionContext.id(),
|
||||
location: {
|
||||
lineNumber: message.lineNumber - 1,
|
||||
columnNumber: message.columnNumber - 1,
|
||||
url: message.filename,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
dispose() {
|
||||
for (const tearDown of this._eventListeners)
|
||||
tearDown.call(null);
|
||||
this._eventListeners = [];
|
||||
}
|
||||
|
||||
async _awaitPromise(executionContext, obj, exceptionDetails = {}) {
|
||||
if (obj.promiseState === 'fulfilled')
|
||||
return {success: true, obj: obj.promiseValue};
|
||||
if (obj.promiseState === 'rejected') {
|
||||
const debuggee = executionContext._debuggee;
|
||||
const errorInfo = debuggee.executeInGlobalWithBindings('({m: e?.message, s: e?.stack})', {e: obj.promiseReason}, {useInnerBindings: true}).return;
|
||||
exceptionDetails.text = errorInfo.getOwnPropertyDescriptor('m').value;
|
||||
exceptionDetails.stack = errorInfo.getOwnPropertyDescriptor('s').value;
|
||||
return {success: false, obj: null};
|
||||
}
|
||||
let resolve, reject;
|
||||
const promise = new Promise((a, b) => {
|
||||
resolve = a;
|
||||
reject = b;
|
||||
});
|
||||
this._pendingPromises.set(obj.promiseID, {resolve, reject, executionContext, exceptionDetails});
|
||||
if (this._pendingPromises.size === 1)
|
||||
this._debugger.onPromiseSettled = this._onPromiseSettled.bind(this);
|
||||
return await promise;
|
||||
}
|
||||
|
||||
_onPromiseSettled(obj) {
|
||||
const pendingPromise = this._pendingPromises.get(obj.promiseID);
|
||||
if (!pendingPromise)
|
||||
return;
|
||||
this._pendingPromises.delete(obj.promiseID);
|
||||
if (!this._pendingPromises.size)
|
||||
this._debugger.onPromiseSettled = undefined;
|
||||
|
||||
if (obj.promiseState === 'fulfilled') {
|
||||
pendingPromise.resolve({success: true, obj: obj.promiseValue});
|
||||
return;
|
||||
};
|
||||
const debuggee = pendingPromise.executionContext._debuggee;
|
||||
const errorInfo = debuggee.executeInGlobalWithBindings('({m: e?.message, s: e?.stack})', {e: obj.promiseReason}, {useInnerBindings: true}).return;
|
||||
pendingPromise.exceptionDetails.text = errorInfo.getOwnPropertyDescriptor('m').value;
|
||||
pendingPromise.exceptionDetails.stack = errorInfo.getOwnPropertyDescriptor('s').value;
|
||||
pendingPromise.resolve({success: false, obj: null});
|
||||
}
|
||||
|
||||
createExecutionContext(domWindow, contextGlobal, auxData) {
|
||||
// Note: domWindow is null for workers.
|
||||
const context = new ExecutionContext(this, domWindow, contextGlobal, auxData);
|
||||
this._executionContexts.set(context._id, context);
|
||||
if (domWindow)
|
||||
this._windowToExecutionContext.set(domWindow, context);
|
||||
emitEvent(this.events.onExecutionContextCreated, context);
|
||||
return context;
|
||||
}
|
||||
|
||||
findExecutionContext(executionContextId) {
|
||||
const executionContext = this._executionContexts.get(executionContextId);
|
||||
if (!executionContext)
|
||||
throw new Error('Failed to find execution context with id = ' + executionContextId);
|
||||
return executionContext;
|
||||
}
|
||||
|
||||
destroyExecutionContext(destroyedContext) {
|
||||
for (const [promiseID, {reject, executionContext}] of this._pendingPromises) {
|
||||
if (executionContext === destroyedContext) {
|
||||
reject(new Error('Execution context was destroyed!'));
|
||||
this._pendingPromises.delete(promiseID);
|
||||
}
|
||||
}
|
||||
if (!this._pendingPromises.size)
|
||||
this._debugger.onPromiseSettled = undefined;
|
||||
this._debugger.removeDebuggee(destroyedContext._contextGlobal);
|
||||
this._executionContexts.delete(destroyedContext._id);
|
||||
if (destroyedContext._domWindow)
|
||||
this._windowToExecutionContext.delete(destroyedContext._domWindow);
|
||||
emitEvent(this.events.onExecutionContextDestroyed, destroyedContext);
|
||||
}
|
||||
}
|
||||
|
||||
class ExecutionContext {
|
||||
constructor(runtime, domWindow, contextGlobal, auxData) {
|
||||
this._runtime = runtime;
|
||||
this._domWindow = domWindow;
|
||||
this._contextGlobal = contextGlobal;
|
||||
this._debuggee = runtime._debugger.addDebuggee(contextGlobal);
|
||||
this._remoteObjects = new Map();
|
||||
this._id = generateId();
|
||||
this._auxData = auxData;
|
||||
this._jsonStringifyObject = this._debuggee.executeInGlobal(`((stringify, object) => {
|
||||
const oldToJSON = Date.prototype?.toJSON;
|
||||
if (oldToJSON)
|
||||
Date.prototype.toJSON = undefined;
|
||||
const oldArrayToJSON = Array.prototype.toJSON;
|
||||
const oldArrayHadToJSON = Array.prototype.hasOwnProperty('toJSON');
|
||||
if (oldArrayHadToJSON)
|
||||
Array.prototype.toJSON = undefined;
|
||||
|
||||
let hasSymbol = false;
|
||||
const result = stringify(object, (key, value) => {
|
||||
if (typeof value === 'symbol')
|
||||
hasSymbol = true;
|
||||
return value;
|
||||
});
|
||||
|
||||
if (oldToJSON)
|
||||
Date.prototype.toJSON = oldToJSON;
|
||||
if (oldArrayHadToJSON)
|
||||
Array.prototype.toJSON = oldArrayToJSON;
|
||||
|
||||
return hasSymbol ? undefined : result;
|
||||
}).bind(null, JSON.stringify.bind(JSON))`).return;
|
||||
}
|
||||
|
||||
id() {
|
||||
return this._id;
|
||||
}
|
||||
|
||||
auxData() {
|
||||
return this._auxData;
|
||||
}
|
||||
|
||||
_isIsolatedWorldContext() {
|
||||
return !!this._auxData.name;
|
||||
}
|
||||
|
||||
async evaluateScript(script, exceptionDetails = {}) {
|
||||
const userInputHelper = this._domWindow ? this._domWindow.windowUtils.setHandlingUserInput(true) : null;
|
||||
if (this._domWindow && this._domWindow.document)
|
||||
this._domWindow.document.notifyUserGestureActivation();
|
||||
|
||||
let {success, obj} = this._getResult(this._debuggee.executeInGlobal(script, {bypassCSP: true}), exceptionDetails);
|
||||
userInputHelper && userInputHelper.destruct();
|
||||
if (!success)
|
||||
return null;
|
||||
if (obj && obj.isPromise) {
|
||||
const awaitResult = await this._runtime._awaitPromise(this, obj, exceptionDetails);
|
||||
if (!awaitResult.success)
|
||||
return null;
|
||||
obj = awaitResult.obj;
|
||||
}
|
||||
return this._createRemoteObject(obj);
|
||||
}
|
||||
|
||||
evaluateScriptSafely(script) {
|
||||
try {
|
||||
this._debuggee.executeInGlobal(script, {bypassCSP: true});
|
||||
} catch (e) {
|
||||
dump(`WARNING: ${e.message}\n${e.stack}\n`);
|
||||
}
|
||||
}
|
||||
|
||||
async evaluateFunction(functionText, args, exceptionDetails = {}) {
|
||||
args = args.map(arg => {
|
||||
if (arg.objectId) {
|
||||
if (!this._remoteObjects.has(arg.objectId))
|
||||
throw new Error('Cannot find object with id = ' + arg.objectId);
|
||||
return this._remoteObjects.get(arg.objectId);
|
||||
}
|
||||
switch (arg.unserializableValue) {
|
||||
case 'Infinity': return Infinity;
|
||||
case '-Infinity': return -Infinity;
|
||||
case '-0': return -0;
|
||||
case 'NaN': return NaN;
|
||||
default: return this._toDebugger(arg.value);
|
||||
}
|
||||
});
|
||||
const userInputHelper = this._domWindow ? this._domWindow.windowUtils.setHandlingUserInput(true) : null;
|
||||
if (this._domWindow && this._domWindow.document)
|
||||
this._domWindow.document.notifyUserGestureActivation();
|
||||
// Invoke via executeInGlobalWithBindings so bypassCSP scopes the call (Debugger.Object.apply has no bypassCSP option).
|
||||
const callBindings = {};
|
||||
const argNames = [];
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
const name = '__pwArg' + i;
|
||||
callBindings[name] = args[i];
|
||||
argNames.push(name);
|
||||
}
|
||||
let {success, obj} = this._getResult(this._debuggee.executeInGlobalWithBindings('(' + functionText + ')(' + argNames.join(',') + ')', callBindings, {useInnerBindings: true, bypassCSP: true}), exceptionDetails);
|
||||
userInputHelper && userInputHelper.destruct();
|
||||
if (!success)
|
||||
return null;
|
||||
if (obj && obj.isPromise) {
|
||||
const awaitResult = await this._runtime._awaitPromise(this, obj, exceptionDetails);
|
||||
if (!awaitResult.success)
|
||||
return null;
|
||||
obj = awaitResult.obj;
|
||||
}
|
||||
return this._createRemoteObject(obj);
|
||||
}
|
||||
|
||||
addBinding(name, script) {
|
||||
Cu.exportFunction((...args) => {
|
||||
emitEvent(this._runtime.events.onBindingCalled, {
|
||||
executionContextId: this._id,
|
||||
name,
|
||||
payload: args[0],
|
||||
});
|
||||
}, this._contextGlobal, {
|
||||
defineAs: name,
|
||||
});
|
||||
this.evaluateScriptSafely(script);
|
||||
}
|
||||
|
||||
unsafeObject(objectId) {
|
||||
if (!this._remoteObjects.has(objectId))
|
||||
return;
|
||||
return { object: this._remoteObjects.get(objectId).unsafeDereference() };
|
||||
}
|
||||
|
||||
rawValueToRemoteObject(rawValue) {
|
||||
const debuggerObj = this._debuggee.makeDebuggeeValue(rawValue);
|
||||
return this._createRemoteObject(debuggerObj);
|
||||
}
|
||||
|
||||
_createRemoteObject(debuggerObj) {
|
||||
if (debuggerObj instanceof Debugger.Object) {
|
||||
const objectId = generateId();
|
||||
this._remoteObjects.set(objectId, debuggerObj);
|
||||
const rawObj = debuggerObj.unsafeDereference();
|
||||
const type = typeof rawObj;
|
||||
let subtype = undefined;
|
||||
if (debuggerObj.isProxy) {
|
||||
subtype = 'proxy';
|
||||
} else if (typeof Node !== 'undefined' && Node.isInstance(rawObj)) {
|
||||
subtype = 'node';
|
||||
} else {
|
||||
switch (debuggerObj.class) {
|
||||
case 'Array': subtype = 'array'; break;
|
||||
case 'RegExp': subtype = 'regexp'; break;
|
||||
case 'Date': subtype = 'date'; break;
|
||||
case 'Map': subtype = 'map'; break;
|
||||
case 'Set': subtype = 'set'; break;
|
||||
case 'WeakMap': subtype = 'weakmap'; break;
|
||||
case 'WeakSet': subtype = 'weakset'; break;
|
||||
case 'Error': subtype = 'error'; break;
|
||||
case 'Promise': subtype = 'promise'; break;
|
||||
case 'Int8Array':
|
||||
case 'Uint8Array':
|
||||
case 'Uint8ClampedArray':
|
||||
case 'Int16Array':
|
||||
case 'Uint16Array':
|
||||
case 'Int32Array':
|
||||
case 'Uint32Array':
|
||||
case 'Float32Array':
|
||||
case 'Float64Array':
|
||||
subtype = 'typedarray';
|
||||
break;
|
||||
}
|
||||
}
|
||||
return {objectId, type, subtype};
|
||||
}
|
||||
if (typeof debuggerObj === 'symbol') {
|
||||
const objectId = generateId();
|
||||
this._remoteObjects.set(objectId, debuggerObj);
|
||||
return {objectId, type: 'symbol'};
|
||||
}
|
||||
|
||||
let unserializableValue = undefined;
|
||||
if (Object.is(debuggerObj, NaN))
|
||||
unserializableValue = 'NaN';
|
||||
else if (Object.is(debuggerObj, -0))
|
||||
unserializableValue = '-0';
|
||||
else if (Object.is(debuggerObj, Infinity))
|
||||
unserializableValue = 'Infinity';
|
||||
else if (Object.is(debuggerObj, -Infinity))
|
||||
unserializableValue = '-Infinity';
|
||||
return unserializableValue ? {unserializableValue} : {value: debuggerObj};
|
||||
}
|
||||
|
||||
ensureSerializedToValue(protocolObject) {
|
||||
if (!protocolObject.objectId)
|
||||
return protocolObject;
|
||||
const obj = this._remoteObjects.get(protocolObject.objectId);
|
||||
this._remoteObjects.delete(protocolObject.objectId);
|
||||
return {value: this._serialize(obj)};
|
||||
}
|
||||
|
||||
_toDebugger(obj) {
|
||||
if (typeof obj !== 'object')
|
||||
return obj;
|
||||
if (obj === null)
|
||||
return obj;
|
||||
const properties = {};
|
||||
for (let [key, value] of Object.entries(obj)) {
|
||||
properties[key] = {
|
||||
configurable: true,
|
||||
writable: true,
|
||||
enumerable: true,
|
||||
value: this._toDebugger(value),
|
||||
};
|
||||
}
|
||||
const baseObject = Array.isArray(obj) ? '([])' : '({})';
|
||||
const debuggerObj = this._debuggee.executeInGlobal(baseObject).return;
|
||||
debuggerObj.defineProperties(properties);
|
||||
return debuggerObj;
|
||||
}
|
||||
|
||||
_serialize(obj) {
|
||||
const result = this._debuggee.executeInGlobalWithBindings('stringify(e)', {e: obj, stringify: this._jsonStringifyObject}, {useInnerBindings: true});
|
||||
if (result.throw)
|
||||
throw new Error('Object is not serializable');
|
||||
return result.return === undefined ? undefined : JSON.parse(result.return);
|
||||
}
|
||||
|
||||
disposeObject(objectId) {
|
||||
this._remoteObjects.delete(objectId);
|
||||
}
|
||||
|
||||
getObjectProperties(objectId) {
|
||||
if (!this._remoteObjects.has(objectId))
|
||||
throw new Error('Cannot find object with id = ' + arg.objectId);
|
||||
const result = [];
|
||||
for (let obj = this._remoteObjects.get(objectId); obj; obj = obj.proto) {
|
||||
for (const propertyName of obj.getOwnPropertyNames()) {
|
||||
const descriptor = obj.getOwnPropertyDescriptor(propertyName);
|
||||
if (!descriptor.enumerable)
|
||||
continue;
|
||||
result.push({
|
||||
name: propertyName,
|
||||
value: this._createRemoteObject(descriptor.value),
|
||||
});
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
_getResult(completionValue, exceptionDetails = {}) {
|
||||
if (!completionValue)
|
||||
throw new Error('evaluation terminated');
|
||||
if (completionValue.throw) {
|
||||
const errorInfo = this._debuggee.executeInGlobalWithBindings('e instanceof Error ? ({m: e.message, s: e.stack}) : null', {e: completionValue.throw}, {useInnerBindings: true}).return;
|
||||
if (errorInfo) {
|
||||
exceptionDetails.text = errorInfo.getOwnPropertyDescriptor('m').value;
|
||||
exceptionDetails.stack = errorInfo.getOwnPropertyDescriptor('s').value;
|
||||
} else {
|
||||
exceptionDetails.value = this._serialize(completionValue.throw);
|
||||
}
|
||||
return {success: false, obj: null};
|
||||
}
|
||||
return {success: true, obj: completionValue.return};
|
||||
}
|
||||
}
|
||||
|
||||
const listenersSymbol = Symbol('listeners');
|
||||
|
||||
function createEvent() {
|
||||
const listeners = new Set();
|
||||
const subscribeFunction = listener => {
|
||||
listeners.add(listener);
|
||||
return () => listeners.delete(listener);
|
||||
}
|
||||
subscribeFunction[listenersSymbol] = listeners;
|
||||
return subscribeFunction;
|
||||
}
|
||||
|
||||
function emitEvent(event, ...args) {
|
||||
let listeners = event[listenersSymbol];
|
||||
if (!listeners || !listeners.size)
|
||||
return;
|
||||
listeners = new Set(listeners);
|
||||
for (const listener of listeners)
|
||||
listener.call(null, ...args);
|
||||
}
|
||||
|
||||
// Export Runtime to global.
|
||||
globalThis.Runtime = Runtime;
|
||||
Reference in New Issue
Block a user