browser_webconsole_object_inspector_getters.js (17948B)
1 /* Any copyright is dedicated to the Public Domain. 2 * http://creativecommons.org/publicdomain/zero/1.0/ */ 3 4 "use strict"; 5 6 // Check evaluating and expanding getters in the console. 7 const TEST_URI = 8 "data:text/html;charset=utf8,<!DOCTYPE html><h1>Object Inspector on Getters</h1>"; 9 const { ELLIPSIS } = require("resource://devtools/shared/l10n.js"); 10 11 add_task(async function () { 12 const hud = await openNewTabAndConsole(TEST_URI); 13 14 const LONGSTRING = "ab ".repeat(1e5); 15 16 await SpecialPowers.spawn( 17 gBrowser.selectedBrowser, 18 [LONGSTRING], 19 function (longString) { 20 content.wrappedJSObject.console.log( 21 "oi-test", 22 Object.create( 23 null, 24 Object.getOwnPropertyDescriptors({ 25 get myStringGetter() { 26 return "hello"; 27 }, 28 get myNumberGetter() { 29 return 123; 30 }, 31 get myUndefinedGetter() { 32 return undefined; 33 }, 34 get myNullGetter() { 35 return null; 36 }, 37 get myZeroGetter() { 38 return 0; 39 }, 40 get myEmptyStringGetter() { 41 return ""; 42 }, 43 get myFalseGetter() { 44 return false; 45 }, 46 get myTrueGetter() { 47 return true; 48 }, 49 get myObjectGetter() { 50 return { foo: "bar" }; 51 }, 52 get myArrayGetter() { 53 return Array.from({ length: 1000 }, (_, i) => i); 54 }, 55 get myMapGetter() { 56 return new Map([["foo", { bar: "baz" }]]); 57 }, 58 get myProxyGetter() { 59 const handler = { 60 get(target, name) { 61 return name in target ? target[name] : 37; 62 }, 63 }; 64 return new Proxy({ a: 1 }, handler); 65 }, 66 get myThrowingGetter() { 67 throw new Error("myError"); 68 }, 69 get myLongStringGetter() { 70 return longString; 71 }, 72 get ["hyphen-getter"]() { 73 return "---"; 74 }, 75 get [`"quoted-getter"`]() { 76 return "quoted"; 77 }, 78 get [`"'\``]() { 79 return "quoted2"; 80 }, 81 }) 82 ) 83 ); 84 } 85 ); 86 87 const node = await waitFor(() => findConsoleAPIMessage(hud, "oi-test")); 88 const oi = node.querySelector(".tree"); 89 90 await expandObjectInspectorNode(oi.querySelector(".tree-node")); 91 92 await testStringGetter(oi); 93 await testNumberGetter(oi); 94 await testUndefinedGetter(oi); 95 await testNullGetter(oi); 96 await testZeroGetter(oi); 97 await testEmptyStringGetter(oi); 98 await testFalseGetter(oi); 99 await testTrueGetter(oi); 100 await testObjectGetter(oi); 101 await testArrayGetter(oi); 102 await testMapGetter(oi); 103 await testProxyGetter(oi); 104 await testThrowingGetter(oi); 105 await testLongStringGetter(oi, LONGSTRING); 106 await testHypgenGetter(oi); 107 await testQuotedGetters(oi); 108 }); 109 110 async function testStringGetter(oi) { 111 let node = findObjectInspectorNode(oi, "myStringGetter"); 112 is( 113 isObjectInspectorNodeExpandable(node), 114 false, 115 "The node can't be expanded" 116 ); 117 const invokeButton = getObjectInspectorInvokeGetterButton(node); 118 ok(invokeButton, "There is an invoke button as expected"); 119 120 invokeButton.click(); 121 await waitFor( 122 () => 123 !getObjectInspectorInvokeGetterButton( 124 findObjectInspectorNode(oi, "myStringGetter") 125 ) 126 ); 127 128 node = findObjectInspectorNode(oi, "myStringGetter"); 129 ok( 130 node.textContent.includes(`myStringGetter: "hello"`), 131 "String getter now has the expected text content" 132 ); 133 is( 134 isObjectInspectorNodeExpandable(node), 135 false, 136 "The node can't be expanded" 137 ); 138 } 139 140 async function testNumberGetter(oi) { 141 let node = findObjectInspectorNode(oi, "myNumberGetter"); 142 is( 143 isObjectInspectorNodeExpandable(node), 144 false, 145 "The node can't be expanded" 146 ); 147 const invokeButton = getObjectInspectorInvokeGetterButton(node); 148 ok(invokeButton, "There is an invoke button as expected"); 149 150 invokeButton.click(); 151 await waitFor( 152 () => 153 !getObjectInspectorInvokeGetterButton( 154 findObjectInspectorNode(oi, "myNumberGetter") 155 ) 156 ); 157 158 node = findObjectInspectorNode(oi, "myNumberGetter"); 159 ok( 160 node.textContent.includes(`myNumberGetter: 123`), 161 "Number getter now has the expected text content" 162 ); 163 is( 164 isObjectInspectorNodeExpandable(node), 165 false, 166 "The node can't be expanded" 167 ); 168 } 169 170 async function testUndefinedGetter(oi) { 171 let node = findObjectInspectorNode(oi, "myUndefinedGetter"); 172 is( 173 isObjectInspectorNodeExpandable(node), 174 false, 175 "The node can't be expanded" 176 ); 177 const invokeButton = getObjectInspectorInvokeGetterButton(node); 178 ok(invokeButton, "There is an invoke button as expected"); 179 180 invokeButton.click(); 181 await waitFor( 182 () => 183 !getObjectInspectorInvokeGetterButton( 184 findObjectInspectorNode(oi, "myUndefinedGetter") 185 ) 186 ); 187 188 node = findObjectInspectorNode(oi, "myUndefinedGetter"); 189 ok( 190 node.textContent.includes(`myUndefinedGetter: undefined`), 191 "undefined getter now has the expected text content" 192 ); 193 is( 194 isObjectInspectorNodeExpandable(node), 195 false, 196 "The node can't be expanded" 197 ); 198 } 199 200 async function testNullGetter(oi) { 201 let node = findObjectInspectorNode(oi, "myNullGetter"); 202 is( 203 isObjectInspectorNodeExpandable(node), 204 false, 205 "The node can't be expanded" 206 ); 207 const invokeButton = getObjectInspectorInvokeGetterButton(node); 208 ok(invokeButton, "There is an invoke button as expected"); 209 210 invokeButton.click(); 211 await waitFor( 212 () => 213 !getObjectInspectorInvokeGetterButton( 214 findObjectInspectorNode(oi, "myNullGetter") 215 ) 216 ); 217 218 node = findObjectInspectorNode(oi, "myNullGetter"); 219 ok( 220 node.textContent.includes(`myNullGetter: null`), 221 "null getter now has the expected text content" 222 ); 223 is( 224 isObjectInspectorNodeExpandable(node), 225 false, 226 "The node can't be expanded" 227 ); 228 } 229 230 async function testZeroGetter(oi) { 231 let node = findObjectInspectorNode(oi, "myZeroGetter"); 232 is( 233 isObjectInspectorNodeExpandable(node), 234 false, 235 "The node can't be expanded" 236 ); 237 const invokeButton = getObjectInspectorInvokeGetterButton(node); 238 ok(invokeButton, "There is an invoke button as expected"); 239 240 invokeButton.click(); 241 await waitFor( 242 () => 243 !getObjectInspectorInvokeGetterButton( 244 findObjectInspectorNode(oi, "myZeroGetter") 245 ) 246 ); 247 248 node = findObjectInspectorNode(oi, "myZeroGetter"); 249 ok( 250 node.textContent.includes(`myZeroGetter: 0`), 251 "0 getter now has the expected text content" 252 ); 253 is( 254 isObjectInspectorNodeExpandable(node), 255 false, 256 "The node can't be expanded" 257 ); 258 } 259 260 async function testEmptyStringGetter(oi) { 261 let node = findObjectInspectorNode(oi, "myEmptyStringGetter"); 262 is( 263 isObjectInspectorNodeExpandable(node), 264 false, 265 "The node can't be expanded" 266 ); 267 const invokeButton = getObjectInspectorInvokeGetterButton(node); 268 ok(invokeButton, "There is an invoke button as expected"); 269 270 invokeButton.click(); 271 await waitFor( 272 () => 273 !getObjectInspectorInvokeGetterButton( 274 findObjectInspectorNode(oi, "myEmptyStringGetter") 275 ) 276 ); 277 278 node = findObjectInspectorNode(oi, "myEmptyStringGetter"); 279 ok( 280 node.textContent.includes(`myEmptyStringGetter: ""`), 281 "empty string getter now has the expected text content" 282 ); 283 is( 284 isObjectInspectorNodeExpandable(node), 285 false, 286 "The node can't be expanded" 287 ); 288 } 289 290 async function testFalseGetter(oi) { 291 let node = findObjectInspectorNode(oi, "myFalseGetter"); 292 is( 293 isObjectInspectorNodeExpandable(node), 294 false, 295 "The node can't be expanded" 296 ); 297 const invokeButton = getObjectInspectorInvokeGetterButton(node); 298 ok(invokeButton, "There is an invoke button as expected"); 299 300 invokeButton.click(); 301 await waitFor( 302 () => 303 !getObjectInspectorInvokeGetterButton( 304 findObjectInspectorNode(oi, "myFalseGetter") 305 ) 306 ); 307 308 node = findObjectInspectorNode(oi, "myFalseGetter"); 309 ok( 310 node.textContent.includes(`myFalseGetter: false`), 311 "false getter now has the expected text content" 312 ); 313 is( 314 isObjectInspectorNodeExpandable(node), 315 false, 316 "The node can't be expanded" 317 ); 318 } 319 320 async function testTrueGetter(oi) { 321 let node = findObjectInspectorNode(oi, "myTrueGetter"); 322 is( 323 isObjectInspectorNodeExpandable(node), 324 false, 325 "The node can't be expanded" 326 ); 327 const invokeButton = getObjectInspectorInvokeGetterButton(node); 328 ok(invokeButton, "There is an invoke button as expected"); 329 330 invokeButton.click(); 331 await waitFor( 332 () => 333 !getObjectInspectorInvokeGetterButton( 334 findObjectInspectorNode(oi, "myTrueGetter") 335 ) 336 ); 337 338 node = findObjectInspectorNode(oi, "myTrueGetter"); 339 ok( 340 node.textContent.includes(`myTrueGetter: true`), 341 "false getter now has the expected text content" 342 ); 343 is( 344 isObjectInspectorNodeExpandable(node), 345 false, 346 "The node can't be expanded" 347 ); 348 } 349 350 async function testObjectGetter(oi) { 351 let node = findObjectInspectorNode(oi, "myObjectGetter"); 352 is( 353 isObjectInspectorNodeExpandable(node), 354 false, 355 "The node can't be expanded" 356 ); 357 const invokeButton = getObjectInspectorInvokeGetterButton(node); 358 ok(invokeButton, "There is an invoke button as expected"); 359 360 invokeButton.click(); 361 await waitFor( 362 () => 363 !getObjectInspectorInvokeGetterButton( 364 findObjectInspectorNode(oi, "myObjectGetter") 365 ) 366 ); 367 368 node = findObjectInspectorNode(oi, "myObjectGetter"); 369 ok( 370 node.textContent.includes(`myObjectGetter: Object { foo: "bar" }`), 371 "object getter now has the expected text content" 372 ); 373 is(isObjectInspectorNodeExpandable(node), true, "The node can be expanded"); 374 375 await expandObjectInspectorNode(node); 376 checkChildren(node, [`foo: "bar"`, `<prototype>`]); 377 } 378 379 async function testArrayGetter(oi) { 380 let node = findObjectInspectorNode(oi, "myArrayGetter"); 381 is( 382 isObjectInspectorNodeExpandable(node), 383 false, 384 "The node can't be expanded" 385 ); 386 const invokeButton = getObjectInspectorInvokeGetterButton(node); 387 ok(invokeButton, "There is an invoke button as expected"); 388 389 invokeButton.click(); 390 await waitFor( 391 () => 392 !getObjectInspectorInvokeGetterButton( 393 findObjectInspectorNode(oi, "myArrayGetter") 394 ) 395 ); 396 397 node = findObjectInspectorNode(oi, "myArrayGetter"); 398 ok( 399 node.textContent.includes( 400 `myArrayGetter: Array(1000) [ 0, 1, 2, ${ELLIPSIS} ]` 401 ), 402 "Array getter now has the expected text content - " 403 ); 404 is(isObjectInspectorNodeExpandable(node), true, "The node can be expanded"); 405 406 await expandObjectInspectorNode(node); 407 const children = getObjectInspectorChildrenNodes(node); 408 409 const firstBucket = children[0]; 410 ok(firstBucket.textContent.includes(`[0${ELLIPSIS}99]`), "Array has buckets"); 411 412 is( 413 isObjectInspectorNodeExpandable(firstBucket), 414 true, 415 "The bucket can be expanded" 416 ); 417 await expandObjectInspectorNode(firstBucket); 418 checkChildren( 419 firstBucket, 420 Array.from({ length: 100 }, (_, i) => `${i}: ${i}`) 421 ); 422 } 423 424 async function testMapGetter(oi) { 425 let node = findObjectInspectorNode(oi, "myMapGetter"); 426 is( 427 isObjectInspectorNodeExpandable(node), 428 false, 429 "The node can't be expanded" 430 ); 431 const invokeButton = getObjectInspectorInvokeGetterButton(node); 432 ok(invokeButton, "There is an invoke button as expected"); 433 434 invokeButton.click(); 435 await waitFor( 436 () => 437 !getObjectInspectorInvokeGetterButton( 438 findObjectInspectorNode(oi, "myMapGetter") 439 ) 440 ); 441 442 node = findObjectInspectorNode(oi, "myMapGetter"); 443 ok( 444 node.textContent.includes(`myMapGetter: Map`), 445 "map getter now has the expected text content" 446 ); 447 is(isObjectInspectorNodeExpandable(node), true, "The node can be expanded"); 448 449 await expandObjectInspectorNode(node); 450 checkChildren(node, [`size`, `<entries>`, `<prototype>`]); 451 452 const entriesNode = findObjectInspectorNode(oi, "<entries>"); 453 await expandObjectInspectorNode(entriesNode); 454 checkChildren(entriesNode, [`foo → Object { bar: "baz" }`]); 455 456 const entryNode = getObjectInspectorChildrenNodes(entriesNode)[0]; 457 await expandObjectInspectorNode(entryNode); 458 checkChildren(entryNode, [`<key>: "foo"`, `<value>: Object { bar: "baz" }`]); 459 } 460 461 async function testProxyGetter(oi) { 462 let node = findObjectInspectorNode(oi, "myProxyGetter"); 463 is( 464 isObjectInspectorNodeExpandable(node), 465 false, 466 "The node can't be expanded" 467 ); 468 const invokeButton = getObjectInspectorInvokeGetterButton(node); 469 ok(invokeButton, "There is an invoke button as expected"); 470 471 invokeButton.click(); 472 await waitFor( 473 () => 474 !getObjectInspectorInvokeGetterButton( 475 findObjectInspectorNode(oi, "myProxyGetter") 476 ) 477 ); 478 479 node = findObjectInspectorNode(oi, "myProxyGetter"); 480 ok( 481 node.textContent.includes(`myProxyGetter: Proxy`), 482 "proxy getter now has the expected text content" 483 ); 484 is(isObjectInspectorNodeExpandable(node), true, "The node can be expanded"); 485 486 await expandObjectInspectorNode(node); 487 checkChildren(node, [`<target>`, `<handler>`]); 488 489 const targetNode = findObjectInspectorNode(oi, "<target>"); 490 await expandObjectInspectorNode(targetNode); 491 checkChildren(targetNode, [`a: 1`, `<prototype>`]); 492 493 const handlerNode = findObjectInspectorNode(oi, "<handler>"); 494 await expandObjectInspectorNode(handlerNode); 495 checkChildren(handlerNode, [`get:`, `<prototype>`]); 496 } 497 498 async function testThrowingGetter(oi) { 499 let node = findObjectInspectorNode(oi, "myThrowingGetter"); 500 is( 501 isObjectInspectorNodeExpandable(node), 502 false, 503 "The node can't be expanded" 504 ); 505 const invokeButton = getObjectInspectorInvokeGetterButton(node); 506 ok(invokeButton, "There is an invoke button as expected"); 507 508 invokeButton.click(); 509 await waitFor( 510 () => 511 !getObjectInspectorInvokeGetterButton( 512 findObjectInspectorNode(oi, "myThrowingGetter") 513 ) 514 ); 515 516 node = findObjectInspectorNode(oi, "myThrowingGetter"); 517 ok( 518 node.textContent.includes(`myThrowingGetter: Error`), 519 "throwing getter does show the error" 520 ); 521 is(isObjectInspectorNodeExpandable(node), true, "The node can be expanded"); 522 523 await expandObjectInspectorNode(node); 524 await waitFor(() => !!getObjectInspectorChildrenNodes(node).length); 525 checkChildren(node, [ 526 `columnNumber`, 527 `fileName`, 528 `lineNumber`, 529 `message`, 530 `stack`, 531 `<prototype>`, 532 ]); 533 } 534 535 async function testLongStringGetter(oi, longString) { 536 const getLongStringNode = () => 537 findObjectInspectorNode(oi, "myLongStringGetter"); 538 const node = getLongStringNode(); 539 is( 540 isObjectInspectorNodeExpandable(node), 541 false, 542 "The node can't be expanded" 543 ); 544 const invokeButton = getObjectInspectorInvokeGetterButton(node); 545 ok(invokeButton, "There is an invoke button as expected"); 546 547 invokeButton.click(); 548 await waitFor(() => 549 getLongStringNode().textContent.includes(`myLongStringGetter: "ab ab`) 550 ); 551 ok(true, "longstring getter shows the initial text"); 552 is( 553 isObjectInspectorNodeExpandable(getLongStringNode()), 554 true, 555 "The node can be expanded" 556 ); 557 558 await expandObjectInspectorNode(getLongStringNode()); 559 await waitFor(() => 560 getLongStringNode().textContent.includes( 561 `myLongStringGetter: "${longString}"` 562 ) 563 ); 564 ok(true, "the longstring was expanded"); 565 } 566 567 async function testHypgenGetter(oi) { 568 const findHyphenGetterNode = () => 569 findObjectInspectorNode(oi, `"hyphen-getter"`); 570 let node = findHyphenGetterNode(); 571 572 is( 573 isObjectInspectorNodeExpandable(node), 574 false, 575 "The node can't be expanded" 576 ); 577 const invokeButton = getObjectInspectorInvokeGetterButton(node); 578 ok(invokeButton, "There is an invoke button as expected"); 579 580 invokeButton.click(); 581 await waitFor( 582 () => !getObjectInspectorInvokeGetterButton(findHyphenGetterNode()) 583 ); 584 585 node = findHyphenGetterNode(); 586 ok( 587 node.textContent.includes(`"hyphen-getter": "---"`), 588 "Node now has the expected text content" 589 ); 590 is( 591 isObjectInspectorNodeExpandable(node), 592 false, 593 "The node can't be expanded" 594 ); 595 } 596 597 async function testQuotedGetters(oi) { 598 const nodes = [ 599 { 600 name: `'"quoted-getter"'`, 601 expected: `"quoted"`, 602 expandable: false, 603 }, 604 { 605 name: `"\\"'\`"`, 606 expected: `"quoted2"`, 607 expandable: false, 608 }, 609 ]; 610 611 for (const { name, expected, expandable } of nodes) { 612 await testGetter(oi, name, expected, expandable); 613 } 614 } 615 616 async function testGetter(oi, propertyName, expectedResult, resultExpandable) { 617 info(`Check «${propertyName}» getter`); 618 const findNode = () => findObjectInspectorNode(oi, propertyName); 619 620 let node = findNode(); 621 is( 622 isObjectInspectorNodeExpandable(node), 623 false, 624 `«${propertyName}» can't be expanded` 625 ); 626 getObjectInspectorInvokeGetterButton(node).click(); 627 await waitFor(() => !getObjectInspectorInvokeGetterButton(findNode())); 628 629 node = findNode(); 630 ok( 631 node.textContent.includes(`${propertyName}: ${expectedResult}`), 632 `«${propertyName}» now has the expected text content («${expectedResult}»)` 633 ); 634 is( 635 isObjectInspectorNodeExpandable(node), 636 resultExpandable, 637 `«${propertyName}» ${resultExpandable ? "now can" : "can't"} be expanded` 638 ); 639 } 640 641 function checkChildren(node, expectedChildren) { 642 const children = getObjectInspectorChildrenNodes(node); 643 is( 644 children.length, 645 expectedChildren.length, 646 "There is the expected number of children" 647 ); 648 children.forEach((child, index) => { 649 ok( 650 child.textContent.includes(expectedChildren[index]), 651 `Expected "${child.textContent}" to include "${expectedChildren[index]}"` 652 ); 653 }); 654 }