/* Any copyright is dedicated to the Public Domain. * http://creativecommons.org/publicdomain/zero/1.0/ */ "use strict"; // Check evaluating and expanding getters in the console. const TEST_URI = "data:text/html;charset=utf8,

Object Inspector on Getters

"; const { ELLIPSIS } = require("resource://devtools/shared/l10n.js"); add_task(async function () { const hud = await openNewTabAndConsole(TEST_URI); const LONGSTRING = "ab ".repeat(1e5); await SpecialPowers.spawn( gBrowser.selectedBrowser, [LONGSTRING], function (longString) { content.wrappedJSObject.console.log( "oi-test", Object.create( null, Object.getOwnPropertyDescriptors({ get myStringGetter() { return "hello"; }, get myNumberGetter() { return 123; }, get myUndefinedGetter() { return undefined; }, get myNullGetter() { return null; }, get myZeroGetter() { return 0; }, get myEmptyStringGetter() { return ""; }, get myFalseGetter() { return false; }, get myTrueGetter() { return true; }, get myObjectGetter() { return { foo: "bar" }; }, get myArrayGetter() { return Array.from({ length: 1000 }, (_, i) => i); }, get myMapGetter() { return new Map([["foo", { bar: "baz" }]]); }, get myProxyGetter() { const handler = { get(target, name) { return name in target ? target[name] : 37; }, }; return new Proxy({ a: 1 }, handler); }, get myThrowingGetter() { throw new Error("myError"); }, get myLongStringGetter() { return longString; }, get ["hyphen-getter"]() { return "---"; }, get [`"quoted-getter"`]() { return "quoted"; }, get [`"'\``]() { return "quoted2"; }, }) ) ); } ); const node = await waitFor(() => findConsoleAPIMessage(hud, "oi-test")); const oi = node.querySelector(".tree"); expandObjectInspectorNode(oi); await waitFor(() => getObjectInspectorNodes(oi).length > 1); await testStringGetter(oi); await testNumberGetter(oi); await testUndefinedGetter(oi); await testNullGetter(oi); await testZeroGetter(oi); await testEmptyStringGetter(oi); await testFalseGetter(oi); await testTrueGetter(oi); await testObjectGetter(oi); await testArrayGetter(oi); await testMapGetter(oi); await testProxyGetter(oi); await testThrowingGetter(oi); await testLongStringGetter(oi, LONGSTRING); await testHypgenGetter(oi); await testQuotedGetters(oi); }); async function testStringGetter(oi) { let node = findObjectInspectorNode(oi, "myStringGetter"); is( isObjectInspectorNodeExpandable(node), false, "The node can't be expanded" ); const invokeButton = getObjectInspectorInvokeGetterButton(node); ok(invokeButton, "There is an invoke button as expected"); invokeButton.click(); await waitFor( () => !getObjectInspectorInvokeGetterButton( findObjectInspectorNode(oi, "myStringGetter") ) ); node = findObjectInspectorNode(oi, "myStringGetter"); ok( node.textContent.includes(`myStringGetter: "hello"`), "String getter now has the expected text content" ); is( isObjectInspectorNodeExpandable(node), false, "The node can't be expanded" ); } async function testNumberGetter(oi) { let node = findObjectInspectorNode(oi, "myNumberGetter"); is( isObjectInspectorNodeExpandable(node), false, "The node can't be expanded" ); const invokeButton = getObjectInspectorInvokeGetterButton(node); ok(invokeButton, "There is an invoke button as expected"); invokeButton.click(); await waitFor( () => !getObjectInspectorInvokeGetterButton( findObjectInspectorNode(oi, "myNumberGetter") ) ); node = findObjectInspectorNode(oi, "myNumberGetter"); ok( node.textContent.includes(`myNumberGetter: 123`), "Number getter now has the expected text content" ); is( isObjectInspectorNodeExpandable(node), false, "The node can't be expanded" ); } async function testUndefinedGetter(oi) { let node = findObjectInspectorNode(oi, "myUndefinedGetter"); is( isObjectInspectorNodeExpandable(node), false, "The node can't be expanded" ); const invokeButton = getObjectInspectorInvokeGetterButton(node); ok(invokeButton, "There is an invoke button as expected"); invokeButton.click(); await waitFor( () => !getObjectInspectorInvokeGetterButton( findObjectInspectorNode(oi, "myUndefinedGetter") ) ); node = findObjectInspectorNode(oi, "myUndefinedGetter"); ok( node.textContent.includes(`myUndefinedGetter: undefined`), "undefined getter now has the expected text content" ); is( isObjectInspectorNodeExpandable(node), false, "The node can't be expanded" ); } async function testNullGetter(oi) { let node = findObjectInspectorNode(oi, "myNullGetter"); is( isObjectInspectorNodeExpandable(node), false, "The node can't be expanded" ); const invokeButton = getObjectInspectorInvokeGetterButton(node); ok(invokeButton, "There is an invoke button as expected"); invokeButton.click(); await waitFor( () => !getObjectInspectorInvokeGetterButton( findObjectInspectorNode(oi, "myNullGetter") ) ); node = findObjectInspectorNode(oi, "myNullGetter"); ok( node.textContent.includes(`myNullGetter: null`), "null getter now has the expected text content" ); is( isObjectInspectorNodeExpandable(node), false, "The node can't be expanded" ); } async function testZeroGetter(oi) { let node = findObjectInspectorNode(oi, "myZeroGetter"); is( isObjectInspectorNodeExpandable(node), false, "The node can't be expanded" ); const invokeButton = getObjectInspectorInvokeGetterButton(node); ok(invokeButton, "There is an invoke button as expected"); invokeButton.click(); await waitFor( () => !getObjectInspectorInvokeGetterButton( findObjectInspectorNode(oi, "myZeroGetter") ) ); node = findObjectInspectorNode(oi, "myZeroGetter"); ok( node.textContent.includes(`myZeroGetter: 0`), "0 getter now has the expected text content" ); is( isObjectInspectorNodeExpandable(node), false, "The node can't be expanded" ); } async function testEmptyStringGetter(oi) { let node = findObjectInspectorNode(oi, "myEmptyStringGetter"); is( isObjectInspectorNodeExpandable(node), false, "The node can't be expanded" ); const invokeButton = getObjectInspectorInvokeGetterButton(node); ok(invokeButton, "There is an invoke button as expected"); invokeButton.click(); await waitFor( () => !getObjectInspectorInvokeGetterButton( findObjectInspectorNode(oi, "myEmptyStringGetter") ) ); node = findObjectInspectorNode(oi, "myEmptyStringGetter"); ok( node.textContent.includes(`myEmptyStringGetter: ""`), "empty string getter now has the expected text content" ); is( isObjectInspectorNodeExpandable(node), false, "The node can't be expanded" ); } async function testFalseGetter(oi) { let node = findObjectInspectorNode(oi, "myFalseGetter"); is( isObjectInspectorNodeExpandable(node), false, "The node can't be expanded" ); const invokeButton = getObjectInspectorInvokeGetterButton(node); ok(invokeButton, "There is an invoke button as expected"); invokeButton.click(); await waitFor( () => !getObjectInspectorInvokeGetterButton( findObjectInspectorNode(oi, "myFalseGetter") ) ); node = findObjectInspectorNode(oi, "myFalseGetter"); ok( node.textContent.includes(`myFalseGetter: false`), "false getter now has the expected text content" ); is( isObjectInspectorNodeExpandable(node), false, "The node can't be expanded" ); } async function testTrueGetter(oi) { let node = findObjectInspectorNode(oi, "myTrueGetter"); is( isObjectInspectorNodeExpandable(node), false, "The node can't be expanded" ); const invokeButton = getObjectInspectorInvokeGetterButton(node); ok(invokeButton, "There is an invoke button as expected"); invokeButton.click(); await waitFor( () => !getObjectInspectorInvokeGetterButton( findObjectInspectorNode(oi, "myTrueGetter") ) ); node = findObjectInspectorNode(oi, "myTrueGetter"); ok( node.textContent.includes(`myTrueGetter: true`), "false getter now has the expected text content" ); is( isObjectInspectorNodeExpandable(node), false, "The node can't be expanded" ); } async function testObjectGetter(oi) { let node = findObjectInspectorNode(oi, "myObjectGetter"); is( isObjectInspectorNodeExpandable(node), false, "The node can't be expanded" ); const invokeButton = getObjectInspectorInvokeGetterButton(node); ok(invokeButton, "There is an invoke button as expected"); invokeButton.click(); await waitFor( () => !getObjectInspectorInvokeGetterButton( findObjectInspectorNode(oi, "myObjectGetter") ) ); node = findObjectInspectorNode(oi, "myObjectGetter"); ok( node.textContent.includes(`myObjectGetter: Object { foo: "bar" }`), "object getter now has the expected text content" ); is(isObjectInspectorNodeExpandable(node), true, "The node can be expanded"); expandObjectInspectorNode(node); await waitFor(() => !!getObjectInspectorChildrenNodes(node).length); checkChildren(node, [`foo: "bar"`, ``]); } async function testArrayGetter(oi) { let node = findObjectInspectorNode(oi, "myArrayGetter"); is( isObjectInspectorNodeExpandable(node), false, "The node can't be expanded" ); const invokeButton = getObjectInspectorInvokeGetterButton(node); ok(invokeButton, "There is an invoke button as expected"); invokeButton.click(); await waitFor( () => !getObjectInspectorInvokeGetterButton( findObjectInspectorNode(oi, "myArrayGetter") ) ); node = findObjectInspectorNode(oi, "myArrayGetter"); ok( node.textContent.includes( `myArrayGetter: Array(1000) [ 0, 1, 2, ${ELLIPSIS} ]` ), "Array getter now has the expected text content - " ); is(isObjectInspectorNodeExpandable(node), true, "The node can be expanded"); expandObjectInspectorNode(node); await waitFor(() => !!getObjectInspectorChildrenNodes(node).length); const children = getObjectInspectorChildrenNodes(node); const firstBucket = children[0]; ok(firstBucket.textContent.includes(`[0${ELLIPSIS}99]`), "Array has buckets"); is( isObjectInspectorNodeExpandable(firstBucket), true, "The bucket can be expanded" ); expandObjectInspectorNode(firstBucket); await waitFor(() => !!getObjectInspectorChildrenNodes(firstBucket).length); checkChildren( firstBucket, Array.from({ length: 100 }, (_, i) => `${i}: ${i}`) ); } async function testMapGetter(oi) { let node = findObjectInspectorNode(oi, "myMapGetter"); is( isObjectInspectorNodeExpandable(node), false, "The node can't be expanded" ); const invokeButton = getObjectInspectorInvokeGetterButton(node); ok(invokeButton, "There is an invoke button as expected"); invokeButton.click(); await waitFor( () => !getObjectInspectorInvokeGetterButton( findObjectInspectorNode(oi, "myMapGetter") ) ); node = findObjectInspectorNode(oi, "myMapGetter"); ok( node.textContent.includes(`myMapGetter: Map`), "map getter now has the expected text content" ); is(isObjectInspectorNodeExpandable(node), true, "The node can be expanded"); expandObjectInspectorNode(node); await waitFor(() => !!getObjectInspectorChildrenNodes(node).length); checkChildren(node, [`size`, ``, ``]); const entriesNode = findObjectInspectorNode(oi, ""); expandObjectInspectorNode(entriesNode); await waitFor(() => !!getObjectInspectorChildrenNodes(entriesNode).length); checkChildren(entriesNode, [`foo → Object { bar: "baz" }`]); const entryNode = getObjectInspectorChildrenNodes(entriesNode)[0]; expandObjectInspectorNode(entryNode); await waitFor(() => !!getObjectInspectorChildrenNodes(entryNode).length); checkChildren(entryNode, [`: "foo"`, `: Object { bar: "baz" }`]); } async function testProxyGetter(oi) { let node = findObjectInspectorNode(oi, "myProxyGetter"); is( isObjectInspectorNodeExpandable(node), false, "The node can't be expanded" ); const invokeButton = getObjectInspectorInvokeGetterButton(node); ok(invokeButton, "There is an invoke button as expected"); invokeButton.click(); await waitFor( () => !getObjectInspectorInvokeGetterButton( findObjectInspectorNode(oi, "myProxyGetter") ) ); node = findObjectInspectorNode(oi, "myProxyGetter"); ok( node.textContent.includes(`myProxyGetter: Proxy`), "proxy getter now has the expected text content" ); is(isObjectInspectorNodeExpandable(node), true, "The node can be expanded"); expandObjectInspectorNode(node); await waitFor(() => !!getObjectInspectorChildrenNodes(node).length); checkChildren(node, [``, ``]); const targetNode = findObjectInspectorNode(oi, ""); expandObjectInspectorNode(targetNode); await waitFor(() => !!getObjectInspectorChildrenNodes(targetNode).length); checkChildren(targetNode, [`a: 1`, ``]); const handlerNode = findObjectInspectorNode(oi, ""); expandObjectInspectorNode(handlerNode); await waitFor(() => !!getObjectInspectorChildrenNodes(handlerNode).length); checkChildren(handlerNode, [`get:`, ``]); } async function testThrowingGetter(oi) { let node = findObjectInspectorNode(oi, "myThrowingGetter"); is( isObjectInspectorNodeExpandable(node), false, "The node can't be expanded" ); const invokeButton = getObjectInspectorInvokeGetterButton(node); ok(invokeButton, "There is an invoke button as expected"); invokeButton.click(); await waitFor( () => !getObjectInspectorInvokeGetterButton( findObjectInspectorNode(oi, "myThrowingGetter") ) ); node = findObjectInspectorNode(oi, "myThrowingGetter"); ok( node.textContent.includes(`myThrowingGetter: Error`), "throwing getter does show the error" ); is(isObjectInspectorNodeExpandable(node), true, "The node can be expanded"); expandObjectInspectorNode(node); await waitFor(() => !!getObjectInspectorChildrenNodes(node).length); checkChildren(node, [ `columnNumber`, `fileName`, `lineNumber`, `message`, `stack`, ``, ]); } async function testLongStringGetter(oi, longString) { const getLongStringNode = () => findObjectInspectorNode(oi, "myLongStringGetter"); const node = getLongStringNode(); is( isObjectInspectorNodeExpandable(node), false, "The node can't be expanded" ); const invokeButton = getObjectInspectorInvokeGetterButton(node); ok(invokeButton, "There is an invoke button as expected"); invokeButton.click(); await waitFor(() => getLongStringNode().textContent.includes(`myLongStringGetter: "ab ab`) ); ok(true, "longstring getter shows the initial text"); is( isObjectInspectorNodeExpandable(getLongStringNode()), true, "The node can be expanded" ); expandObjectInspectorNode(getLongStringNode()); await waitFor(() => getLongStringNode().textContent.includes( `myLongStringGetter: "${longString}"` ) ); ok(true, "the longstring was expanded"); } async function testHypgenGetter(oi) { const findHyphenGetterNode = () => findObjectInspectorNode(oi, `"hyphen-getter"`); let node = findHyphenGetterNode(); is( isObjectInspectorNodeExpandable(node), false, "The node can't be expanded" ); const invokeButton = getObjectInspectorInvokeGetterButton(node); ok(invokeButton, "There is an invoke button as expected"); invokeButton.click(); await waitFor( () => !getObjectInspectorInvokeGetterButton(findHyphenGetterNode()) ); node = findHyphenGetterNode(); ok( node.textContent.includes(`"hyphen-getter": "---"`), "Node now has the expected text content" ); is( isObjectInspectorNodeExpandable(node), false, "The node can't be expanded" ); } async function testQuotedGetters(oi) { const nodes = [ { name: `'"quoted-getter"'`, expected: `"quoted"`, expandable: false, }, { name: `"\\"'\`"`, expected: `"quoted2"`, expandable: false, }, ]; for (const { name, expected, expandable } of nodes) { await testGetter(oi, name, expected, expandable); } } async function testGetter(oi, propertyName, expectedResult, resultExpandable) { info(`Check «${propertyName}» getter`); const findNode = () => findObjectInspectorNode(oi, propertyName); let node = findNode(); is( isObjectInspectorNodeExpandable(node), false, `«${propertyName}» can't be expanded` ); getObjectInspectorInvokeGetterButton(node).click(); await waitFor(() => !getObjectInspectorInvokeGetterButton(findNode())); node = findNode(); ok( node.textContent.includes(`${propertyName}: ${expectedResult}`), `«${propertyName}» now has the expected text content («${expectedResult}»)` ); is( isObjectInspectorNodeExpandable(node), resultExpandable, `«${propertyName}» ${resultExpandable ? "now can" : "can't"} be expanded` ); } function checkChildren(node, expectedChildren) { const children = getObjectInspectorChildrenNodes(node); is( children.length, expectedChildren.length, "There is the expected number of children" ); children.forEach((child, index) => { ok( child.textContent.includes(expectedChildren[index]), `Expected "${child.textContent}" to include "${expectedChildren[index]}"` ); }); }