Merge commit '6e92d2345e8231a44556ce7d416451f5f3e6c398' as 'engine262'

This commit is contained in:
2026-03-27 10:07:29 +05:30
433 changed files with 90478 additions and 0 deletions
+405
View File
@@ -0,0 +1,405 @@
import type { Protocol } from 'devtools-protocol';
import { getInspector } from './inspect.mts';
import type { Inspector } from './index.mts';
import {
EnsureCompletion, JSStringValue, ManagedRealm, NullValue, ObjectValue, SymbolValue, ThrowCompletion, Value,
getHostDefinedErrorStack,
type ValueCompletion,
getCurrentStack,
isECMAScriptFunctionObject,
SymbolDescriptiveString,
type EnvironmentRecordWithThisBinding,
EnvironmentRecord,
DeclarativeEnvironmentRecord,
ObjectEnvironmentRecord,
FunctionEnvironmentRecord,
GlobalEnvironmentRecord,
ModuleEnvironmentRecord,
OrdinaryObjectCreate,
Descriptor,
isArgumentExoticObject,
Agent,
surroundingAgent,
IsAccessorDescriptor,
isIntegerIndex,
isBuiltinFunctionObject,
isArrayBufferObject,
DataBlock,
CallSite,
CallFrame,
type OrdinaryObject,
} from '#self';
interface InspectedRealmDescriptor {
readonly realm: ManagedRealm;
readonly descriptor: Protocol.Runtime.ExecutionContextDescription;
readonly agent: Agent;
detach(): void;
}
export class InspectorContext {
#io: Inspector;
constructor(io: Inspector) {
this.#io = io;
}
realms: (InspectedRealmDescriptor | undefined)[] = [];
attachRealm(realm: ManagedRealm, agent: Agent) {
const id = this.realms.length;
const descriptor: Protocol.Runtime.ExecutionContextDescription = {
id,
origin: realm.HostDefined.specifier || 'vm://repl',
name: realm.HostDefined.name || 'engine262',
uniqueId: id.toString(),
};
this.realms.push({
realm,
descriptor,
agent,
detach: () => {
realm.HostDefined.attachingInspector = oldInspector;
realm.HostDefined.attachingInspectorReportError = function attachingInspectorReportError(realm, error) {
if (this.attachingInspector && realm instanceof ManagedRealm) {
(this.attachingInspector as Inspector).console(realm, 'error' as Protocol.Runtime.ConsoleAPICalledEventType, [error]);
}
};
},
});
const oldInspector = realm.HostDefined.attachingInspector;
realm.HostDefined.attachingInspector = this.#io;
const oldPromiseRejectionTracker = realm.HostDefined.promiseRejectionTracker;
realm.HostDefined.promiseRejectionTracker = (promise, operation) => {
oldPromiseRejectionTracker?.(promise, operation);
if (operation === 'reject') {
this.#io.sendEvent['Runtime.exceptionThrown']({
timestamp: Date.now(),
exceptionDetails: this.createExceptionDetails(promise, true),
});
} else {
const id = this.#exceptionMap.get(promise);
if (id) {
this.#io.sendEvent['Runtime.exceptionRevoked']({
reason: 'Handler added to rejected promise',
exceptionId: id,
});
}
}
};
this.#io.sendEvent['Runtime.executionContextCreated']({ context: descriptor });
}
detachAgent(agent: Agent) {
for (const realm of this.realms) {
if (realm?.agent === agent) {
this.detachRealm(realm.realm);
}
}
}
detachRealm(realm: ManagedRealm) {
const index = this.realms.findIndex((c) => c?.realm === realm);
if (index === -1) {
return;
}
const { descriptor } = this.realms[index]!;
realm.HostDefined.attachingInspector = undefined;
realm.HostDefined.attachingInspectorReportError = undefined;
this.realms[index] = undefined;
this.#io.sendEvent['Runtime.executionContextDestroyed']({ executionContextId: descriptor.id, executionContextUniqueId: descriptor.uniqueId });
}
getRealm(realm: ManagedRealm | string | number | undefined) {
if (realm === undefined) {
if (surroundingAgent.runningExecutionContext && surroundingAgent.currentRealmRecord instanceof ManagedRealm) {
realm = surroundingAgent.currentRealmRecord;
} else {
return undefined;
}
}
if (typeof realm === 'string') {
return this.realms.find((c) => c?.descriptor.uniqueId === realm);
} else if (typeof realm === 'number') {
return this.realms[realm];
}
return this.realms.find((c) => c?.realm === realm);
}
/** @deprecated in this case we are guessing the realm should be using, which may create bad result */
getAnyRealm() {
return this.realms.find(Boolean);
}
#idToObject = new Map<string, ObjectValue | SymbolValue>();
// id 0 is falsy, skip it
#idToArrayBufferBlock: (undefined | ArrayBuffer)[] = [undefined];
#objectToId = new Map<ObjectValue | SymbolValue, string>();
#objectCounter = 1;
#internObject(object: ObjectValue | SymbolValue, group = 'default') {
if (this.#objectToId.has(object)) {
return this.#objectToId.get(object)!;
}
const id = `${group}:${this.#objectCounter}`;
this.#objectCounter += 1;
this.#idToObject.set(id, object);
this.#objectToId.set(object, id);
return id;
}
releaseObject(id: string) {
const object = this.#idToObject.get(id);
if (object) {
this.#idToObject.delete(id);
this.#objectToId.delete(object);
}
}
releaseObjectGroup(group: string) {
for (const [id, object] of this.#idToObject.entries()) {
if (id.startsWith(group)) {
this.#idToObject.delete(id);
this.#objectToId.delete(object);
}
}
}
getObject(objectId: string) {
return this.#idToObject.get(objectId);
}
toRemoteObject(value: Value, options: { objectGroup?: string, generatePreview?: boolean }): Protocol.Runtime.RemoteObject {
return getInspector(value).toRemoteObject(value, (val) => this.#internObject(val, options.objectGroup), options.generatePreview);
}
getProperties({
objectId, accessorPropertiesOnly, generatePreview, nonIndexedPropertiesOnly, ownProperties,
}: Protocol.Runtime.GetPropertiesRequest): Protocol.Runtime.GetPropertiesResponse {
const object = this.getObject(objectId);
if (!(object instanceof ObjectValue)) {
return { result: [] };
}
const wrap = (v: Value) => this.toRemoteObject(v, { generatePreview });
const properties: Protocol.Runtime.PropertyDescriptor[] = [];
const internalProperties: Protocol.Runtime.InternalPropertyDescriptor[] = [];
const privateProperties: Protocol.Runtime.PrivatePropertyDescriptor[] = [];
object.PrivateElements.forEach((value) => {
privateProperties.push({
name: value.Key.Description.stringValue(),
value: value.Value ? wrap(value.Value) : undefined,
get: value.Get ? wrap(value.Get) : undefined,
set: value.Set ? wrap(value.Set) : undefined,
});
});
(() => {
let p: NullValue | ObjectValue = object;
while (p instanceof ObjectValue) {
for (const key of p.properties.keys()) {
if (nonIndexedPropertiesOnly && isIntegerIndex(key)) {
continue;
}
const desc = (p.properties.get(key));
if (!desc) {
return;
}
if (accessorPropertiesOnly && !IsAccessorDescriptor(desc)) {
continue;
}
const descriptor: Protocol.Runtime.PropertyDescriptor = {
name: key instanceof JSStringValue
? key.stringValue()
: SymbolDescriptiveString(key).stringValue(),
value: desc.Value && !('HostUninitializedBindingMarkerObject' in desc.Value) ? wrap(desc.Value) : undefined,
writable: desc.Writable === Value.true,
get: desc.Get ? wrap(desc.Get) : undefined,
set: desc.Set ? wrap(desc.Set) : undefined,
configurable: desc.Configurable === Value.true,
enumerable: desc.Enumerable === Value.true,
wasThrown: false,
isOwn: p === object,
symbol: key instanceof SymbolValue ? wrap(key) : undefined,
};
properties.push(descriptor);
}
if (ownProperties) {
break;
}
if ('Prototype' in p) {
p = (p as OrdinaryObject).Prototype;
} else {
p = Value.null;
}
}
})();
const additionalInternalFields = getInspector(object).toInternalProperties?.(object, (val) => this.#internObject(val, 'default'), generatePreview);
if (additionalInternalFields) {
internalProperties.push(...additionalInternalFields);
}
if ('Prototype' in object) {
internalProperties.push({
name: '[[Prototype]]',
value: wrap(object.Prototype as Value),
});
}
if (isBuiltinFunctionObject(object) && object.nativeFunction.section) {
internalProperties.push({
name: '[[Section]]',
value: {
type: 'string',
value: object.nativeFunction.section,
},
});
}
if (isArrayBufferObject(object) && object.ArrayBufferData instanceof DataBlock) {
internalProperties.push({
name: '[[ArrayBufferByteLength]]',
value: {
type: 'number',
value: object.ArrayBufferByteLength,
},
});
this.#idToArrayBufferBlock.push(object.ArrayBufferData.buffer);
internalProperties.push({
name: '[[ArrayBufferData]]',
value: {
type: 'number',
value: this.#idToArrayBufferBlock.length - 1,
},
});
}
return { result: properties, internalProperties, privateProperties };
}
#exceptionMap = new WeakMap<Value, number>();
createExceptionDetails(completion: ThrowCompletion | Value, isPromise: boolean): Protocol.Runtime.ExceptionDetails {
const value = completion instanceof ThrowCompletion ? completion.Value : completion;
const stack = getHostDefinedErrorStack(value);
const frames = InspectorContext.callSiteToCallFrame(stack);
const exceptionId = this.#objectCounter;
this.#objectCounter += 1;
this.#exceptionMap.set(value, exceptionId);
return {
text: isPromise ? 'Uncaught (in promise)' : 'Uncaught',
stackTrace: stack ? { callFrames: frames } : undefined,
exception: getInspector(value).toRemoteObject(value, (val) => this.#internObject(val), false),
lineNumber: frames[0]?.lineNumber || 0,
columnNumber: frames[0]?.columnNumber || 0,
exceptionId,
scriptId: frames[0]?.scriptId,
url: frames[0]?.url,
};
}
static callSiteToCallFrame(callSite: readonly (CallSite | CallFrame)[] | undefined): Protocol.Runtime.CallFrame[] {
return callSite?.map((call) => call.toCallFrame()!).filter(Boolean) || [];
}
createEvaluationResult(completion: ValueCompletion): Protocol.Runtime.EvaluateResponse {
completion = EnsureCompletion(completion);
if (!(completion.Value instanceof Value)) {
throw new RangeError('Invalid completion value');
}
return {
exceptionDetails: completion instanceof ThrowCompletion ? this.createExceptionDetails(completion, false) : undefined,
result: this.toRemoteObject(completion.Value, {}),
};
}
getDebuggerCallFrame(): Protocol.Debugger.CallFrame[] {
const stacks = getCurrentStack(false);
const length = surroundingAgent.executionContextStack.length;
return stacks.map((stack, index): Protocol.Debugger.CallFrame => {
if (!stack.getScriptId()) {
return undefined!;
}
const scopeChain: Protocol.Debugger.Scope[] = [];
let env: EnvironmentRecord | NullValue = stack.context.LexicalEnvironment;
while (env instanceof EnvironmentRecord) {
const result = getDisplayObjectFromEnvironmentRecord(env);
if (result) {
scopeChain.push({ type: result.type, object: this.toRemoteObject(result.object, {}) });
}
env = env.OuterEnv;
}
return {
callFrameId: String(length - index - 1),
functionName: stack.getFunctionName() || '<anonymous>',
location: {
scriptId: stack.getScriptId()!,
lineNumber: (stack.lineNumber || 1) - 1,
columnNumber: (stack.columnNumber || 1) - 1,
},
this: this.toRemoteObject(HostGetThisEnvironment(stack.context.LexicalEnvironment), {}),
url: stack.getSpecifier() || '',
canBeRestarted: false,
functionLocation: isECMAScriptFunctionObject(stack.context.Function) ? {
lineNumber: (stack.context.Function.ECMAScriptCode?.location.start.line || 1) - 1,
columnNumber: (stack.context.Function.ECMAScriptCode?.location.start.column || 1) - 1,
scriptId: stack.getScriptId() || '',
} : undefined,
scopeChain,
};
}).filter(Boolean);
}
evaluateMode: 'script' | 'module' | 'console' = 'script';
}
function HostGetThisEnvironment(env: EnvironmentRecord | NullValue): Value {
while (!(env instanceof NullValue)) {
const exists = env.HasThisBinding();
if (exists === Value.true) {
const value = (env as EnvironmentRecordWithThisBinding).GetThisBinding();
if (value instanceof ThrowCompletion) {
return Value.undefined;
}
return value as Value;
}
const outer = env.OuterEnv;
env = outer;
}
throw new ReferenceError('No this environment found');
}
function getDisplayObjectFromEnvironmentRecord(record: EnvironmentRecord): undefined | { type: Protocol.Debugger.Scope['type'], object: ObjectValue } {
if (record instanceof DeclarativeEnvironmentRecord) {
const object = OrdinaryObjectCreate(Value.null, ['HostInspectorScopePreview']);
for (const [key, binding] of record.bindings) {
const value = binding.initialized ? binding.value! : OrdinaryObjectCreate(Value.null, ['HostUninitializedBindingMarkerObject']);
if (isArgumentExoticObject(value)) {
continue;
}
object.properties.set(key, Descriptor({
Enumerable: isArgumentExoticObject(value) ? Value.false : Value.true,
Value: value,
Writable: binding.mutable ? Value.true : Value.false,
}));
}
let type: Protocol.Debugger.Scope['type'] = 'block';
if (record instanceof FunctionEnvironmentRecord) {
type = 'local';
} else if (record instanceof ModuleEnvironmentRecord) {
type = 'module';
}
if (type !== 'local' && !object.properties.size) {
return undefined;
}
return { type, object };
} else if (record instanceof ObjectEnvironmentRecord) {
return { type: record.IsWithEnvironment === Value.true ? 'with' : 'global', object: record.BindingObject };
} else if (record instanceof GlobalEnvironmentRecord) {
return { type: 'global', object: record.GlobalThisValue };
}
throw new TypeError('Unknown environment record');
}
+139
View File
@@ -0,0 +1,139 @@
import type { Protocol } from 'devtools-protocol';
import { InspectorContext } from './context.mts';
import * as impl from './methods.mts';
import type { DebuggerContext, DebuggerPreference, DevtoolEvents } from './types.mts';
import { getParsedEvent } from './internal-utils.mts';
import {
Agent, ManagedRealm, Realm, type Arguments,
} from '#self';
const ignoreNamespaces = ['Network'];
const ignoreMethods: string[] = [];
export type { DebuggerPreference } from './types.mts';
export { createConsole } from './utils.mts';
interface AgentRecord {
readonly agent: Agent;
onDetach(): void;
}
export abstract class Inspector {
#context = new InspectorContext(this);
#agents: AgentRecord[] = [];
attachAgent(agent: Agent, priorRealms: ManagedRealm[]) {
const oldOnDebugger = agent.hostDefinedOptions.onDebugger;
agent.hostDefinedOptions.onDebugger = () => {
oldOnDebugger?.();
this.sendEvent['Debugger.paused']({
reason: 'debugCommand',
callFrames: this.#context.getDebuggerCallFrame(),
});
};
const oldOnRealmCreated = agent.hostDefinedOptions.onRealmCreated;
agent.hostDefinedOptions.onRealmCreated = (realm) => {
oldOnRealmCreated?.(realm);
this.#context.attachRealm(realm, agent);
};
const oldOnScriptParsed = agent.hostDefinedOptions.onScriptParsed;
agent.hostDefinedOptions.onScriptParsed = (script, id) => {
oldOnScriptParsed?.(script, id);
const realmId = this.#context.getRealm(script.Realm as ManagedRealm)?.descriptor.id;
if (realmId === undefined) {
return;
}
this.sendEvent['Debugger.scriptParsed'](getParsedEvent(script, id, realmId));
};
this.#agents.push({
agent,
onDetach: () => {
agent.hostDefinedOptions.onDebugger = oldOnDebugger;
agent.hostDefinedOptions.onRealmCreated = oldOnRealmCreated;
agent.hostDefinedOptions.onScriptParsed = oldOnScriptParsed;
this.#agents = this.#agents.filter((x) => x.agent !== agent);
},
});
priorRealms.forEach((realm) => {
this.#context.attachRealm(realm, agent);
});
}
detachAgent(agent: Agent) {
const record = this.#agents.find((x) => x.agent === agent);
record?.onDetach();
this.#context.detachAgent(agent);
}
protected abstract send(data: object): void;
readonly preference: DebuggerPreference = { previewDebug: false };
protected onMessage(id: unknown, methodArg: string, params: unknown): void {
if (ignoreMethods.includes(methodArg)) {
return;
}
const [namespace, method] = methodArg.split('.');
if (ignoreNamespaces.includes(namespace)) {
return;
}
if (!(namespace in impl)) {
// eslint-disable-next-line no-console
console.error(`Unknown namespace requested: ${namespace}`);
return;
}
const ns = (impl as Record<string, object>)[namespace];
if (!(method in ns)) {
// eslint-disable-next-line no-console
console.error(`Unknown method requested: ${namespace}.${method}`);
return;
}
const f = (ns as Record<string, (args: unknown, context: DebuggerContext) => unknown>)[method];
new Promise((resolve) => {
resolve(f(params, this.#debugContext));
}).then((result = {}) => {
this.send({ id, result });
});
}
sendEvent: DevtoolEvents = Object.create(new Proxy({}, {
get: (_, key: string) => {
const f = (params: Record<string, unknown>) => {
this.send({ method: key, params });
};
Object.defineProperty(this.sendEvent, key, { value: f });
return f;
},
}));
console(realm: Realm, type: Protocol.Runtime.ConsoleAPICalledEventType, args: Arguments) {
const context = this.#context.getRealm(realm as ManagedRealm);
if (!context) {
return;
}
this.sendEvent['Runtime.consoleAPICalled']({
type,
args: args.map((x) => this.#context.toRemoteObject(x, { })),
executionContextId: context.descriptor.id,
timestamp: Date.now(),
});
}
#debugContext: DebuggerContext = {
sendEvent: this.sendEvent,
preference: this.preference,
context: this.#context,
onDebuggerAttached: () => {
this.#context.realms.forEach((realm) => {
if (realm) {
this.sendEvent['Runtime.executionContextCreated']({
context: realm.descriptor,
});
}
});
},
};
}
+624
View File
@@ -0,0 +1,624 @@
import type { Protocol } from 'devtools-protocol';
import {
BigIntValue,
Descriptor,
evalQ,
Get,
IntrinsicsFunctionToString, isArrayBufferObject, isArrayExoticObject, IsCallable, isDataViewObject, isDateObject, isECMAScriptFunctionObject, isErrorObject, isIntegerIndex, isMapObject, isPromiseObject, isProxyExoticObject, isRegExpObject, isSetObject, isTypedArrayObject, isWeakMapObject, isWeakSetObject, JSStringValue, NumberValue, ObjectValue, PrivateElementRecord, PrivateName, R, surroundingAgent, SymbolDescriptiveString, SymbolValue, ToString, skipDebugger, UndefinedValue, Value, type ArrayBufferObject, type BooleanValue, type DataViewObject, type DateObject, type FunctionObject, type MapObject, type NullValue, type PromiseObject, type PropertyKeyValue, type ProxyObject, type RegExpObject, type SetObject, type TypedArrayObject,
type WeakMapObject,
type WeakSetObject,
type ModuleNamespaceObject,
isModuleNamespaceObject,
DataBlock,
TypedArrayGetElement,
TypedArrayLength,
MakeTypedArrayWithBufferWitnessRecord,
DateProto_toISOString,
ValueOfNormalCompletion,
NormalCompletion,
type ShadowRealmObject,
isShadowRealmObject,
isWrappedFunctionExoticObject,
ArrayExoticObjectInternalMethods,
F,
type TemporalInstantObject,
TemporalInstantToString,
isTemporalInstantObject,
TemporalDurationToString,
type TemporalDurationObject,
isTemporalDurationObject,
isTemporalPlainDateObject,
type TemporalPlainDateObject,
ISODateTimeToString,
isTemporalPlainDateTimeObject,
type TemporalPlainDateTimeObject,
TemporalMonthDayToString,
isTemporalPlainMonthDayObject,
type TemporalPlainMonthDayObject,
TimeRecordToString,
isTemporalPlainTimeObject,
type TemporalPlainTimeObject,
TemporalYearMonthToString,
isTemporalPlainYearMonthObject,
type TemporalPlainYearMonthObject,
TemporalDateToString,
TemporalZonedDateTimeToString,
isTemporalZonedDateTimeObject,
type TemporalZonedDateTimeObject,
} from '#self';
/*
Test code: copy this into the inspector console.
primitive: console.log('primitive:', null, undefined, true, false, 0, -0, NaN, Infinity, -Infinity, 1n, 'string', Symbol(), Symbol('text'), Symbol.for('global'), Symbol.iterator);
fn: console.log('builtin:', eval, '\nfunction:', function() { code }, '\ngenerator:', function*() { code }, '\nasync:', async function() { code }, '\nasync generator:', async function*() { code }, '\narrow:', () => { code }, '\narrow async:', async () => { code });
normal: console.log('normal:', {}, new (class T { #a }), globalThis);
arraybuffer: console.log('arraybuffer:', new ArrayBuffer(8));
dataview: console.log('dataview:', new DataView(new ArrayBuffer(8)));
map: console.log('map:', new Map(), new Map([[eval, globalThis], [1, 2]]));
set: console.log('set:', new Set(), new Set([1, globalThis]));
weakmap: console.log('weakmap:', new WeakMap(), new WeakMap([[{}, 1]]));
weakset: console.log('weakset:', new WeakSet(), new WeakSet([{}]));
date: console.log('date:', new Date());
promise: console.log('promise:', new Promise(() => {}), Promise.resolve(globalThis), Promise.reject(globalThis));
proxy: { const x = Proxy.revocable({}, {}); x.revoke(); console.log('proxy:', new Proxy({}, {}), new Proxy(function() {}, {}), x.proxy); }
regexp: console.log('regexp:', /pattern/, new RegExp('pattern', 'g'));
array: console.log('array:', [], [1, 2], Object.assign([1, 2], { a: 1 }), [0, ,,, 3]);
typedarray: console.log('typedarray:', new Int8Array(8), new Int16Array(8), new Int32Array(8), new Uint8Array(8), new Uint16Array(8), new Uint32Array(8), new Uint8ClampedArray(8), new Float32Array(8), new Float64Array(8), new BigInt64Array(8), new BigUint64Array(8));
*/
interface Inspector<T extends Value> {
toRemoteObject(value: T, getObjectId: (val: SymbolValue | ObjectValue) => string, generatePreview: boolean | undefined): Protocol.Runtime.RemoteObject;
toObjectPreview(value: T): Protocol.Runtime.ObjectPreview;
toPropertyPreview(name: string, value: T): Protocol.Runtime.PropertyPreview;
toDescription(value: T): string;
toInternalProperties?(value: T, getObjectId: (val: SymbolValue | ObjectValue) => string, generatePreview: boolean | undefined): Protocol.Runtime.InternalPropertyDescriptor[];
}
const Null: Inspector<NullValue> = {
toRemoteObject: () => ({ type: 'object', subtype: 'null', value: null }),
toObjectPreview: () => ({
type: 'object', subtype: 'null', properties: [], overflow: false,
}),
toPropertyPreview: (name) => ({
name, type: 'object', subtype: 'null', value: 'null',
}),
toDescription: () => '',
};
const Undefined: Inspector<UndefinedValue> = {
toRemoteObject: () => ({ type: 'undefined' }),
toObjectPreview: () => ({
type: 'undefined', properties: [], overflow: false,
}),
toPropertyPreview: (name) => ({
name, type: 'undefined', value: 'undefined',
}),
toDescription: () => 'undefined',
};
const Boolean: Inspector<BooleanValue> = {
toRemoteObject: (value) => ({ type: 'boolean', value: value.booleanValue() }),
toPropertyPreview: (name, value) => ({
name, type: 'boolean', value: value.booleanValue().toString(),
}),
toObjectPreview(value) {
return {
type: 'boolean',
value: value.booleanValue(),
description: value.booleanValue().toString(),
overflow: false,
properties: [],
};
},
toDescription: (value) => value.booleanValue().toString(),
};
const Symbol: Inspector<SymbolValue> = {
toRemoteObject: (value, getObjectId) => ({
type: 'symbol',
description: SymbolDescriptiveString(value).stringValue(),
objectId: getObjectId(value),
}),
toPropertyPreview: (name, value) => ({
name, type: 'symbol', value: SymbolDescriptiveString(value).stringValue(),
}),
toObjectPreview: (value) => ({
type: 'symbol',
description: SymbolDescriptiveString(value).stringValue(),
overflow: false,
properties: [],
}),
toDescription: (value) => SymbolDescriptiveString(value).stringValue(),
};
const String: Inspector<JSStringValue> = {
toRemoteObject: (value) => ({ type: 'string', value: value.stringValue() }),
toPropertyPreview(name, value) {
return {
name, type: 'string', value: value.stringValue(),
};
},
toObjectPreview(value) {
return {
type: 'string',
description: value.stringValue(),
overflow: false,
properties: [],
};
},
toDescription: (value) => value.stringValue(),
};
const Number: Inspector<NumberValue> = {
toRemoteObject(value) {
const v = R(value);
let description = v.toString();
const isNeg0 = Object.is(v, -0);
// Includes values `-0`, `NaN`, `Infinity`, `-Infinity`, and bigint literals.
if (isNeg0 || !globalThis.Number.isFinite(v)) {
if (typeof v === 'bigint') {
description += 'n';
return { type: 'bigint', unserializableValue: description, description };
}
return { type: 'number', unserializableValue: description, description: isNeg0 ? '-0' : description };
}
return { type: 'number', value: v, description };
},
toPropertyPreview(name, value) {
return {
name, type: 'number', value: this.toDescription(value),
};
},
toObjectPreview(value) {
return {
type: 'number',
description: this.toDescription(value),
overflow: false,
properties: [],
};
},
toDescription: (value) => {
const r = R(value);
return value instanceof BigIntValue ? `${r}n` : r.toString();
},
};
function unwrapFunction(value: FunctionObject): FunctionObject {
if (isWrappedFunctionExoticObject(value)) {
return unwrapFunction(value.WrappedTargetFunction);
}
return value;
}
const Function: Inspector<FunctionObject> = {
toRemoteObject(value, getObjectId) {
value = unwrapFunction(value);
const result: Protocol.Runtime.RemoteObject = {
type: 'function',
objectId: getObjectId(value),
};
result.description = IntrinsicsFunctionToString(value);
if (isECMAScriptFunctionObject(value) && value.ECMAScriptCode) {
if (value.ECMAScriptCode.type === 'FunctionBody') {
result.className = 'Function';
} else if (value.ECMAScriptCode.type === 'GeneratorBody') {
result.className = 'GeneratorFunction';
} else if (value.ECMAScriptCode.type === 'AsyncBody') {
result.className = 'AsyncFunction';
} else if (value.ECMAScriptCode.type === 'AsyncGeneratorBody') {
result.className = 'AsyncGeneratorFunction';
}
} else {
result.className = 'Function';
}
return result;
},
toPropertyPreview: (name) => ({ name, type: 'function', value: '' }),
toObjectPreview(value) {
return {
type: 'function',
description: IntrinsicsFunctionToString(value),
overflow: false,
properties: [],
};
},
toDescription: () => 'Function',
};
class ObjectInspector<T extends ObjectValue> implements Inspector<T> {
subtype;
className;
toDescription;
private toEntries;
private additionalProperties;
private internalProperties;
constructor(
className: string | ((value: Value) => string),
subtype: Protocol.Runtime.RemoteObject['subtype'],
toDescription: (value: T) => string,
additionalOptions?: {
entries?: (value: T) => Protocol.Runtime.ObjectPreview['entries'];
additionalProperties?: (value: T) => Iterable<[string, Value]>;
internalProperties?: (value: T) => Iterable<[string, Value | MapObject['MapData'] | SetObject['SetData']]>;
},
) {
this.className = className;
this.subtype = subtype;
this.toDescription = toDescription;
this.toEntries = additionalOptions?.entries;
this.additionalProperties = additionalOptions?.additionalProperties;
this.internalProperties = additionalOptions?.internalProperties;
}
toRemoteObject(value: T, getObjectId: (val: ObjectValue) => string): Protocol.Runtime.RemoteObject {
return {
type: 'object',
subtype: this.subtype,
objectId: getObjectId(value),
className: typeof this.className === 'string' ? this.className : this.className(value),
description: this.toDescription(value),
preview: this.toObjectPreview(value),
};
}
toPropertyPreview(name: string, value: T): Protocol.Runtime.PropertyPreview {
return {
name,
type: 'object',
subtype: this.subtype,
value: this.toDescription(value),
};
}
toInternalProperties(value: T, getObjectId: (val: ObjectValue | SymbolValue) => string, generatePreview: boolean | undefined): Protocol.Runtime.InternalPropertyDescriptor[] {
const internalProperties = [...this.internalProperties?.(value) || []];
if (!internalProperties.length) {
return [];
}
return internalProperties.map(([name, val]): Protocol.Runtime.InternalPropertyDescriptor => {
let value: Protocol.Runtime.RemoteObject;
if (val instanceof Value) {
value = getInspector(val).toRemoteObject(val, getObjectId, generatePreview);
} else {
const array = new ObjectValue([]);
array.DefineOwnProperty = ArrayExoticObjectInternalMethods.DefineOwnProperty;
array.properties.set('length', Descriptor({ Value: F(val.length) }));
for (const [index, item] of val.entries()) {
let value;
if (item instanceof Value) {
value = item;
} else {
if (!item?.Key || !item.Value) {
continue;
}
value = new ObjectValue(['InspectorEntry']);
value.properties.set('key', Descriptor({ Value: item.Key }));
value.properties.set('value', Descriptor({ Value: item.Value }));
}
array.properties.set(Value(index.toString()), Descriptor({ Value: value }));
}
value = Array.toRemoteObject(array, getObjectId, generatePreview);
}
return ({ name, value });
});
}
toObjectPreview(value: T): Protocol.Runtime.ObjectPreview {
const e = this.toEntries?.(value);
return {
type: 'object',
subtype: this.subtype,
description: this.toDescription(value),
entries: e?.length ? e : undefined,
...propertiesToPropertyPreview(value, [...this.internalProperties?.(value) || [], ...this.additionalProperties?.(value) || []]),
};
}
}
const InspectorEntry = new ObjectInspector<ObjectValue>('Object', 'internal#entry' as never, (value) => {
const key = value.properties.get(Value('key'))!.Value!;
const val = value.properties.get(Value('value'))!.Value!;
return `{${getInspector(key).toDescription(key)} => ${getInspector(val).toDescription(val)}}`;
});
const Default = new ObjectInspector<ObjectValue>('Object', undefined, (object) => {
const [ctor] = object.ConstructedBy;
if (!ctor) {
return 'Object';
}
return propertyNameToString(ctor.HostInitialName);
});
const ArrayBuffer = new ObjectInspector<ArrayBufferObject>('ArrayBuffer', 'arraybuffer', (value) => `ArrayBuffer(${value.ArrayBufferByteLength})`, {});
const DataView = new ObjectInspector<DataViewObject>('DataView', 'dataview', (value) => `DataView(${value.ByteLength})`);
const Error = new ObjectInspector<ObjectValue>('SyntaxError', 'error', (value) => {
let text = '';
surroundingAgent.debugger_scopePreview(() => {
evalQ((Q) => {
if (value instanceof ObjectValue) {
const stack = Q(skipDebugger(Get(value, Value('stack'))));
if (stack !== Value.undefined) {
text += Q(skipDebugger(ToString(stack))).stringValue();
}
}
});
});
return text;
});
const Map = new ObjectInspector<MapObject>('Map', 'map', (value) => `Map(${value.MapData.filter((x) => !!x.Key).length})`, {
additionalProperties: (value) => [['size', Value(value.MapData.filter((x) => !!x.Key).length)]],
internalProperties: (value) => [['[[Entries]]', value.MapData]],
entries: (value) => value.MapData.filter((x) => x.Key).map(({ Key, Value }) => ({
key: getInspector(Key!).toObjectPreview(Key!),
value: getInspector(Value!).toObjectPreview(Value!),
})),
});
const Set = new ObjectInspector<SetObject>('Set', 'set', (value) => `Set(${value.SetData.filter(globalThis.Boolean).length})`, {
additionalProperties: (value) => [['size', Value(value.SetData.filter(globalThis.Boolean).length)]],
internalProperties: (value) => [['[[Entries]]', value.SetData]],
entries: (value) => value.SetData.filter(globalThis.Boolean).map((Value) => ({
value: getInspector(Value!).toObjectPreview(Value!),
})),
});
const WeakMap = new ObjectInspector<WeakMapObject>('WeakMap', 'weakmap', () => 'WeakMap', {
internalProperties: (value) => [['[[Entries]]', value.WeakMapData]],
entries: (value) => value.WeakMapData.filter((x) => x.Key).map(({ Key, Value }) => ({
key: getInspector(Key!).toObjectPreview(Key!),
value: getInspector(Value!).toObjectPreview(Value!),
})),
});
const WeakSet = new ObjectInspector<WeakSetObject>('WeakSet', 'weakset', () => 'WeakSet', {
internalProperties: (value) => [['[[Entries]]', value.WeakSetData]],
entries: (value) => value.WeakSetData.filter(globalThis.Boolean).map((Value) => ({
value: getInspector(Value!).toObjectPreview(Value!),
})),
});
const Date = new ObjectInspector<DateObject>('Date', 'date', ((value: DateObject) => {
if (!globalThis.Number.isFinite(R(value.DateValue))) {
return 'Invalid Date';
}
const val = DateProto_toISOString([], { thisValue: value, NewTarget: Value.undefined });
return ValueOfNormalCompletion(val as NormalCompletion<JSStringValue>).stringValue();
}));
const TemporalInstant = new ObjectInspector<TemporalInstantObject>(
'Temporal.Instant',
'date',
(value) => `Temporal.Instant <${TemporalInstantToString(value, undefined, 'auto')}>`,
);
const TemporalDuration = new ObjectInspector<TemporalDurationObject>('Temporal.Duration', 'date', (value) => `Temporal.Duration <${TemporalDurationToString(value, 'auto')}>`);
const TemporalPlainDate = new ObjectInspector<TemporalPlainDateObject>('Temporal.PlainDate', 'date', (value) => `Temporal.PlainDate <${TemporalDateToString(value, 'auto')}>`);
const TemporalPlainDateTime = new ObjectInspector<TemporalPlainDateTimeObject>(
'Temporal.PlainDateTime',
'date',
(value) => `Temporal.PlainDateTime <${ISODateTimeToString(value.ISODateTime, value.Calendar, 'auto', 'auto')}>`,
);
const TemporalPlainMonthDay = new ObjectInspector<TemporalPlainMonthDayObject>(
'Temporal.PlainMonthDay',
'date',
(value) => `Temporal.PlainMonthDay <${TemporalMonthDayToString(value, 'auto')}>`,
);
const TemporalPlainTime = new ObjectInspector<TemporalPlainTimeObject>('Temporal.PlainTime', 'date', (value) => `Temporal.PlainTime <${TimeRecordToString(value.Time, 'auto')}>`);
const TemporalPlainYearMonth = new ObjectInspector<TemporalPlainYearMonthObject>(
'Temporal.PlainYearMonth',
'date',
(value) => `Temporal.PlainYearMonth <${TemporalYearMonthToString(value, 'auto')}>`,
);
const TemporalZonedDateTime = new ObjectInspector<TemporalZonedDateTimeObject>(
'Temporal.ZonedDateTime',
'date',
(value) => `Temporal.ZonedDateTime <${TemporalZonedDateTimeToString(value, 'auto', 'auto', 'auto', 'auto')}>`,
);
const Promise = new ObjectInspector<PromiseObject>('Promise', 'promise', () => 'Promise', {
internalProperties: (value) => [['[[PromiseState]]', Value(value.PromiseState)], ['[[PromiseResult]]', value.PromiseResult || Value.undefined]],
});
const Proxy = new ObjectInspector<ProxyObject>('Proxy', 'proxy', (value) => {
if (IsCallable(value.ProxyTarget)) {
return 'Proxy(Function)';
}
if (value.ProxyTarget instanceof ObjectValue) {
return 'Proxy(Object)';
}
return 'Proxy';
});
const RegExp = new ObjectInspector<RegExpObject>('RegExp', 'regexp', (value) => `/${value.OriginalSource.stringValue()}/${value.OriginalFlags.stringValue()}`);
const Module = new ObjectInspector<ModuleNamespaceObject>('Module', undefined, () => 'Module', {});
const ShadowRealm = new ObjectInspector<ShadowRealmObject>('ShadowRealm', undefined, () => 'ShadowRealm', {
internalProperties: (realm) => [['[[GlobalObject]]', realm.ShadowRealm.GlobalObject]],
});
const Array: Inspector<ObjectValue> = {
toRemoteObject(value, getObjectId) {
return {
type: 'object',
className: 'Array',
subtype: 'array',
objectId: getObjectId(value),
description: getInspector(value).toDescription(value),
preview: this.toObjectPreview?.(value),
};
},
toPropertyPreview(name, value) {
return {
name, type: 'object', subtype: 'array', value: this.toDescription(value),
};
},
toObjectPreview(value) {
const result: Protocol.Runtime.ObjectPreview = {
type: 'object',
subtype: 'array',
overflow: false,
properties: [],
description: this.toDescription(value),
};
const indexProp: Protocol.Runtime.PropertyPreview[] = [];
const otherProp: Protocol.Runtime.PropertyPreview[] = [];
for (const [key, desc] of value.properties) {
if (indexProp.length > 100) {
result.overflow = true;
break;
}
if (isIntegerIndex(key)) {
indexProp.push(propertyToPropertyPreview(key, desc));
} else if (!(key instanceof JSStringValue && key.stringValue() === 'length')) {
otherProp.push(propertyToPropertyPreview(key, desc));
}
}
result.properties = indexProp.concat(otherProp).slice(0, 100);
return result;
},
toDescription(value) {
const length = [...value.properties.entries()].find(([key]) => key instanceof JSStringValue && key.stringValue() === 'length');
if (!length || !(length[1].Value instanceof NumberValue)) {
throw new TypeError('Bad ArrayExoticObject');
}
return `Array(${R(length[1].Value)})`;
},
};
const TypedArray = new ObjectInspector<TypedArrayObject>('TypedArray', 'typedarray', (value) => `${value.TypedArrayName.stringValue()}(${value.ArrayLength})`);
function propertyNameToString(value: PropertyKeyValue | PrivateName): string {
if (value instanceof JSStringValue) {
return value.stringValue();
} else if (value instanceof PrivateName) {
return value.Description.stringValue();
} else {
return SymbolDescriptiveString(value).stringValue();
}
}
function propertyToPropertyPreview(key: PropertyKeyValue | PrivateName, desc: Descriptor | PrivateElementRecord): Protocol.Runtime.PropertyPreview {
const name = propertyNameToString(key);
if (desc.Get || desc.Set) {
return { name, type: 'accessor' };
} else {
return getInspector(desc.Value!).toPropertyPreview(name, desc.Value!);
}
}
function propertiesToPropertyPreview(value: ObjectValue, extra: undefined | Iterable<[string, Value | MapObject['MapData'] | SetObject['SetData']]>, max = 5) {
let overflow = false;
const properties: Protocol.Runtime.PropertyPreview[] = [];
if (extra) {
for (const [key, value] of extra) {
if (value instanceof Value) {
properties.push(getInspector(value).toPropertyPreview(key, value));
}
// TODO:... handle Value[]
}
}
if (isTypedArrayObject(value) && value.ViewedArrayBuffer instanceof ObjectValue && value.ViewedArrayBuffer.ArrayBufferData instanceof DataBlock) {
const record = MakeTypedArrayWithBufferWitnessRecord(value, 'seq-cst');
const length = TypedArrayLength(record);
for (let index = 0; index < length; index += 1) {
const index_value = TypedArrayGetElement(value, Value(index));
if (index_value instanceof UndefinedValue) {
break;
}
if (properties.length > 100) {
overflow = true;
break;
}
properties.push(getInspector(index_value).toPropertyPreview(index.toString(), index_value));
}
properties.push(
{
name: 'buffer', type: 'object', subtype: 'arraybuffer', value: `ArrayBuffer(${value.ViewedArrayBuffer.ArrayBufferData.byteLength})`,
},
{ name: 'byteLength', type: 'number', value: globalThis.String(value.ArrayLength) },
{ name: 'byteOffset', type: 'number', value: globalThis.String(value.ByteOffset) },
{ name: 'length', type: 'number', value: globalThis.String(length) },
);
}
for (const [key, desc] of value.properties) {
if (properties.length > max) {
overflow = true;
break;
}
properties.push(propertyToPropertyPreview(key, desc));
}
for (const desc of value.PrivateElements) {
if (properties.length > max) {
overflow = true;
break;
}
properties.push(propertyToPropertyPreview(desc.Key, desc));
}
return { overflow, properties };
}
export function getInspector(value: Value): Inspector<Value> {
switch (true) {
case value === Value.null:
return Null;
case value === Value.undefined:
return Undefined;
case value === Value.true || value === Value.false:
return Boolean;
case value instanceof SymbolValue:
return Symbol;
case value instanceof JSStringValue:
return String;
case value instanceof NumberValue:
case value instanceof BigIntValue:
return Number;
case isProxyExoticObject(value):
return Proxy;
case IsCallable(value):
return Function;
case isArrayExoticObject(value):
return Array;
case isRegExpObject(value):
return RegExp;
case isDateObject(value):
return Date;
case isMapObject(value):
return Map;
case isSetObject(value):
return Set;
case isWeakMapObject(value):
return WeakMap;
case isWeakSetObject(value):
return WeakSet;
// generator
case isErrorObject(value):
return Error;
case isPromiseObject(value):
return Promise;
case isTypedArrayObject(value):
return TypedArray;
case isArrayBufferObject(value):
return ArrayBuffer;
case isDataViewObject(value):
return DataView;
case isModuleNamespaceObject(value):
return Module;
case isShadowRealmObject(value):
return ShadowRealm;
case isTemporalInstantObject(value):
return TemporalInstant;
case isTemporalDurationObject(value):
return TemporalDuration;
case isTemporalPlainDateObject(value):
return TemporalPlainDate;
case isTemporalPlainDateTimeObject(value):
return TemporalPlainDateTime;
case isTemporalPlainMonthDayObject(value):
return TemporalPlainMonthDay;
case isTemporalPlainTimeObject(value):
return TemporalPlainTime;
case isTemporalPlainYearMonthObject(value):
return TemporalPlainYearMonth;
case isTemporalZonedDateTimeObject(value):
return TemporalZonedDateTime;
case (value as ObjectValue).internalSlotsList.includes('InspectorEntry'):
return InspectorEntry;
default:
return Default;
}
}
@@ -0,0 +1,18 @@
import type { Protocol } from 'devtools-protocol';
import { DynamicParsedCodeRecord, SourceTextModuleRecord, type ScriptRecord } from '#self';
export function getParsedEvent(source: ScriptRecord | SourceTextModuleRecord | DynamicParsedCodeRecord, id: string, executionContextId: number): Protocol.Debugger.ScriptParsedEvent {
const lines = source.ECMAScriptCode.sourceText.split('\n');
return {
isModule: source instanceof SourceTextModuleRecord,
scriptId: id,
url: source.HostDefined.specifier || `vm:///${id}`,
startLine: 0,
startColumn: 0,
endLine: lines.length,
endColumn: lines.pop()!.length,
executionContextId,
hash: '',
buildId: '',
};
}
File diff suppressed because it is too large Load Diff
+355
View File
@@ -0,0 +1,355 @@
import type { Protocol } from 'devtools-protocol';
import type {
DebuggerContext,
DebuggerNamespace, HeapProfilerNamespace, ProfilerNamespace, RuntimeNamespace,
TargetNamespace,
} from './types.mts';
import { getParsedEvent } from './internal-utils.mts';
import { InspectorContext } from './context.mts';
import {
Call, NormalCompletion, ObjectValue, ParseScript, runJobQueue, ScriptRecord, surroundingAgent, ThrowCompletion, skipDebugger, Value, type FunctionObject,
ParseModule,
SourceTextModuleRecord, performDevtoolsEval,
ValueOfNormalCompletion,
JSStringValue,
evalQ,
Assert,
kInternal,
captureStack,
} from '#self';
export const Debugger: DebuggerNamespace = {
enable(_req, { onDebuggerAttached }) {
onDebuggerAttached();
return { debuggerId: 'debugger.0' };
},
getScriptSource({ scriptId }) {
const source = surroundingAgent.parsedSources.get(scriptId);
if (!source) {
throw new Error('Not found');
}
return { scriptSource: source.ECMAScriptCode.sourceText };
},
setAsyncCallStackDepth() { },
setBlackboxPatterns() { },
setBlackboxExecutionContexts() { },
// #region breakpoints
getPossibleBreakpoints() {
// getPossibleBreakpoints({ start, end, restrictToFunction }) {
return { locations: [] };
// return { locations: getBreakpointCandidates(start, end, restrictToFunction) };
},
removeBreakpoint({ breakpointId }) {
surroundingAgent?.removeBreakpoint(breakpointId);
},
// setBreakpoint({ location, condition }) { },
setBreakpointByUrl(req) {
return surroundingAgent?.addBreakpointByUrl(req);
},
// setBreakpointOnFunctionCall({ objectId, condition }) { },
setBreakpointsActive({ active }) {
surroundingAgent.breakpointsEnabled = active;
},
// setInstrumentationBreakpoint({ instrumentation }) { },
setPauseOnExceptions({ state }) {
if (surroundingAgent) {
surroundingAgent.pauseOnExceptions = state === 'none' ? undefined : state;
}
},
// #endregion
stepInto(_, { sendEvent }) {
sendEvent['Debugger.resumed']();
surroundingAgent.resumeEvaluate({ pauseAt: 'step-in' });
},
resume(_, { sendEvent }) {
sendEvent['Debugger.resumed']();
surroundingAgent.resumeEvaluate();
},
stepOver(_req, { sendEvent }) {
sendEvent['Debugger.resumed']();
surroundingAgent.resumeEvaluate({ pauseAt: 'step-over' });
},
stepOut(_req, { sendEvent }) {
sendEvent['Debugger.resumed']();
surroundingAgent.resumeEvaluate({ pauseAt: 'step-out' });
},
evaluateOnCallFrame(req, context) {
return evaluate({
...req,
uniqueContextId: context.context.getRealm(undefined)!.descriptor.uniqueId,
evalMode: context.context.evaluateMode,
}, context);
},
engine262_setEvaluateMode({ mode }, { context }) {
if (mode === 'module' || mode === 'script' || mode === 'console') {
context.evaluateMode = mode;
}
},
engine262_setFeatures() {
throw new Error('Method should not be implemented here.');
},
};
export const Profiler: ProfilerNamespace = {
enable() { },
};
export const Runtime: RuntimeNamespace = {
discardConsoleEntries() { },
enable() {},
compileScript(options, { context, sendEvent }) {
let parsed!: ScriptRecord | SourceTextModuleRecord | ObjectValue[];
let realm = context.getRealm(options.executionContextId);
if (!realm && !options.persistScript) {
realm = context.getAnyRealm();
}
if (!realm) {
return unsupportedError;
}
realm.realm.scope(() => {
if (context.evaluateMode === 'module') {
parsed = ParseModule(options.expression, realm.realm, { specifier: options.sourceURL, doNotTrackScriptId: !options.persistScript });
} else {
parsed = ParseScript(options.expression, realm.realm, { specifier: options.sourceURL, doNotTrackScriptId: !options.persistScript, [kInternal]: { allowAllPrivateNames: true } });
}
});
if (!parsed) {
throw new Error('No parsed result');
}
if (Array.isArray(parsed)) {
const e = context.createExceptionDetails(ThrowCompletion(parsed[0]), false);
// Note: it has to be this message to trigger devtools' line wrap.
e.exception!.description = 'SyntaxError: Unexpected end of input';
return { exceptionDetails: e };
}
if (options.persistScript) {
if (realm?.descriptor.id === undefined) {
throw new Error('No realm id found');
}
const event = getParsedEvent(parsed, parsed.HostDefined.scriptId!, realm.descriptor.id);
sendEvent['Debugger.scriptParsed'](event);
return { scriptId: event.scriptId };
}
return {};
},
callFunctionOn(options, { context }): Protocol.Runtime.CallFunctionOnResponse {
const realmDesc = context.getRealm(options.uniqueContextId || options.executionContextId) || context.getAnyRealm();
if (!realmDesc) {
throw new Error('No realm found');
}
const { Value: F } = realmDesc.realm.evaluateScript(`(${options.functionDeclaration})`, { doNotTrackScriptId: true }) as NormalCompletion<FunctionObject>;
const thisValue = options.objectId
? context.getObject(options.objectId)!
: Value.undefined;
const args = options.arguments?.map((a) => {
// TODO: revisit
if ('value' in a) {
return Value(a.value);
}
if (a.objectId) {
return context.getObject(a.objectId)!;
}
if ('unserializableValue' in a) {
throw new RangeError();
}
return Value.undefined;
});
return realmDesc.realm.scope((): Protocol.Runtime.CallFunctionOnResponse => {
const completion = evalQ((Q, X): Protocol.Runtime.CallFunctionOnResponse => {
const r = Q(skipDebugger(Call(F, thisValue, args || [])));
if (options.returnByValue) {
const value = X(Call(realmDesc.realm.Intrinsics['%JSON.stringify%'], Value.undefined, [r]));
if (value instanceof JSStringValue) {
const valueRealized = JSON.parse(value.stringValue());
return { result: { type: typeof value, value: valueRealized } };
}
}
return context.createEvaluationResult(r);
});
if (completion instanceof ThrowCompletion) {
return { result: { type: 'undefined' }, exceptionDetails: context.createExceptionDetails(completion, false) };
}
return completion.Value;
});
},
evaluate(options, context) {
return evaluate({
...options,
evalMode: context.context.evaluateMode,
uniqueContextId: options.uniqueContextId!,
}, context);
},
getExceptionDetails(req, { context }) {
const object = context.getObject(req.errorObjectId)!;
if (object instanceof ObjectValue) {
return {
exceptionDetails: context.createExceptionDetails(ThrowCompletion(object), false),
};
}
return {
exceptionDetails: {
text: 'unsupported', lineNumber: 0, columnNumber: 0, exceptionId: 0,
},
};
},
getHeapUsage() {
return {
usedSize: 0, totalSize: 0, backingStorageSize: 0, embedderHeapUsedSize: 0,
};
},
getIsolateId() {
return { id: 'isolate.0' };
},
getProperties(options, { context }) {
return context.getProperties(options);
},
globalLexicalScopeNames({ executionContextId }, { context }) {
const global = context.getRealm(executionContextId)?.realm.GlobalObject;
if (!global) {
return { names: [] };
}
const keys = skipDebugger(global.OwnPropertyKeys());
if (keys instanceof ThrowCompletion) {
return { names: [] };
}
return { names: ValueOfNormalCompletion(keys).map((k) => (k instanceof JSStringValue ? k.stringValue() : null!)).filter(Boolean) };
},
releaseObject(req, { context }) {
context.releaseObject(req.objectId);
},
releaseObjectGroup({ objectGroup }, { context }) {
context.releaseObjectGroup(objectGroup);
},
runIfWaitingForDebugger() { },
};
export const HeapProfiler: HeapProfilerNamespace = {
enable() { },
collectGarbage() { },
};
export const Target: TargetNamespace = {
setDiscoverTargets() { },
// @ts-expect-error no doc
setRemoteLocations() { },
};
const unsupportedError: Protocol.Runtime.EvaluateResponse = {
result: { type: 'undefined' },
exceptionDetails: {
text: 'unsupported', lineNumber: 0, columnNumber: 0, exceptionId: 0,
},
};
function evaluate(options: {
uniqueContextId: string,
expression: string,
evalMode: InspectorContext['evaluateMode'],
throwOnSideEffect?: boolean,
awaitPromise?: boolean,
callFrameId?: string,
}, _context: DebuggerContext): Protocol.Runtime.EvaluateResponse | Promise<Protocol.Runtime.EvaluateResponse> {
const { context } = _context;
const isPreview = options.throwOnSideEffect;
if (options.awaitPromise) {
return unsupportedError;
}
const realm = context.getRealm(options.uniqueContextId);
if (!realm) {
return unsupportedError;
}
const isCallOnFrame = typeof options.callFrameId === 'string';
let callOnFramePoppedLevel = 0;
const oldExecutionStack = [...surroundingAgent.executionContextStack];
if (isCallOnFrame) {
const frame = surroundingAgent.executionContextStack[options.callFrameId as `${number}`];
if (!frame) {
// eslint-disable-next-line no-console
console.error('Execution context not found: ', options.callFrameId);
return unsupportedError;
}
for (const currentFrame of [...surroundingAgent.executionContextStack].reverse()) {
if (currentFrame === frame) {
break;
}
callOnFramePoppedLevel += 1;
surroundingAgent.executionContextStack.pop(currentFrame);
}
}
const promise = new Promise<Protocol.Runtime.EvaluateResponse>((resolve) => {
let toBeEvaluated;
if (isPreview || options.evalMode === 'console' || isCallOnFrame) {
toBeEvaluated = performDevtoolsEval(options.expression, realm.realm, false, !!(isPreview || isCallOnFrame));
} else {
let parsed!: ScriptRecord | SourceTextModuleRecord | ObjectValue[];
const realm = context.getRealm(options.uniqueContextId);
realm?.realm.scope(() => {
if (options.evalMode === 'module') {
parsed = ParseModule(options.expression, realm.realm);
} else {
parsed = ParseScript(options.expression, realm.realm);
}
});
if (Array.isArray(parsed)) {
const e = context.createExceptionDetails(ThrowCompletion(parsed[0]), false);
resolve({ exceptionDetails: e, result: { type: 'undefined' } });
return;
}
toBeEvaluated = parsed;
}
const noDebuggerEvaluate = () => {
if (!('next' in toBeEvaluated)) {
throw new Assert.Error('Unexpected');
}
resolve(context.createEvaluationResult(skipDebugger(toBeEvaluated)));
};
if (isPreview) {
surroundingAgent.debugger_scopePreview(noDebuggerEvaluate);
return;
}
if (isCallOnFrame) {
noDebuggerEvaluate();
return;
}
const completion = realm.realm.evaluate(toBeEvaluated, (completion) => {
resolve(context.createEvaluationResult(completion));
runJobQueue();
});
if (completion) {
return;
}
surroundingAgent.resumeEvaluate();
});
promise.then(() => {
if (callOnFramePoppedLevel) {
Assert(oldExecutionStack.length - callOnFramePoppedLevel === surroundingAgent.executionContextStack.length);
for (const [newIndex, newStack] of surroundingAgent.executionContextStack.entries()) {
Assert(newStack === oldExecutionStack[newIndex]);
}
surroundingAgent.executionContextStack.length = 0;
for (const stack of oldExecutionStack) {
surroundingAgent.executionContextStack.push(stack);
}
}
}, (err): Protocol.Runtime.EvaluateResponse => {
const expr = surroundingAgent.runningExecutionContext.callSite.lastNode?.sourceText;
const frame = InspectorContext.callSiteToCallFrame(captureStack().stack);
_context.sendEvent['Runtime.exceptionThrown']({
timestamp: Date.now(),
exceptionDetails: {
stackTrace: frame.length ? { callFrames: frame } : undefined,
text: `engine262 error when evaluating the following node:\n\n ${expr}\n\n${err.constructor.name}: ${err.message}\n${err.stack.slice(err.stack.indexOf(err.message) + err.message.length + 1)}\n\nFrom now on, the engine262 VM state is broken, please press the reload button.`,
columnNumber: frame[0]?.columnNumber,
lineNumber: frame[0]?.lineNumber,
scriptId: frame[0]?.scriptId,
url: frame[0]?.url,
exceptionId: 0,
},
});
return {
result: { type: 'undefined' },
};
});
return promise;
}
+17
View File
@@ -0,0 +1,17 @@
{
"references": [{ "path": "../../src/" }],
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"composite": true,
"incremental": true,
"declarationDir": "../../lib/inspector",
"tsBuildInfoFile": "../../lib/inspector/.tsbuildinfo",
"erasableSyntaxOnly": true,
"rootDir": "./",
"outDir": "../../lib/inspector/",
"allowImportingTsExtensions": true,
"rewriteRelativeImportExtensions": true,
"resolveJsonModule": true
},
"include": ["./*.mts"]
}
+225
View File
@@ -0,0 +1,225 @@
import type { Protocol } from 'devtools-protocol';
import type { InspectorContext } from './context.mts';
export interface DebuggerPreference {
previewDebug: boolean;
}
export interface DebuggerContext {
sendEvent: DevtoolEvents;
onDebuggerAttached(): void;
preference: DebuggerPreference;
context: InspectorContext;
}
export interface DebuggerNamespace {
engine262_setEvaluateMode(req: { mode: 'module' | 'script' | 'console' }, context: DebuggerContext): void;
engine262_setFeatures(req: { features: string[] }, context: DebuggerContext): void;
}
export interface DebuggerNamespace {
/** https://chromedevtools.github.io/devtools-protocol/v8/Debugger/#method-continueToLocation */
continueToLocation?(req: Protocol.Debugger.ContinueToLocationRequest, context: DebuggerContext): void;
/** https://chromedevtools.github.io/devtools-protocol/v8/Debugger/#method-disable */
disable?(req: void, context: DebuggerContext): void;
/** https://chromedevtools.github.io/devtools-protocol/v8/Debugger/#method-enable */
enable?(req: Protocol.Debugger.EnableRequest, context: DebuggerContext): Protocol.Debugger.EnableResponse;
/** https://chromedevtools.github.io/devtools-protocol/v8/Debugger/#method-evaluateOnCallFrame */
evaluateOnCallFrame?(req: Protocol.Debugger.EvaluateOnCallFrameRequest, context: DebuggerContext): Protocol.Debugger.EvaluateOnCallFrameResponse | Promise<Protocol.Debugger.EvaluateOnCallFrameResponse>;
/** https://chromedevtools.github.io/devtools-protocol/v8/Debugger/#method-getPossibleBreakpoints */
getPossibleBreakpoints?(req: Protocol.Debugger.GetPossibleBreakpointsRequest, context: DebuggerContext): Protocol.Debugger.GetPossibleBreakpointsResponse;
/** https://chromedevtools.github.io/devtools-protocol/v8/Debugger/#method-getScriptSource */
getScriptSource?(req: Protocol.Debugger.GetScriptSourceRequest, context: DebuggerContext): Protocol.Debugger.GetScriptSourceResponse;
/** https://chromedevtools.github.io/devtools-protocol/v8/Debugger/#method-pause */
pause?(req: void, context: DebuggerContext): void;
/** https://chromedevtools.github.io/devtools-protocol/v8/Debugger/#method-removeBreakpoint */
removeBreakpoint?(req: Protocol.Debugger.RemoveBreakpointRequest, context: DebuggerContext): void;
/** https://chromedevtools.github.io/devtools-protocol/v8/Debugger/#method-restartFrame */
restartFrame?(req: Protocol.Debugger.RestartFrameRequest, context: DebuggerContext): Protocol.Debugger.RestartFrameResponse;
/** https://chromedevtools.github.io/devtools-protocol/v8/Debugger/#method-resume */
resume?(req: void, context: DebuggerContext): void;
/** https://chromedevtools.github.io/devtools-protocol/v8/Debugger/#method-searchInContent */
searchInContent?(req: Protocol.Debugger.SearchInContentRequest, context: DebuggerContext): Protocol.Debugger.SearchInContentResponse;
/** https://chromedevtools.github.io/devtools-protocol/v8/Debugger/#method-setAsyncCallStackDepth */
setAsyncCallStackDepth?(req: Protocol.Debugger.SetAsyncCallStackDepthRequest, context: DebuggerContext): void;
/** https://chromedevtools.github.io/devtools-protocol/v8/Debugger/#method-setBreakpoint */
setBreakpoint?(req: Protocol.Debugger.SetBreakpointRequest, context: DebuggerContext): Protocol.Debugger.SetBreakpointResponse;
/** https://chromedevtools.github.io/devtools-protocol/v8/Debugger/#method-setBreakpointByUrl */
setBreakpointByUrl?(req: Protocol.Debugger.SetBreakpointByUrlRequest, context: DebuggerContext): Protocol.Debugger.SetBreakpointByUrlResponse;
/** https://chromedevtools.github.io/devtools-protocol/v8/Debugger/#method-setBreakpointsActive */
setBreakpointsActive?(req: Protocol.Debugger.SetBreakpointsActiveRequest, context: DebuggerContext): void;
/** https://chromedevtools.github.io/devtools-protocol/v8/Debugger/#method-setInstrumentationBreakpoint */
setInstrumentationBreakpoint?(req: Protocol.Debugger.SetInstrumentationBreakpointRequest, context: DebuggerContext): Protocol.Debugger.SetInstrumentationBreakpointResponse;
/** https://chromedevtools.github.io/devtools-protocol/v8/Debugger/#method-setPauseOnExceptions */
setPauseOnExceptions?(req: Protocol.Debugger.SetPauseOnExceptionsRequest, context: DebuggerContext): void;
/** https://chromedevtools.github.io/devtools-protocol/v8/Debugger/#method-setScriptSource */
setScriptSource?(req: Protocol.Debugger.SetScriptSourceRequest, context: DebuggerContext): Protocol.Debugger.SetScriptSourceResponse;
/** https://chromedevtools.github.io/devtools-protocol/v8/Debugger/#method-setSkipAllPauses */
setSkipAllPauses?(req: Protocol.Debugger.SetSkipAllPausesRequest, context: DebuggerContext): void;
/** https://chromedevtools.github.io/devtools-protocol/v8/Debugger/#method-setVariableValue */
setVariableValue?(req: Protocol.Debugger.SetVariableValueRequest, context: DebuggerContext): void;
/** https://chromedevtools.github.io/devtools-protocol/v8/Debugger/#method-stepInto */
stepInto?(req: Protocol.Debugger.StepIntoRequest, context: DebuggerContext): void;
/** https://chromedevtools.github.io/devtools-protocol/v8/Debugger/#method-stepOut */
stepOut?(req: void, context: DebuggerContext): void;
/** https://chromedevtools.github.io/devtools-protocol/v8/Debugger/#method-stepOver */
stepOver?(req: Protocol.Debugger.StepOverRequest, context: DebuggerContext): void;
/** https://chromedevtools.github.io/devtools-protocol/v8/Debugger/#method-getStackTrace */
getStackTrace?(req: Protocol.Debugger.GetStackTraceRequest, context: DebuggerContext): Protocol.Debugger.GetStackTraceResponse;
/** https://chromedevtools.github.io/devtools-protocol/v8/Debugger/#method-setBlackboxedRanges */
setBlackboxedRanges?(req: Protocol.Debugger.SetBlackboxedRangesRequest, context: DebuggerContext): void;
/** https://chromedevtools.github.io/devtools-protocol/v8/Debugger/#method-setBlackboxExecutionContexts */
setBlackboxExecutionContexts?(req: Protocol.Debugger.SetBlackboxExecutionContextsRequest, context: DebuggerContext): void;
/** https://chromedevtools.github.io/devtools-protocol/v8/Debugger/#method-setBlackboxPatterns */
setBlackboxPatterns?(req: Protocol.Debugger.SetBlackboxPatternsRequest, context: DebuggerContext): void;
/** https://chromedevtools.github.io/devtools-protocol/v8/Debugger/#method-setBreakpointOnFunctionCall */
setBreakpointOnFunctionCall?(req: Protocol.Debugger.SetBreakpointOnFunctionCallRequest, context: DebuggerContext): Protocol.Debugger.SetBreakpointOnFunctionCallResponse;
/** https://chromedevtools.github.io/devtools-protocol/v8/Debugger/#method-setReturnValue */
setReturnValue?(req: Protocol.Debugger.SetReturnValueRequest, context: DebuggerContext): void;
}
export interface ProfilerNamespace {
/** https://chromedevtools.github.io/devtools-protocol/v8/Profiler/#method-disable */
disable?(req: void, context: DebuggerContext): void;
/** https://chromedevtools.github.io/devtools-protocol/v8/Profiler/#method-enable */
enable?(req: void, context: DebuggerContext): void;
/** https://chromedevtools.github.io/devtools-protocol/v8/Profiler/#method-getBestEffortCoverage */
getBestEffortCoverage?(req: void, context: DebuggerContext): Protocol.Profiler.GetBestEffortCoverageResponse;
/** https://chromedevtools.github.io/devtools-protocol/v8/Profiler/#method-setSamplingInterval */
setSamplingInterval?(req: Protocol.Profiler.SetSamplingIntervalRequest, context: DebuggerContext): void;
/** https://chromedevtools.github.io/devtools-protocol/v8/Profiler/#method-start */
start?(req: void, context: DebuggerContext): void;
/** https://chromedevtools.github.io/devtools-protocol/v8/Profiler/#method-startPreciseCoverage */
startPreciseCoverage?(req: Protocol.Profiler.StartPreciseCoverageRequest, context: DebuggerContext): Protocol.Profiler.StartPreciseCoverageResponse;
/** https://chromedevtools.github.io/devtools-protocol/v8/Profiler/#method-stop */
stop?(req: void, context: DebuggerContext): void;
/** https://chromedevtools.github.io/devtools-protocol/v8/Profiler/#method-stopPreciseCoverage */
stopPreciseCoverage?(req: void, context: DebuggerContext): void;
/** https://chromedevtools.github.io/devtools-protocol/v8/Profiler/#method-takePreciseCoverage */
takePreciseCoverage?(req: void, context: DebuggerContext): Protocol.Profiler.TakePreciseCoverageResponse;
}
export interface RuntimeNamespace {
/** https://chromedevtools.github.io/devtools-protocol/v8/Runtime/#method-addBinding */
addBinding?(req: Protocol.Runtime.AddBindingRequest, context: DebuggerContext): void;
/** https://chromedevtools.github.io/devtools-protocol/v8/Runtime/#method-awaitPromise */
awaitPromise?(req: Protocol.Runtime.AwaitPromiseRequest, context: DebuggerContext): Protocol.Runtime.AwaitPromiseResponse;
/** https://chromedevtools.github.io/devtools-protocol/v8/Runtime/#method-callFunctionOn */
callFunctionOn?(req: Protocol.Runtime.CallFunctionOnRequest, context: DebuggerContext): Protocol.Runtime.CallFunctionOnResponse;
/** https://chromedevtools.github.io/devtools-protocol/v8/Runtime/#method-compileScript */
compileScript?(req: Protocol.Runtime.CompileScriptRequest, context: DebuggerContext): Protocol.Runtime.CompileScriptResponse;
/** https://chromedevtools.github.io/devtools-protocol/v8/Runtime/#method-disable */
disable?(req: void, context: DebuggerContext): void;
/** https://chromedevtools.github.io/devtools-protocol/v8/Runtime/#method-discardConsoleEntries */
discardConsoleEntries?(req: void, context: DebuggerContext): void;
/** https://chromedevtools.github.io/devtools-protocol/v8/Runtime/#method-enable */
enable?(req: void, context: DebuggerContext): void;
/** https://chromedevtools.github.io/devtools-protocol/v8/Runtime/#method-evaluate */
evaluate?(req: Protocol.Runtime.EvaluateRequest, context: DebuggerContext): Protocol.Runtime.EvaluateResponse | Promise<Protocol.Runtime.EvaluateResponse>;
/** https://chromedevtools.github.io/devtools-protocol/v8/Runtime/#method-getProperties */
getProperties?(req: Protocol.Runtime.GetPropertiesRequest, context: DebuggerContext): Protocol.Runtime.GetPropertiesResponse;
/** https://chromedevtools.github.io/devtools-protocol/v8/Runtime/#method-globalLexicalScopeNames */
globalLexicalScopeNames?(req: Protocol.Runtime.GlobalLexicalScopeNamesRequest, context: DebuggerContext): Protocol.Runtime.GlobalLexicalScopeNamesResponse;
/** https://chromedevtools.github.io/devtools-protocol/v8/Runtime/#method-queryObjects */
queryObjects?(req: Protocol.Runtime.QueryObjectsRequest, context: DebuggerContext): Protocol.Runtime.QueryObjectsResponse;
/** https://chromedevtools.github.io/devtools-protocol/v8/Runtime/#method-releaseObject */
releaseObject?(req: Protocol.Runtime.ReleaseObjectRequest, context: DebuggerContext): void;
/** https://chromedevtools.github.io/devtools-protocol/v8/Runtime/#method-releaseObjectGroup */
releaseObjectGroup?(req: Protocol.Runtime.ReleaseObjectGroupRequest, context: DebuggerContext): void;
/** https://chromedevtools.github.io/devtools-protocol/v8/Runtime/#method-removeBinding */
removeBinding?(req: Protocol.Runtime.RemoveBindingRequest, context: DebuggerContext): void;
/** https://chromedevtools.github.io/devtools-protocol/v8/Runtime/#method-runIfWaitingForDebugger */
runIfWaitingForDebugger?(req: void, context: DebuggerContext): void;
/** https://chromedevtools.github.io/devtools-protocol/v8/Runtime/#method-runScript */
runScript?(req: Protocol.Runtime.RunScriptRequest, context: DebuggerContext): Protocol.Runtime.RunScriptResponse;
/** https://chromedevtools.github.io/devtools-protocol/v8/Runtime/#method-setAsyncCallStackDepth */
setAsyncCallStackDepth?(req: Protocol.Runtime.SetAsyncCallStackDepthRequest, context: DebuggerContext): void;
/** https://chromedevtools.github.io/devtools-protocol/v8/Runtime/#method-getExceptionDetails */
getExceptionDetails?(req: Protocol.Runtime.GetExceptionDetailsRequest, context: DebuggerContext): Protocol.Runtime.GetExceptionDetailsResponse;
/** https://chromedevtools.github.io/devtools-protocol/v8/Runtime/#method-getHeapUsage */
getHeapUsage?(req: void, context: DebuggerContext): Protocol.Runtime.GetHeapUsageResponse;
/** https://chromedevtools.github.io/devtools-protocol/v8/Runtime/#method-getIsolateId */
getIsolateId?(req: void, context: DebuggerContext): Protocol.Runtime.GetIsolateIdResponse;
/** https://chromedevtools.github.io/devtools-protocol/v8/Runtime/#method-setCustomObjectFormatterEnabled */
setCustomObjectFormatterEnabled?(req: Protocol.Runtime.SetCustomObjectFormatterEnabledRequest, context: DebuggerContext): void;
/** https://chromedevtools.github.io/devtools-protocol/v8/Runtime/#method-setMaxCallStackSizeToCapture */
setMaxCallStackSizeToCapture?(req: Protocol.Runtime.SetMaxCallStackSizeToCaptureRequest, context: DebuggerContext): void;
/** https://chromedevtools.github.io/devtools-protocol/v8/Runtime/#method-terminateExecution */
terminateExecution?(req: void, context: DebuggerContext): void;
}
export interface HeapProfilerNamespace {
/** https://chromedevtools.github.io/devtools-protocol/v8/HeapProfiler/#method-addInspectedHeapObject */
addInspectedHeapObject?(req: Protocol.HeapProfiler.AddInspectedHeapObjectRequest, context: DebuggerContext): void;
/** https://chromedevtools.github.io/devtools-protocol/v8/HeapProfiler/#method-collectGarbage */
collectGarbage?(req: void, context: DebuggerContext): void;
/** https://chromedevtools.github.io/devtools-protocol/v8/HeapProfiler/#method-disable */
disable?(req: void, context: DebuggerContext): void;
/** https://chromedevtools.github.io/devtools-protocol/v8/HeapProfiler/#method-enable */
enable?(req: void, context: DebuggerContext): void;
/** https://chromedevtools.github.io/devtools-protocol/v8/HeapProfiler/#method-getHeapObjectId */
getHeapObjectId?(req: Protocol.HeapProfiler.GetHeapObjectIdRequest, context: DebuggerContext): Protocol.HeapProfiler.GetHeapObjectIdResponse;
/** https://chromedevtools.github.io/devtools-protocol/v8/HeapProfiler/#method-getObjectByHeapObjectId */
getObjectByHeapObjectId?(req: Protocol.HeapProfiler.GetObjectByHeapObjectIdRequest, context: DebuggerContext): Protocol.HeapProfiler.GetObjectByHeapObjectIdResponse;
/** https://chromedevtools.github.io/devtools-protocol/v8/HeapProfiler/#method-getSamplingProfile */
getSamplingProfile?(req: void, context: DebuggerContext): Protocol.HeapProfiler.GetSamplingProfileResponse;
/** https://chromedevtools.github.io/devtools-protocol/v8/HeapProfiler/#method-startSampling */
startSampling?(req: Protocol.HeapProfiler.StartSamplingRequest, context: DebuggerContext): void;
/** https://chromedevtools.github.io/devtools-protocol/v8/HeapProfiler/#method-startTrackingHeapObjects */
startTrackingHeapObjects?(req: Protocol.HeapProfiler.StartTrackingHeapObjectsRequest, context: DebuggerContext): void;
/** https://chromedevtools.github.io/devtools-protocol/v8/HeapProfiler/#method-stopSampling */
stopSampling?(req: void, context: DebuggerContext): Protocol.HeapProfiler.StopSamplingResponse;
/** https://chromedevtools.github.io/devtools-protocol/v8/HeapProfiler/#method-stopTrackingHeapObjects */
stopTrackingHeapObjects?(req: Protocol.HeapProfiler.StopTrackingHeapObjectsRequest, context: DebuggerContext): void;
/** https://chromedevtools.github.io/devtools-protocol/v8/HeapProfiler/#method-takeHeapSnapshot */
takeHeapSnapshot?(req: Protocol.HeapProfiler.TakeHeapSnapshotRequest, context: DebuggerContext): void;
}
// https://chromedevtools.github.io/devtools-protocol/1-3/Target/
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
export interface TargetNamespace {
/** https://chromedevtools.github.io/devtools-protocol/1-3/Target/#method-setDiscoverTargets */
setDiscoverTargets?(req: Protocol.Target.SetDiscoverTargetsRequest, context: DebuggerContext): void;
}
export interface DevtoolEvents {
/** https://chromedevtools.github.io/devtools-protocol/v8/Debugger/#event-paused */
'Debugger.paused'(event: Protocol.Debugger.PausedEvent): void;
/** https://chromedevtools.github.io/devtools-protocol/v8/Debugger/#event-resumed */
'Debugger.resumed'(event: void): void;
/** https://chromedevtools.github.io/devtools-protocol/v8/Debugger/#event-scriptFailedToParse */
'Debugger.scriptFailedToParse'(event: Protocol.Debugger.ScriptFailedToParseEvent): void;
/** https://chromedevtools.github.io/devtools-protocol/v8/Debugger/#event-scriptParsed */
'Debugger.scriptParsed'(event: Protocol.Debugger.ScriptParsedEvent): void;
/** https://chromedevtools.github.io/devtools-protocol/v8/HeapProfiler/#event-addHeapSnapshotChunk */
'HeapProfiler.addHeapSnapshotChunk'(event: Protocol.HeapProfiler.AddHeapSnapshotChunkEvent): void;
/** https://chromedevtools.github.io/devtools-protocol/v8/HeapProfiler/#event-heapStatsUpdate */
'HeapProfiler.heapStatsUpdate'(event: Protocol.HeapProfiler.HeapStatsUpdateEvent): void;
/** https://chromedevtools.github.io/devtools-protocol/v8/HeapProfiler/#event-lastSeenObjectId */
'HeapProfiler.lastSeenObjectId'(event: Protocol.HeapProfiler.LastSeenObjectIdEvent): void;
/** https://chromedevtools.github.io/devtools-protocol/v8/HeapProfiler/#event-reportHeapSnapshotProgress */
'HeapProfiler.reportHeapSnapshotProgress'(event: Protocol.HeapProfiler.ReportHeapSnapshotProgressEvent): void;
/** https://chromedevtools.github.io/devtools-protocol/v8/HeapProfiler/#event-resetProfiles */
'HeapProfiler.resetProfiles'(event: void): void;
/** https://chromedevtools.github.io/devtools-protocol/v8/Profiler/#event-consoleProfileFinished */
'Profiler.consoleProfileFinished'(event: Protocol.Profiler.ConsoleProfileFinishedEvent): void;
/** https://chromedevtools.github.io/devtools-protocol/v8/Profiler/#event-consoleProfileStarted */
'Profiler.consoleProfileStarted'(event: Protocol.Profiler.ConsoleProfileStartedEvent): void;
/** https://chromedevtools.github.io/devtools-protocol/v8/Profiler/#event-preciseCoverageDeltaUpdate */
'Profiler.preciseCoverageDeltaUpdate'(event: Protocol.Profiler.PreciseCoverageDeltaUpdateEvent): void;
/** https://chromedevtools.github.io/devtools-protocol/v8/Runtime/#event-consoleAPICalled */
'Runtime.consoleAPICalled'(event: Protocol.Runtime.ConsoleAPICalledEvent): void;
/** https://chromedevtools.github.io/devtools-protocol/v8/Runtime/#event-exceptionRevoked */
'Runtime.exceptionRevoked'(event: Protocol.Runtime.ExceptionRevokedEvent): void;
/** https://chromedevtools.github.io/devtools-protocol/v8/Runtime/#event-exceptionThrown */
'Runtime.exceptionThrown'(event: Protocol.Runtime.ExceptionThrownEvent): void;
/** https://chromedevtools.github.io/devtools-protocol/v8/Runtime/#event-executionContextCreated */
'Runtime.executionContextCreated'(event: Protocol.Runtime.ExecutionContextCreatedEvent): void;
/** https://chromedevtools.github.io/devtools-protocol/v8/Runtime/#event-executionContextDestroyed */
'Runtime.executionContextDestroyed'(event: Protocol.Runtime.ExecutionContextDestroyedEvent): void;
/** https://chromedevtools.github.io/devtools-protocol/v8/Runtime/#event-executionContextsCleared */
'Runtime.executionContextsCleared'(event: void): void;
/** https://chromedevtools.github.io/devtools-protocol/v8/Runtime/#event-inspectRequested */
'Runtime.inspectRequested'(event: Protocol.Runtime.InspectRequestedEvent): void;
/** https://chromedevtools.github.io/devtools-protocol/v8/Runtime/#event-bindingCalled */
'Runtime.bindingCalled'(event: Protocol.Runtime.BindingCalledEvent): void;
}
+81
View File
@@ -0,0 +1,81 @@
import type Protocol from 'devtools-protocol';
import type { Inspector } from './index.mts';
import {
CreateBuiltinFunction, CreateDataProperty, DefinePropertyOrThrow, Descriptor, OrdinaryObjectCreate, surroundingAgent, ThrowCompletion, skipDebugger, Value, type Arguments, type ManagedRealm,
type PlainEvaluator,
type PlainCompletion,
} from '#self';
const consoleMethods = [
'log',
'debug',
'info',
'error',
'warning',
'dir',
'dirxml',
'table',
'trace',
'clear',
'startGroup',
'startGroupCollapsed',
'endGroup',
'assert',
'profile',
'profileEnd',
'count',
'timeEnd',
] as const;
type ConsoleMethod = typeof consoleMethods[number];
export function createConsole(
realm: ManagedRealm,
defaultBehaviour: Partial<Record<ConsoleMethod, (args: Arguments) => void | PlainCompletion<void> | PlainEvaluator<void>>> & { default?: (method: ConsoleMethod, args: Arguments) => void | PlainCompletion<void> | PlainEvaluator<void> },
) {
realm.scope(() => {
const console = OrdinaryObjectCreate(realm.Intrinsics['%Object.prototype%']);
skipDebugger(DefinePropertyOrThrow(
realm.GlobalObject,
Value('console'),
Descriptor({
Configurable: Value.true,
Enumerable: Value.false,
Writable: Value.true,
Value: console,
}),
));
consoleMethods.forEach((method) => {
const f = CreateBuiltinFunction(
function* Console(args): PlainEvaluator<Value> {
if (surroundingAgent.debugger_isPreviewing) {
return Value.undefined;
}
let completion;
if (defaultBehaviour[method]) {
completion = defaultBehaviour[method](args);
} else if (defaultBehaviour.default) {
completion = defaultBehaviour.default(method, args);
}
if (completion) {
if (typeof completion === 'object' && 'next' in completion) {
completion = yield* completion;
}
// Do not use Q(host) here. A host may return something invalid like ReturnCompletion.
if (completion instanceof ThrowCompletion) {
return completion;
}
}
if (realm.HostDefined.attachingInspector) {
(realm.HostDefined.attachingInspector as Inspector).console(realm, method as Protocol.Protocol.Runtime.ConsoleAPICalledEventType, args);
}
return Value.undefined;
},
1,
Value(method),
[],
);
skipDebugger(CreateDataProperty(console, Value(method), f));
});
});
}
+239
View File
@@ -0,0 +1,239 @@
#!/usr/bin/env node
import { start } from 'node:repl';
import { readFileSync } from 'node:fs';
import { resolve } from 'node:path';
import { format as _format, inspect as _inspect, parseArgs } from 'node:util';
// let's try if the following message on old node causes test failure on test262.fyi
// "ExperimentalWarning: Importing JSON modules is an experimental feature and might change at any time"
// import packageJson from '../../package.json' with { type: 'json' };
import { createRequire } from 'node:module';
import { createConsole } from '../inspector/utils.mts';
import type { NodeWebsocketInspector } from './inspector.mts';
import { loadImportedModule } from './module.mts';
import {
setSurroundingAgent, FEATURES, inspect, Value, Completion, AbruptCompletion,
type Arguments,
evalQ,
Agent,
ManagedRealm,
skipDebugger,
type ValueCompletion,
createTest262Intrinsics,
surroundingAgent,
ThrowCompletion,
ValueOfNormalCompletion,
ScriptEvaluation,
type PlainEvaluator,
} from '#self';
const packageJson = createRequire(import.meta.url)('../../package.json');
const help = `
engine262 v${packageJson.version}
Usage:
engine262 [options]
engine262 [options] [input file]
engine262 [input file]
Options:
-h, --help Show help (this screen)
-m, --module Evaluate contents of input-file as a module.
-e, --eval Evaluate the given string.
--features=... A comma separated list of features.
--features=all Enable all features.
--list-features List available features.
--no-test262 Do not expose $ and $262 for test262.
--no-inspector Do not attach an inspector.
--no-preview Do not enable preview in the inspector.
`;
const argv = parseArgs({
args: process.argv.slice(2),
allowPositionals: true,
allowNegative: true,
strict: true,
options: {
'help': { type: 'boolean', short: 'h' },
'eval': { type: 'string', short: 'e' },
'module': { type: 'boolean', short: 'm' },
'features': { type: 'string' },
'list-features': { type: 'boolean' },
'inspector': { type: 'boolean' },
'test262': { type: 'boolean', default: true },
// hidden options
'preview-debug': { type: 'boolean' },
},
});
if (argv.values.help) {
process.stdout.write(help);
process.exit(0);
} else if (argv.values['list-features']) {
let nameLength = 0;
let flagLength = 0;
FEATURES.forEach((f) => {
if (f.name.length > nameLength) {
nameLength = f.name.length;
}
if (f.flag.length > flagLength) {
flagLength = f.flag.length;
}
});
const log = (f: string, n: string, u: string) => {
process.stdout.write(`${f.padEnd(flagLength, ' ')} ${n.padEnd(nameLength, ' ')} ${u}\n`);
};
log('flag', 'name', 'url');
log('----', '----', '---');
FEATURES.forEach((f) => {
log(f.flag, f.name, f.url);
});
process.exit(0);
}
let features: string[];
if (argv.values.features === 'all') {
features = FEATURES.map((f) => f.flag);
} else if (argv.values.features) {
features = argv.values.features.split(',');
} else {
features = [];
}
const agent = new Agent({
features,
supportedImportAttributes: ['type'],
loadImportedModule,
});
setSurroundingAgent(agent);
const realm = new ManagedRealm({ resolverCache: new Map(), name: 'repl', specifier: process.cwd() });
// Define console.log
{
const format = (function* format(args: Arguments): PlainEvaluator<string> {
const str = [];
for (const arg of args.values()) {
// TODO: inspect should return a PlainEvaluator so debugger can hook in.
str.push(inspect(arg));
}
return str.join(' ');
});
createConsole(realm, {
* log(args) {
process.stdout.write(`${yield* format(args)}\n`);
},
* error(args) {
process.stderr.write(`${yield* format(args)}\n`);
},
* debug(args) {
process.stderr.write(`${yield* format(args)}\n`);
},
});
}
if (argv.values.test262) {
createTest262Intrinsics(realm, argv.values.test262);
}
let inspector: NodeWebsocketInspector | undefined;
if (argv.values.inspector !== false) {
let has_ws = false;
try {
await import('ws');
has_ws = true;
} catch {
if (argv.values.inspector === true) {
process.stderr.write('--inspector requires the "ws" package to be installed.\n');
process.exit(1);
}
}
if (has_ws) {
const { NodeWebsocketInspector } = await import('./inspector.mts');
inspector = await NodeWebsocketInspector.new();
inspector.attachAgent(surroundingAgent, [realm]);
inspector.preference.previewDebug = argv.values['preview-debug'] || false;
}
}
function oneShotEval(source: string, filename: string) {
realm.scope(() => {
const completion = evalQ((Q) => {
if (argv.values.module || filename.endsWith('.mjs')) {
const module = Q(realm.compileModule(source, { specifier: filename }));
realm.HostDefined.resolverCache?.set(filename, module);
const load = Q(module.LoadRequestedModules());
if (load.PromiseState === 'rejected') {
Q(ThrowCompletion(load.PromiseResult!));
} else if (load.PromiseState === 'pending') {
throw new Error('Internal error: .LoadRequestedModules() returned a pending promise');
}
Q(module.Link());
const evaluate = Q(skipDebugger(module.Evaluate()));
if (evaluate.PromiseState === 'rejected') {
Q(ThrowCompletion(evaluate.PromiseResult!));
}
} else {
Q(realm.evaluateScript(source, { specifier: filename }));
}
});
if (completion instanceof AbruptCompletion) {
const inspected = inspect(completion);
process.stderr.write(`${inspected}\n`);
process.exit(1);
}
});
inspector?.stop();
}
if (argv.positionals[0]) {
const source = readFileSync(argv.positionals[0], 'utf8');
oneShotEval(source, resolve(argv.positionals[0]));
} else if (!process.stdin.isTTY) {
process.stdin.setEncoding('utf8');
let source = '';
process.stdin.on('data', (data) => {
source += data;
});
process.stdin.once('end', () => {
oneShotEval(source, process.cwd());
});
} else if (argv.values.eval) {
oneShotEval(argv.values.eval, process.cwd());
} else {
process.stdout.write(`${packageJson.name} v${String(packageJson.version).replace('0.0.1-', '')}
Type ".help" for more information. Please report bugs to ${packageJson.bugs.url}
`);
const server = start({
prompt: '> ',
eval: (cmd, _context, _filename, callback) => {
try {
const script = realm.compileScript(cmd, {});
if (script instanceof ThrowCompletion) {
callback(null, script);
return;
}
let c;
surroundingAgent.evaluate(ScriptEvaluation(ValueOfNormalCompletion(script)), (completion) => {
c = completion;
callback(null, completion);
});
if (!c) {
surroundingAgent.resumeEvaluate();
}
} catch (e) {
callback(e as Error, null);
}
},
preview: false,
writer: (o) => realm.scope(() => {
if (o instanceof Value || o instanceof Completion) {
return inspect(o as Value | ValueCompletion);
}
return _inspect(o);
}),
});
server.on('exit', () => inspector?.stop());
}
+58
View File
@@ -0,0 +1,58 @@
/* eslint-disable no-console */
import {
Agent, inspect, ManagedRealm, NormalCompletion, setSurroundingAgent, ThrowCompletion, type Arguments, type PlainEvaluator,
} from '#self';
import { createConsole } from '#self/inspector';
// Agent is the running environment.
const agent = new Agent({
});
// Only one agent can be active at a time.
setSurroundingAgent(agent);
// A Realm is a separate global environment.
// In Web browsers, each iframe has its own Realm and they may interact with each other.
const realm = new ManagedRealm({ resolverCache: new Map(), name: 'My Realm', specifier: process.cwd() });
// Define console.log
{
const format = (function* format(args: Arguments): PlainEvaluator<string> {
const str = [];
for (const arg of args.values()) {
str.push(inspect(arg));
}
return str.join(' ');
});
createConsole(realm, {
* log(args) {
process.stdout.write(`${yield* format(args)}\n`);
},
* error(args) {
process.stderr.write(`${yield* format(args)}\n`);
},
* debug(args) {
process.stderr.write(`${yield* format(args)}\n`);
},
* default(method, args) {
process.stdout.write(`[console.${method}] ${yield* format(args)}\n`);
},
});
}
// Do not forget to use realm.scope when running code.
realm.scope(() => {
// Run ECMAScript code in the Realm.
realm.evaluateScript(`
console.log('Hello from engine262!');
console.log('2 + 2 =', 2 + 2);
`, { specifier: 'example.mts' });
const result = realm.evaluateScript(`
throw new Error('This is an example error');
`, { specifier: 'example.mts' });
if (result instanceof NormalCompletion) {
console.log('No Error');
} else if (result instanceof ThrowCompletion) {
console.error('Caught error from evaluated script:', inspect(result.Value));
}
});
+124
View File
@@ -0,0 +1,124 @@
import http from 'node:http';
import https from 'node:https';
import { WebSocketServer } from 'ws';
import packageJson from '../../package.json' with { type: 'json' };
// Note: typescript will not copy json files, so it will not appear in the lib directory
// eslint-disable-next-line import/no-useless-path-segments
import protocol from '../../lib-src/inspector/js_protocol.json' with { type: 'json' };
import { Inspector } from '../inspector/index.mts';
const ANSI = {
reset: '\u001b[0m',
red: '\u001b[31m',
green: '\u001b[32m',
yellow: '\u001b[33m',
blue: '\u001b[34m',
};
export class NodeWebsocketInspector extends Inspector {
_server: http.Server | https.Server;
_ws: WebSocketServer;
isDebug = false;
protected override send(data: object): void {
const s = JSON.stringify(data);
this._ws.clients.forEach((ws) => {
ws.send(s);
});
}
protected constructor(server: http.Server | https.Server, isDebug: boolean) {
super();
this._server = server;
const ws = new WebSocketServer({ server });
this._ws = ws;
ws.on('connection', (ws) => {
const send = (obj: unknown) => {
const s = JSON.stringify(obj);
ws.send(s);
};
const sendEvent = Object.create(new Proxy({}, {
get: (_, key: string) => {
const f = (params: Record<string, unknown>) => {
send({ method: key, params });
};
Object.defineProperty(sendEvent, key, { value: key });
return f;
},
}));
ws.on('message', (data: string) => {
const { id, method, params } = JSON.parse(data);
if (isDebug) {
process.stdout.write(`${ANSI.green}${method}${ANSI.reset}: ${JSON.stringify(params)}\n`);
}
this.onMessage(id, method, params);
});
});
}
static inspectorHTTPServer(req: http.IncomingMessage, res: http.ServerResponse<http.IncomingMessage>) {
if (req.method !== 'GET') {
res.writeHead(405);
res.end();
return;
}
const json = (obj: unknown) => {
const s = JSON.stringify(obj);
res.writeHead(200, {
'Content-Type': 'application/json',
'Content-Length': Buffer.byteLength(s),
});
res.end(s);
};
switch (req.url) {
case '/json':
case '/json/list':
json([{
description: `${packageJson.name} instance`,
devtoolsFrontendUrl: 'chrome-devtools://devtools/bundled/js_app.html?experiments=true&v8only=true&ws=localhost:9229/',
devtoolsFrontendUrlCompat: 'chrome-devtools://devtools/bundled/inspector.html?experiments=true&v8only=true&ws=localhost:9229/',
faviconUrl: 'https://avatars0.githubusercontent.com/u/51185628',
id: 'inspector.0',
title: 'engine262',
type: 'node',
url: `file://${process.cwd()}`,
webSocketDebuggerUrl: 'ws://localhost:9229/',
}]);
break;
case '/json/version':
json({
'Browser': `${packageJson.name}/v${packageJson.version}`,
'Protocol-Version': `${protocol.version.major}.${protocol.version.minor}`,
});
break;
case '/json/protocol':
json(protocol);
break;
default:
res.writeHead(404);
res.end();
break;
}
}
static new(port = 9229, host = '127.0.0.1', isDebug = !!process.env.DEBUG) {
const server = http.createServer(NodeWebsocketInspector.inspectorHTTPServer);
const inspector = new NodeWebsocketInspector(server, isDebug);
return new Promise<NodeWebsocketInspector>((resolve) => {
server.listen(port, host, () => {
resolve(inspector);
});
});
}
stop() {
this._server.close();
this._ws.close();
}
}
+74
View File
@@ -0,0 +1,74 @@
import { readFile, readFileSync } from 'node:fs';
import path from 'node:path';
import {
evalQ, ManagedRealm, Realm, Throw, ThrowCompletion, type AgentHostDefined,
} from '#self';
export function createLoadImportedModule(getCache = (realm: ManagedRealm) => realm.HostDefined.resolverCache) {
const validateType = (attributes: Map<string, string>, finish: (completion: ThrowCompletion) => void) => {
const type = attributes.get('type');
if (type && type !== 'json') {
finish(Throw('TypeError', 'UnsupportedModuleType', type));
return false;
}
return true;
};
const parseModule = (realm: ManagedRealm, resolved: string, attributes: Map<string, string>, source: string) => (attributes.get('type') === 'json' || resolved.endsWith('.json')
? realm.createJSONModule(resolved, source)
: realm.compileModule(source, { specifier: resolved }));
const loadImportedModuleSyncOrAsync = (
readFile: (path: string, callback: (err: NodeJS.ErrnoException | null, data: string) => void) => void,
...[referrer, specifier, attributes, _hostDefined, finish]: Parameters<NonNullable<AgentHostDefined['loadImportedModule']>>
) => {
const realm = (referrer instanceof Realm ? referrer : referrer.Realm) as ManagedRealm;
const cache = getCache(realm);
if (!referrer.HostDefined.specifier) {
finish(Throw('SyntaxError', 'CouldNotResolveModule', specifier));
return;
}
if (!validateType(attributes, finish)) {
return;
}
evalQ(async (Q) => {
const base = path.dirname(referrer.HostDefined.specifier!);
const resolved = path.resolve(base, specifier);
if (cache?.has(resolved)) {
finish(cache.get(resolved)!);
return;
}
try {
readFile(resolved, (err, data) => {
if (err) {
finish(Throw('SyntaxError', 'CouldNotResolveModule', specifier));
return;
}
const m = Q(parseModule(realm, resolved, attributes, data));
cache?.set(resolved, m);
finish(m);
});
} catch (error) {
finish(Throw('SyntaxError', 'CouldNotResolveModule', specifier));
}
});
};
const loadImportedModule: NonNullable<AgentHostDefined['loadImportedModule']> = loadImportedModuleSyncOrAsync.bind(null, (path, callback) => {
readFile(path, 'utf8', callback);
});
const loadImportedModuleSync: NonNullable<AgentHostDefined['loadImportedModule']> = loadImportedModuleSyncOrAsync.bind(null, (path, callback) => {
try {
const data = readFileSync(path, 'utf8');
callback(null, data);
} catch (error) {
callback(error as NodeJS.ErrnoException, '');
}
});
return { loadImportedModule, loadImportedModuleSync };
}
export const { loadImportedModule, loadImportedModuleSync } = createLoadImportedModule();
+21
View File
@@ -0,0 +1,21 @@
{
"extends": "../../tsconfig.base.json",
"references": [{ "path": "../inspector" }, { "path": "../../src" }],
"compilerOptions": {
"incremental": true,
"declarationDir": "../../lib/node",
"tsBuildInfoFile": "../../lib/node/.tsbuildinfo",
"erasableSyntaxOnly": true,
"rewriteRelativeImportExtensions": true,
"rootDir": "./",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"outDir": "../../lib/node/"
},
"include": [
"./example.mts",
"./bin.mts",
"./inspector.mts",
"./module.mts"
]
}