diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 14:29:10 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 14:29:10 +0000 |
commit | 2aa4a82499d4becd2284cdb482213d541b8804dd (patch) | |
tree | b80bf8bf13c3766139fbacc530efd0dd9d54394c /devtools/server/tests/xpcshell/test_objectgrips-17.js | |
parent | Initial commit. (diff) | |
download | firefox-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.js | 322 |
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 } + ) + ); + } +} |