mirror of
https://github.com/servo/servo
synced 2026-05-05 22:52:07 +02:00
Replace the `EvaluateJS` function to use `debugger.js` `Eval` event and the proper context. This takes the global context and calls [executeInGlobal](https://firefox-source-docs.mozilla.org/js/Debugger/Debugger.Object.html#executeinglobal-code-options). In future we also add the version that takes a frame and calls [eval](https://firefox-source-docs.mozilla.org/js/Debugger/Debugger.Frame.html#eval-code-options). Testing: No new tests added yet, Old tests are not impacted by this change. Fixes: Part of https://github.com/servo/servo/issues/36027 --------- Signed-off-by: atbrakhi <atbrakhi@igalia.com> Co-authored-by: eri <eri@igalia.com>
168 lines
6.8 KiB
JavaScript
168 lines
6.8 KiB
JavaScript
if ("dbg" in this) {
|
||
throw new Error("Debugger script must not run more than once!");
|
||
}
|
||
|
||
const dbg = new Debugger;
|
||
const debuggeesToPipelineIds = new Map;
|
||
const debuggeesToWorkerIds = new Map;
|
||
const sourceIdsToScripts = new Map;
|
||
|
||
// Find script by scriptId within a script tree
|
||
function findScriptById(script, scriptId) {
|
||
if (script.sourceStart === scriptId) {
|
||
return script;
|
||
}
|
||
for (const child of script.getChildScripts()) {
|
||
const found = findScriptById(child, scriptId);
|
||
if (found) return found;
|
||
}
|
||
return null;
|
||
}
|
||
|
||
// Walk script tree and call callback for each script
|
||
function walkScriptTree(script, callback) {
|
||
callback(script);
|
||
for (const child of script.getChildScripts()) {
|
||
walkScriptTree(child, callback);
|
||
}
|
||
}
|
||
|
||
dbg.uncaughtExceptionHook = function(error) {
|
||
console.error(`[debugger] Uncaught exception at ${error.fileName}:${error.lineNumber}:${error.columnNumber}: ${error.name}: ${error.message}`);
|
||
};
|
||
|
||
dbg.onNewScript = function(script) {
|
||
// TODO: handle wasm (`script.source.introductionType == wasm`)
|
||
sourceIdsToScripts.set(script.source.id, script);
|
||
notifyNewSource({
|
||
pipelineId: debuggeesToPipelineIds.get(script.global),
|
||
workerId: debuggeesToWorkerIds.get(script.global),
|
||
spidermonkeyId: script.source.id,
|
||
url: script.source.url,
|
||
urlOverride: script.source.displayURL,
|
||
text: script.source.text,
|
||
introductionType: script.source.introductionType ?? null,
|
||
});
|
||
};
|
||
|
||
addEventListener("addDebuggee", event => {
|
||
const {global, pipelineId: {namespaceId, index}, workerId} = event;
|
||
const debuggerObject = dbg.addDebuggee(global);
|
||
debuggeesToPipelineIds.set(debuggerObject, { namespaceId, index });
|
||
debuggeesToWorkerIds.set(debuggerObject, workerId);
|
||
});
|
||
|
||
// Create a result value object from a debuggee value.
|
||
// Debuggee values: <https://firefox-source-docs.mozilla.org/js/Debugger/Conventions.html#debuggee-values>
|
||
// Type detection follows Firefox's createValueGrip pattern:
|
||
// <https://searchfox.org/mozilla-central/source/devtools/server/actors/object/utils.js#116>
|
||
function createValueResult(value) {
|
||
switch (typeof value) {
|
||
case "undefined":
|
||
return { valueType: "undefined" };
|
||
case "boolean":
|
||
return { valueType: "boolean", booleanValue: value };
|
||
case "number":
|
||
return { valueType: "number", numberValue: value };
|
||
case "string":
|
||
return { valueType: "string", stringValue: value };
|
||
case "object":
|
||
if (value === null) {
|
||
return { valueType: "null" };
|
||
}
|
||
// Debugger.Object - use the `class` accessor property
|
||
// <https://firefox-source-docs.mozilla.org/js/Debugger/Debugger.Object.html>
|
||
return { valueType: "object", objectClass: value.class };
|
||
default:
|
||
return { valueType: "string", stringValue: String(value) };
|
||
}
|
||
}
|
||
|
||
// <https://firefox-source-docs.mozilla.org/js/Debugger/Debugger.Object.html#executeinglobal-code-options>
|
||
addEventListener("eval", event => {
|
||
const {code, pipelineId: {namespaceId, index}, workerId} = event;
|
||
let object = debuggeesToPipelineIds.keys().next().value;
|
||
let completionValue = object.executeInGlobal(code);
|
||
|
||
// Completion values: <https://firefox-source-docs.mozilla.org/js/Debugger/Conventions.html#completion-values>
|
||
let resultValue;
|
||
|
||
if (completionValue === null) {
|
||
resultValue = { completionType: "terminated", valueType: "undefined" };
|
||
} else if ("throw" in completionValue) {
|
||
// Adopt the value to ensure proper Debugger ownership
|
||
// <https://firefox-source-docs.mozilla.org/js/Debugger/Debugger.html#adoptdebuggeevalue-value>
|
||
// <https://searchfox.org/firefox-main/source/devtools/server/actors/webconsole/eval-with-debugger.js#312>
|
||
// we probably don't need adoptDebuggeeValue, as we only have one debugger instance for now
|
||
// let value = dbg.adoptDebuggeeValue(completionValue.throw);
|
||
resultValue = { completionType: "throw", ...createValueResult(completionValue.throw) };
|
||
} else if ("return" in completionValue) {
|
||
// let value = dbg.adoptDebuggeeValue(completionValue.return);
|
||
resultValue = { completionType: "return", ...createValueResult(completionValue.return) };
|
||
}
|
||
|
||
evalResult(event, resultValue);
|
||
});
|
||
|
||
addEventListener("getPossibleBreakpoints", event => {
|
||
const {spidermonkeyId} = event;
|
||
const script = sourceIdsToScripts.get(spidermonkeyId);
|
||
const result = [];
|
||
walkScriptTree(script, (currentScript) => {
|
||
for (const location of currentScript.getPossibleBreakpoints()) {
|
||
location["scriptId"] = currentScript.sourceStart;
|
||
result.push(location);
|
||
}
|
||
});
|
||
getPossibleBreakpointsResult(event, result);
|
||
});
|
||
|
||
addEventListener("setBreakpoint", event => {
|
||
const {spidermonkeyId, scriptId, offset} = event;
|
||
const script = sourceIdsToScripts.get(spidermonkeyId);
|
||
const target = findScriptById(script, scriptId);
|
||
if (target) {
|
||
target.setBreakpoint(offset, {
|
||
hit: () => {
|
||
// <https://firefox-source-docs.mozilla.org/js/Debugger/Conventions.html#resumption-values>
|
||
// TODO: notify script to pause
|
||
return { throw: "1" };
|
||
}
|
||
});
|
||
}
|
||
});
|
||
|
||
// <https://firefox-source-docs.mozilla.org/js/Debugger/Debugger.Frame.html>
|
||
addEventListener("pause", event => {
|
||
dbg.onEnterFrame = function(frame) {
|
||
dbg.onEnterFrame = undefined;
|
||
// TODO: Some properties throw if terminated is true
|
||
// TODO: Check if start line / column is correct or we need the proper breakpoint
|
||
const result = {
|
||
// TODO: arguments: frame.arguments,
|
||
column: frame.script.startColumn,
|
||
displayName: frame.script.displayName,
|
||
line: frame.script.startLine,
|
||
onStack: frame.onStack,
|
||
oldest: frame.older == null,
|
||
terminated: frame.terminated,
|
||
type_: frame.type,
|
||
url: frame.script.url,
|
||
};
|
||
getFrameResult(event, result);
|
||
};
|
||
});
|
||
|
||
// <https://firefox-source-docs.mozilla.org/js/Debugger/Debugger.Script.html#clearbreakpoint-handler-offset>
|
||
// There may be more than one breakpoint at the same offset with different handlers, but we don’t handle that case for now.
|
||
addEventListener("clearBreakpoint", event => {
|
||
const {spidermonkeyId, scriptId, offset} = event;
|
||
const script = sourceIdsToScripts.get(spidermonkeyId);
|
||
const target = findScriptById(script, scriptId);
|
||
if (target) {
|
||
// <https://firefox-source-docs.mozilla.org/js/Debugger/Debugger.Script.html#clearallbreakpoints-offset>
|
||
// If the instance refers to a JSScript, remove all breakpoints set in this script at that offset.
|
||
target.clearAllBreakpoints(offset);
|
||
}
|
||
});
|