summaryrefslogtreecommitdiffstats
path: root/devtools/server/tests/xpcshell/test_objectgrips-17.js
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 14:29:10 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 14:29:10 +0000
commit2aa4a82499d4becd2284cdb482213d541b8804dd (patch)
treeb80bf8bf13c3766139fbacc530efd0dd9d54394c /devtools/server/tests/xpcshell/test_objectgrips-17.js
parentInitial commit. (diff)
downloadfirefox-2aa4a82499d4becd2284cdb482213d541b8804dd.tar.xz
firefox-2aa4a82499d4becd2284cdb482213d541b8804dd.zip
Adding upstream version 86.0.1.upstream/86.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'devtools/server/tests/xpcshell/test_objectgrips-17.js')
-rw-r--r--devtools/server/tests/xpcshell/test_objectgrips-17.js322
1 files changed, 322 insertions, 0 deletions
diff --git a/devtools/server/tests/xpcshell/test_objectgrips-17.js b/devtools/server/tests/xpcshell/test_objectgrips-17.js
new file mode 100644
index 0000000000..5c072eb26d
--- /dev/null
+++ b/devtools/server/tests/xpcshell/test_objectgrips-17.js
@@ -0,0 +1,322 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+/* eslint-disable no-shadow, max-nested-callbacks */
+
+"use strict";
+
+Services.prefs.setBoolPref("security.allow_eval_with_system_principal", true);
+
+registerCleanupFunction(() => {
+ Services.prefs.clearUserPref("security.allow_eval_with_system_principal");
+});
+
+async function testPrincipal(options, globalPrincipal, debuggeeHasXrays) {
+ const { debuggee } = options;
+ // Create a global object with the specified security principal.
+ // If none is specified, use the debuggee.
+ if (globalPrincipal === undefined) {
+ await test(options, {
+ global: debuggee,
+ subsumes: true,
+ isOpaque: false,
+ globalIsInvisible: false,
+ });
+ return;
+ }
+
+ const debuggeePrincipal = Cu.getObjectPrincipal(debuggee);
+ const sameOrigin = debuggeePrincipal.origin === globalPrincipal.origin;
+ const subsumes = debuggeePrincipal.subsumes(globalPrincipal);
+ for (const globalHasXrays of [true, false]) {
+ const isOpaque =
+ subsumes &&
+ globalPrincipal !== systemPrincipal &&
+ ((sameOrigin && debuggeeHasXrays) || globalHasXrays);
+ for (const globalIsInvisible of [true, false]) {
+ let global = Cu.Sandbox(globalPrincipal, {
+ wantXrays: globalHasXrays,
+ invisibleToDebugger: globalIsInvisible,
+ });
+ // Previously, the Sandbox constructor would (bizarrely) waive xrays on
+ // the return Sandbox if wantXrays was false. This has now been fixed,
+ // but we need to mimic that behavior here to make the test continue
+ // to pass.
+ if (!globalHasXrays) {
+ global = Cu.waiveXrays(global);
+ }
+ await test(options, { global, subsumes, isOpaque, globalIsInvisible });
+ }
+ }
+}
+
+async function test({ threadFront, debuggee }, testOptions) {
+ const { global } = testOptions;
+ const packet = await executeOnNextTickAndWaitForPause(eval_code, threadFront);
+ // Get the grips.
+ const [
+ proxyGrip,
+ inheritsProxyGrip,
+ inheritsProxy2Grip,
+ ] = packet.frame.arguments;
+
+ // Check the grip of the proxy object.
+ check_proxy_grip(debuggee, testOptions, proxyGrip);
+
+ // Check the target and handler slots of the proxy object.
+ const proxyClient = threadFront.pauseGrip(proxyGrip);
+ const proxySlots = await proxyClient.getProxySlots();
+ check_proxy_slots(debuggee, testOptions, proxyGrip, proxySlots);
+
+ // Check the prototype and properties of the proxy object.
+ const proxyResponse = await proxyClient.getPrototypeAndProperties();
+ check_properties(testOptions, proxyResponse.ownProperties, true, false);
+ check_prototype(debuggee, testOptions, proxyResponse.prototype, true, false);
+
+ // Check the prototype and properties of the object which inherits from the proxy.
+ const inheritsProxyClient = threadFront.pauseGrip(inheritsProxyGrip);
+ const inheritsProxyResponse = await inheritsProxyClient.getPrototypeAndProperties();
+ check_properties(
+ testOptions,
+ inheritsProxyResponse.ownProperties,
+ false,
+ false
+ );
+ check_prototype(
+ debuggee,
+ testOptions,
+ inheritsProxyResponse.prototype,
+ false,
+ false
+ );
+
+ // The prototype chain was not iterated if the object was inaccessible, so now check
+ // another object which inherits from the proxy, but was created in the debuggee.
+ const inheritsProxy2Client = threadFront.pauseGrip(inheritsProxy2Grip);
+ const inheritsProxy2Response = await inheritsProxy2Client.getPrototypeAndProperties();
+ check_properties(
+ testOptions,
+ inheritsProxy2Response.ownProperties,
+ false,
+ true
+ );
+ check_prototype(
+ debuggee,
+ testOptions,
+ inheritsProxy2Response.prototype,
+ false,
+ true
+ );
+
+ // Check that none of the above ran proxy traps.
+ strictEqual(global.trapDidRun, false, "No proxy trap did run.");
+
+ // Resume the debugger and finish the current test.
+ await threadFront.resume();
+
+ function eval_code() {
+ // Create objects in `global`, and debug them in `debuggee`. They may get various
+ // kinds of security wrappers, or no wrapper at all.
+ // To detect that no proxy trap runs, the proxy handler should define all possible
+ // traps, but the list is long and may change. Therefore a second proxy is used as
+ // the handler, so that a single `get` trap suffices.
+ global.eval(`
+ var trapDidRun = false;
+ var proxy = new Proxy({}, new Proxy({}, {get: (_, trap) => {
+ trapDidRun = true;
+ throw new Error("proxy trap '" + trap + "' was called.");
+ }}));
+ var inheritsProxy = Object.create(proxy, {x:{value:1}});
+ `);
+ const data = Cu.createObjectIn(debuggee, { defineAs: "data" });
+ data.proxy = global.proxy;
+ data.inheritsProxy = global.inheritsProxy;
+ debuggee.eval(`
+ var inheritsProxy2 = Object.create(data.proxy, {x:{value:1}});
+ stopMe(data.proxy, data.inheritsProxy, inheritsProxy2);
+ `);
+ }
+}
+
+function check_proxy_grip(debuggee, testOptions, grip) {
+ const { global, isOpaque, subsumes, globalIsInvisible } = testOptions;
+ const { preview } = grip;
+
+ if (global === debuggee) {
+ // The proxy has no security wrappers.
+ strictEqual(grip.class, "Proxy", "The grip has a Proxy class.");
+ strictEqual(
+ preview.ownPropertiesLength,
+ 2,
+ "The preview has 2 properties."
+ );
+ const props = preview.ownProperties;
+ ok(props["<target>"].value, "<target> contains the [[ProxyTarget]].");
+ ok(props["<handler>"].value, "<handler> contains the [[ProxyHandler]].");
+ } else if (isOpaque) {
+ // The proxy has opaque security wrappers.
+ strictEqual(grip.class, "Opaque", "The grip has an Opaque class.");
+ strictEqual(grip.ownPropertyLength, 0, "The grip has no properties.");
+ } else if (!subsumes) {
+ // The proxy belongs to compartment not subsumed by the debuggee.
+ strictEqual(grip.class, "Restricted", "The grip has a Restricted class.");
+ strictEqual(
+ grip.ownPropertyLength,
+ undefined,
+ "The grip doesn't know the number of properties."
+ );
+ } else if (globalIsInvisible) {
+ // The proxy belongs to an invisible-to-debugger compartment.
+ strictEqual(
+ grip.class,
+ "InvisibleToDebugger: Object",
+ "The grip has an InvisibleToDebugger class."
+ );
+ ok(
+ !("ownPropertyLength" in grip),
+ "The grip doesn't know the number of properties."
+ );
+ } else {
+ // The proxy has non-opaque security wrappers.
+ strictEqual(grip.class, "Proxy", "The grip has a Proxy class.");
+ strictEqual(
+ preview.ownPropertiesLength,
+ 0,
+ "The preview has no properties."
+ );
+ ok(!("<target>" in preview), "The preview has no <target> property.");
+ ok(!("<handler>" in preview), "The preview has no <handler> property.");
+ }
+}
+
+function check_proxy_slots(debuggee, testOptions, grip, proxySlots) {
+ const { global } = testOptions;
+
+ if (grip.class !== "Proxy") {
+ strictEqual(
+ proxySlots,
+ null,
+ "Slots can only be retrived for Proxy grips."
+ );
+ } else if (global === debuggee) {
+ const { proxyTarget, proxyHandler } = proxySlots;
+ strictEqual(
+ proxyTarget.getGrip().type,
+ "object",
+ "There is a [[ProxyTarget]] grip."
+ );
+ strictEqual(
+ proxyHandler.getGrip().type,
+ "object",
+ "There is a [[ProxyHandler]] grip."
+ );
+ } else {
+ const { proxyTarget, proxyHandler } = proxySlots;
+ strictEqual(
+ proxyTarget.type,
+ "undefined",
+ "There is no [[ProxyTarget]] grip."
+ );
+ strictEqual(
+ proxyHandler.type,
+ "undefined",
+ "There is no [[ProxyHandler]] grip."
+ );
+ }
+}
+
+function check_properties(testOptions, props, isProxy, createdInDebuggee) {
+ const { subsumes, globalIsInvisible } = testOptions;
+ const ownPropertiesLength = Reflect.ownKeys(props).length;
+
+ if (createdInDebuggee || (!isProxy && subsumes && !globalIsInvisible)) {
+ // The debuggee can access the properties.
+ strictEqual(ownPropertiesLength, 1, "1 own property was retrieved.");
+ strictEqual(props.x.value, 1, "The property has the right value.");
+ } else {
+ // The debuggee is not allowed to access the object.
+ strictEqual(ownPropertiesLength, 0, "No own property could be retrieved.");
+ }
+}
+
+function check_prototype(
+ debuggee,
+ testOptions,
+ proto,
+ isProxy,
+ createdInDebuggee
+) {
+ const { global, isOpaque, subsumes, globalIsInvisible } = testOptions;
+ if (isOpaque && !globalIsInvisible && !createdInDebuggee) {
+ // The object is or inherits from a proxy with opaque security wrappers.
+ // The debuggee sees `Object.prototype` when retrieving the prototype.
+ strictEqual(
+ proto.getGrip().class,
+ "Object",
+ "The prototype has a Object class."
+ );
+ } else if (isProxy && isOpaque && globalIsInvisible) {
+ // The object is a proxy with opaque security wrappers in an invisible global.
+ // The debuggee sees an inaccessible `Object.prototype` when retrieving the prototype.
+ strictEqual(
+ proto.getGrip().class,
+ "InvisibleToDebugger: Object",
+ "The prototype has an InvisibleToDebugger class."
+ );
+ } else if (
+ createdInDebuggee ||
+ (!isProxy && subsumes && !globalIsInvisible)
+ ) {
+ // The object inherits from a proxy and has no security wrappers or non-opaque ones.
+ // The debuggee sees the proxy when retrieving the prototype.
+ check_proxy_grip(
+ debuggee,
+ { global, isOpaque, subsumes, globalIsInvisible },
+ proto.getGrip()
+ );
+ } else {
+ // The debuggee is not allowed to access the object. It sees a null prototype.
+ strictEqual(proto.type, "null", "The prototype is null.");
+ }
+}
+
+function createNullPrincipal() {
+ return Cc["@mozilla.org/nullprincipal;1"].createInstance(Ci.nsIPrincipal);
+}
+
+async function run_tests_in_principal(
+ options,
+ debuggeePrincipal,
+ debuggeeHasXrays
+) {
+ const { debuggee } = options;
+ debuggee.eval(
+ function stopMe(arg1, arg2) {
+ debugger;
+ }.toString()
+ );
+
+ // Test objects created in the debuggee.
+ await testPrincipal(options, undefined, debuggeeHasXrays);
+
+ // Test objects created in a system principal new global.
+ await testPrincipal(options, systemPrincipal, debuggeeHasXrays);
+
+ // Test objects created in a cross-origin null principal new global.
+ await testPrincipal(options, createNullPrincipal(), debuggeeHasXrays);
+
+ if (debuggeePrincipal != systemPrincipal) {
+ // Test objects created in a same-origin principal new global.
+ await testPrincipal(options, debuggeePrincipal, debuggeeHasXrays);
+ }
+}
+
+for (const principal of [systemPrincipal, createNullPrincipal()]) {
+ for (const wantXrays of [true, false]) {
+ add_task(
+ threadFrontTest(
+ options => run_tests_in_principal(options, principal, wantXrays),
+ { principal, wantXrays }
+ )
+ );
+ }
+}