/* Any copyright is dedicated to the Public Domain. http://creativecommons.org/publicdomain/zero/1.0/ */ "use strict"; // Test that onEnumProperties returns the expected data // when passing `ignoreNonIndexedProperties` and `ignoreIndexedProperties` options // with various objects. (See Bug 1403065) const DO_NOT_CHECK_VALUE = Symbol(); Services.prefs.setBoolPref("security.allow_eval_with_system_principal", true); registerCleanupFunction(() => { Services.prefs.clearUserPref("security.allow_eval_with_system_principal"); }); add_task( threadFrontTest(async ({ threadFront, debuggee, client }) => { debuggee.eval( function stopMe(arg1) { debugger; }.toString() ); const testCases = [ { evaledObject: { a: 10 }, expectedIndexedProperties: [], expectedNonIndexedProperties: [["a", 10]], }, { evaledObject: { length: 10 }, expectedIndexedProperties: [], expectedNonIndexedProperties: [["length", 10]], }, { evaledObject: { a: 10, 0: "indexed" }, expectedIndexedProperties: [["0", "indexed"]], expectedNonIndexedProperties: [["a", 10]], }, { evaledObject: { 1: 1, length: 42, a: 10 }, expectedIndexedProperties: [["1", 1]], expectedNonIndexedProperties: [ ["length", 42], ["a", 10], ], }, { evaledObject: { 1: 1, length: 2.34, a: 10 }, expectedIndexedProperties: [["1", 1]], expectedNonIndexedProperties: [ ["length", 2.34], ["a", 10], ], }, { evaledObject: { 1: 1, length: -0, a: 10 }, expectedIndexedProperties: [["1", 1]], expectedNonIndexedProperties: [ ["length", -0], ["a", 10], ], }, { evaledObject: { 1: 1, length: -10, a: 10 }, expectedIndexedProperties: [["1", 1]], expectedNonIndexedProperties: [ ["length", -10], ["a", 10], ], }, { evaledObject: { 1: 1, length: true, a: 10 }, expectedIndexedProperties: [["1", 1]], expectedNonIndexedProperties: [ ["length", true], ["a", 10], ], }, { evaledObject: { 1: 1, length: null, a: 10 }, expectedIndexedProperties: [["1", 1]], expectedNonIndexedProperties: [ ["length", DO_NOT_CHECK_VALUE], ["a", 10], ], }, { evaledObject: { 1: 1, length: Math.pow(2, 53), a: 10 }, expectedIndexedProperties: [["1", 1]], expectedNonIndexedProperties: [ ["length", 9007199254740992], ["a", 10], ], }, { evaledObject: { 1: 1, length: "fake", a: 10 }, expectedIndexedProperties: [["1", 1]], expectedNonIndexedProperties: [ ["length", "fake"], ["a", 10], ], }, { evaledObject: { 1: 1, length: Infinity, a: 10 }, expectedIndexedProperties: [["1", 1]], expectedNonIndexedProperties: [ ["length", DO_NOT_CHECK_VALUE], ["a", 10], ], }, { evaledObject: { 0: 0, length: 0 }, expectedIndexedProperties: [["0", 0]], expectedNonIndexedProperties: [["length", 0]], }, { evaledObject: { 0: 0, 1: 1, length: 1 }, expectedIndexedProperties: [ ["0", 0], ["1", 1], ], expectedNonIndexedProperties: [["length", 1]], }, { evaledObject: { length: 0 }, expectedIndexedProperties: [], expectedNonIndexedProperties: [["length", 0]], }, { evaledObject: { 1: 1 }, expectedIndexedProperties: [["1", 1]], expectedNonIndexedProperties: [], }, { evaledObject: { a: 1, [2 ** 32 - 2]: 2, [2 ** 32 - 1]: 3 }, expectedIndexedProperties: [["4294967294", 2]], expectedNonIndexedProperties: [ ["a", 1], ["4294967295", 3], ], }, { evaledObject: `(() => { x = [12, 42]; x.foo = 90; return x; })()`, expectedIndexedProperties: [ ["0", 12], ["1", 42], ], expectedNonIndexedProperties: [ ["length", 2], ["foo", 90], ], }, { evaledObject: `(() => { x = [12, 42]; x.length = 3; return x; })()`, expectedIndexedProperties: [ ["0", 12], ["1", 42], ["2", undefined], ], expectedNonIndexedProperties: [["length", 3]], }, { evaledObject: `(() => { x = [12, 42]; x.length = 1; return x; })()`, expectedIndexedProperties: [["0", 12]], expectedNonIndexedProperties: [["length", 1]], }, { evaledObject: `(() => { x = [, 42,,]; x.foo = 90; return x; })()`, expectedIndexedProperties: [ ["0", undefined], ["1", 42], ["2", undefined], ], expectedNonIndexedProperties: [ ["length", 3], ["foo", 90], ], }, { evaledObject: `(() => { x = Array(2); x.foo = "bar"; x.bar = "foo"; return x; })()`, expectedIndexedProperties: [ ["0", undefined], ["1", undefined], ], expectedNonIndexedProperties: [ ["length", 2], ["foo", "bar"], ["bar", "foo"], ], }, { evaledObject: `(() => { x = new Int8Array(new ArrayBuffer(2)); x.foo = "bar"; x.bar = "foo"; return x; })()`, expectedIndexedProperties: [ ["0", 0], ["1", 0], ], expectedNonIndexedProperties: [ ["foo", "bar"], ["bar", "foo"], ["length", 2], ["buffer", DO_NOT_CHECK_VALUE], ["byteLength", 2], ["byteOffset", 0], ], }, { evaledObject: `(() => { x = new Int8Array([1, 2]); Object.defineProperty(x, 'length', {value: 0}); return x; })()`, expectedIndexedProperties: [ ["0", 1], ["1", 2], ], expectedNonIndexedProperties: [ ["length", 0], ["buffer", DO_NOT_CHECK_VALUE], ["byteLength", 2], ["byteOffset", 0], ], }, { evaledObject: `(() => { x = new Int32Array([1, 2]); Object.setPrototypeOf(x, null); return x; })()`, expectedIndexedProperties: [ ["0", 1], ["1", 2], ], expectedNonIndexedProperties: [], }, { evaledObject: `(() => { return new (class extends Int8Array {})([1, 2]); })()`, expectedIndexedProperties: [ ["0", 1], ["1", 2], ], expectedNonIndexedProperties: [ ["length", 2], ["buffer", DO_NOT_CHECK_VALUE], ["byteLength", 2], ["byteOffset", 0], ], }, ]; for (const test of testCases) { await test_object_grip(debuggee, client, threadFront, test); } }) ); async function test_object_grip( debuggee, dbgClient, threadFront, testData = {} ) { const { evaledObject, expectedIndexedProperties, expectedNonIndexedProperties, } = testData; const packet = await executeOnNextTickAndWaitForPause(eval_code, threadFront); const [grip] = packet.frame.arguments; const objClient = threadFront.pauseGrip(grip); info(` Check enumProperties response for ${ typeof evaledObject === "string" ? evaledObject : JSON.stringify(evaledObject) } `); // Checks the result of enumProperties. let response = await objClient.enumProperties({ ignoreNonIndexedProperties: true, }); await check_enum_properties(response, expectedIndexedProperties); response = await objClient.enumProperties({ ignoreIndexedProperties: true, }); await check_enum_properties(response, expectedNonIndexedProperties); await threadFront.resume(); function eval_code() { // Be sure to run debuggee code in its own HTML 'task', so that when we call // the onDebuggerStatement hook, the test's own microtasks don't get suspended // along with the debuggee's. do_timeout(0, () => { debuggee.eval(` stopMe(${ typeof evaledObject === "string" ? evaledObject : JSON.stringify(evaledObject) }); `); }); } } async function check_enum_properties(iterator, expected = []) { equal( iterator.count, expected.length, "iterator.count has the expected value" ); info("Check iterator.slice response for all properties"); const sliceResponse = await iterator.slice(0, iterator.count); ok( sliceResponse && Object.getOwnPropertyNames(sliceResponse).includes("ownProperties"), "The response object has an ownProperties property" ); const { ownProperties } = sliceResponse; const names = Object.getOwnPropertyNames(ownProperties); equal( names.length, expected.length, "The response has the expected number of properties" ); for (let i = 0; i < names.length; i++) { const name = names[i]; const [key, value] = expected[i]; equal(name, key, "Property has the expected name"); const property = ownProperties[name]; if (value === DO_NOT_CHECK_VALUE) { return; } if (value === undefined) { equal( property, undefined, `Response has no value for the "${key}" property` ); } else { const propValue = property.hasOwnProperty("value") ? property.value : property.getterValue; equal(propValue, value, `Property "${key}" has the expected value`); } } }