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