browser_script_command_execute_basic.js (31507B)
1 /* Any copyright is dedicated to the Public Domain. 2 http://creativecommons.org/publicdomain/zero/1.0/ */ 3 4 "use strict"; 5 6 // Testing basic expression evaluation 7 const { 8 MAX_AUTOCOMPLETE_ATTEMPTS, 9 MAX_AUTOCOMPLETIONS, 10 } = require("resource://devtools/shared/webconsole/js-property-provider.js"); 11 const { 12 DevToolsServer, 13 } = require("resource://devtools/server/devtools-server.js"); 14 15 add_task(async () => { 16 const tab = await addTab(`data:text/html;charset=utf-8, 17 <!DOCTYPE html> 18 <html dir="ltr" class="class1"> 19 <head><title>Testcase</title></head> 20 <script> 21 window.foobarObject = Object.create( 22 null, 23 Object.getOwnPropertyDescriptors({ 24 foo: 1, 25 foobar: 2, 26 foobaz: 3, 27 omg: 4, 28 omgfoo: 5, 29 strfoo: "foobarz", 30 omgstr: "foobarz" + "abb".repeat(${DevToolsServer.LONG_STRING_LENGTH} * 2), 31 }) 32 ); 33 34 window.largeObject1 = Object.create(null); 35 for (let i = 0; i < ${MAX_AUTOCOMPLETE_ATTEMPTS} + 1; i++) { 36 window.largeObject1["a" + i] = i; 37 } 38 39 window.largeObject2 = Object.create(null); 40 for (let i = 0; i < ${MAX_AUTOCOMPLETIONS} * 2; i++) { 41 window.largeObject2["a" + i] = i; 42 } 43 44 var originalExec = RegExp.prototype.exec; 45 46 var promptIterable = { [Symbol.iterator]() { return { next: prompt } } }; 47 48 function aliasedTest() { 49 const aliased = "ALIASED"; 50 return [0].map(() => aliased)[0]; 51 } 52 53 var testMap = new Map([[1, 1], [2, 2], [3, 3], [4, 4]]); 54 var testSet = new Set([1, 2, 3, 4, 5]); 55 var testProxy = new Proxy({}, { getPrototypeOf: prompt }); 56 var testArray = [1,2,3]; 57 var testInt8Array = new Int8Array([1, 2, 3]); 58 var testArrayBuffer = testInt8Array.buffer; 59 var testDataView = new DataView(testArrayBuffer, 2); 60 61 var testCanvasContext = document.createElement("canvas").getContext("2d"); 62 63 var objWithNativeGetter = {}; 64 Object.defineProperty(objWithNativeGetter, "print", { get: print }); 65 Object.defineProperty(objWithNativeGetter, "Element", { get: Element }); 66 Object.defineProperty(objWithNativeGetter, "setAttribute", { get: Element.prototype.setAttribute }); 67 Object.defineProperty(objWithNativeGetter, "setClassName", { get: Object.getOwnPropertyDescriptor(Element.prototype, "className").set }); 68 Object.defineProperty(objWithNativeGetter, "requestPermission", { get: Notification.requestPermission }); 69 70 async function testAsync() { return 10; } 71 async function testAsyncAwait() { await 1; return 10; } 72 async function * testAsyncGen() { return 10; } 73 async function * testAsyncGenAwait() { await 1; return 10; } 74 75 function testFunc() {} 76 77 var testLocale = new Intl.Locale("de-latn-de-u-ca-gregory-co-phonebk-hc-h23-kf-true-kn-false-nu-latn"); 78 79 var testFormData = new FormData(); 80 var testHeaders = new Headers(); 81 var testURLSearchParams = new URLSearchParams(); 82 var testReadableStream = new ReadableStream(); 83 </script> 84 <body id="body1" class="class2"><h1>Body text</h1></body> 85 </html>`); 86 87 const commands = await CommandsFactory.forTab(tab); 88 await commands.targetCommand.startListening(); 89 90 await doSimpleEval(commands); 91 await doWindowEval(commands); 92 await doEvalWithException(commands); 93 await doEvalWithHelper(commands); 94 await doEvalString(commands); 95 await doEvalLongString(commands); 96 await doEvalWithBinding(commands); 97 await forceLexicalInit(commands); 98 await doSimpleEagerEval(commands); 99 await doEagerEvalWithSideEffect(commands); 100 await doEagerEvalWithSideEffectIterator(commands); 101 await doEagerEvalWithSideEffectMonkeyPatched(commands); 102 await doEagerEvalESGetters(commands); 103 await doEagerEvalDOMGetters(commands); 104 await doEagerEvalOtherNativeGetters(commands); 105 await doEagerEvalDOMMethods(commands); 106 await doEagerEvalAsyncFunctions(commands); 107 108 await commands.destroy(); 109 }); 110 111 async function doSimpleEval(commands) { 112 info("test eval '2+2'"); 113 const response = await commands.scriptCommand.execute("2+2"); 114 checkObject(response, { 115 input: "2+2", 116 result: 4, 117 }); 118 119 ok(!response.exception, "no eval exception"); 120 ok(!response.helperResult, "no helper result"); 121 } 122 123 async function doWindowEval(commands) { 124 info("test eval 'document'"); 125 const response = await commands.scriptCommand.execute("document"); 126 checkObject(response, { 127 input: "document", 128 result: { 129 type: "object", 130 class: "HTMLDocument", 131 actor: /[a-z]/, 132 }, 133 }); 134 135 ok(!response.exception, "no eval exception"); 136 ok(!response.helperResult, "no helper result"); 137 } 138 139 async function doEvalWithException(commands) { 140 info("test eval with exception"); 141 const response = await commands.scriptCommand.execute( 142 "window.doTheImpossible()" 143 ); 144 checkObject(response, { 145 input: "window.doTheImpossible()", 146 result: { 147 type: "undefined", 148 }, 149 exceptionMessage: /doTheImpossible/, 150 }); 151 152 ok(response.exception, "js eval exception"); 153 ok(!response.helperResult, "no helper result"); 154 } 155 156 async function doEvalWithHelper(commands) { 157 info("test eval with helper"); 158 const response = await commands.scriptCommand.execute("clear()"); 159 checkObject(response, { 160 input: "clear()", 161 result: { 162 type: "undefined", 163 }, 164 helperResult: { type: "clearOutput" }, 165 }); 166 167 ok(!response.exception, "no eval exception"); 168 } 169 170 async function doEvalString(commands) { 171 const response = await commands.scriptCommand.execute( 172 "window.foobarObject.strfoo" 173 ); 174 175 checkObject(response, { 176 input: "window.foobarObject.strfoo", 177 result: "foobarz", 178 }); 179 } 180 181 async function doEvalLongString(commands) { 182 const response = await commands.scriptCommand.execute( 183 "window.foobarObject.omgstr" 184 ); 185 186 const str = await SpecialPowers.spawn( 187 gBrowser.selectedBrowser, 188 [], 189 function () { 190 return content.wrappedJSObject.foobarObject.omgstr; 191 } 192 ); 193 194 const initial = str.substring(0, DevToolsServer.LONG_STRING_INITIAL_LENGTH); 195 196 checkObject(response, { 197 input: "window.foobarObject.omgstr", 198 result: { 199 type: "longString", 200 initial, 201 length: str.length, 202 }, 203 }); 204 } 205 206 async function doEvalWithBinding(commands) { 207 const response = await commands.scriptCommand.execute("document;"); 208 const documentActor = response.result.actorID; 209 210 info("running a command with _self as document using selectedObjectActor"); 211 const selectedObjectSame = await commands.scriptCommand.execute( 212 "_self === document", 213 { 214 selectedObjectActor: documentActor, 215 } 216 ); 217 checkObject(selectedObjectSame, { 218 result: true, 219 }); 220 } 221 222 async function forceLexicalInit(commands) { 223 info("test that failed let/const bindings are initialized to undefined"); 224 225 const testData = [ 226 { 227 stmt: "let foopie = wubbalubadubdub", 228 vars: ["foopie"], 229 }, 230 { 231 stmt: "let {z, w={n}=null} = {}", 232 vars: ["z", "w"], 233 }, 234 { 235 stmt: "let [a, b, c] = null", 236 vars: ["a", "b", "c"], 237 }, 238 { 239 stmt: "const nein1 = rofl, nein2 = copter", 240 vars: ["nein1", "nein2"], 241 }, 242 { 243 stmt: "const {ha} = null", 244 vars: ["ha"], 245 }, 246 { 247 stmt: "const [haw=[lame]=null] = []", 248 vars: ["haw"], 249 }, 250 { 251 stmt: "const [rawr, wat=[lame]=null] = []", 252 vars: ["rawr", "haw"], 253 }, 254 { 255 stmt: "let {zzz: xyz=99, zwz: wb} = nexistepas()", 256 vars: ["xyz", "wb"], 257 }, 258 { 259 stmt: "let {c3pdoh=101} = null", 260 vars: ["c3pdoh"], 261 }, 262 { 263 stmt: "const {...x} = x", 264 vars: ["x"], 265 }, 266 { 267 stmt: "const {xx,yy,...rest} = null", 268 vars: ["xx", "yy", "rest"], 269 }, 270 ]; 271 272 for (const data of testData) { 273 const response = await commands.scriptCommand.execute(data.stmt); 274 checkObject(response, { 275 input: data.stmt, 276 result: { type: "undefined" }, 277 }); 278 ok(response.exception, "expected exception"); 279 for (const varName of data.vars) { 280 const response2 = await commands.scriptCommand.execute(varName); 281 checkObject(response2, { 282 input: varName, 283 result: { type: "undefined" }, 284 }); 285 ok(!response2.exception, "unexpected exception"); 286 } 287 } 288 } 289 290 async function doSimpleEagerEval(commands) { 291 const testData = [ 292 { 293 code: "2+2", 294 result: 4, 295 }, 296 { 297 code: "(x => x * 2)(3)", 298 result: 6, 299 }, 300 { 301 code: "[1, 2, 3].map(x => x * 2).join()", 302 result: "2,4,6", 303 }, 304 { 305 code: `"abc".match(/a./)[0]`, 306 result: "ab", 307 }, 308 { 309 code: "aliasedTest()", 310 result: "ALIASED", 311 }, 312 { 313 code: "testArray.concat([4,5]).join()", 314 result: "1,2,3,4,5", 315 }, 316 { 317 code: "testArray.entries().toString()", 318 result: "[object Array Iterator]", 319 }, 320 { 321 code: "testArray.keys().toString()", 322 result: "[object Array Iterator]", 323 }, 324 { 325 code: "testArray.values().toString()", 326 result: "[object Array Iterator]", 327 }, 328 { 329 code: "testArray.every(x => x < 100)", 330 result: true, 331 }, 332 { 333 code: "testArray.some(x => x > 1)", 334 result: true, 335 }, 336 { 337 code: "testArray.filter(x => x % 2 == 0).join()", 338 result: "2", 339 }, 340 { 341 code: "testArray.find(x => x % 2 == 0)", 342 result: 2, 343 }, 344 { 345 code: "testArray.findIndex(x => x % 2 == 0)", 346 result: 1, 347 }, 348 { 349 code: "[testArray].flat().join()", 350 result: "1,2,3", 351 }, 352 { 353 code: "[testArray].flatMap(x => x).join()", 354 result: "1,2,3", 355 }, 356 { 357 code: "testArray.forEach(x => x); testArray.join()", 358 result: "1,2,3", 359 }, 360 { 361 code: "testArray.includes(1)", 362 result: true, 363 }, 364 { 365 code: "testArray.lastIndexOf(1)", 366 result: 0, 367 }, 368 { 369 code: "testArray.map(x => x + 1).join()", 370 result: "2,3,4", 371 }, 372 { 373 code: "testArray.reduce((acc,x) => acc + x, 0)", 374 result: 6, 375 }, 376 { 377 code: "testArray.reduceRight((acc,x) => acc + x, 0)", 378 result: 6, 379 }, 380 { 381 code: "testArray.slice(0,1).join()", 382 result: "1", 383 }, 384 { 385 code: "testArray.toReversed().join()", 386 result: "3,2,1", 387 }, 388 { 389 code: "testArray.toSorted().join()", 390 result: "1,2,3", 391 }, 392 { 393 code: "testArray.toSpliced(0,1).join()", 394 result: "2,3", 395 }, 396 { 397 code: "testArray.with(1, 'b').join()", 398 result: "1,b,3", 399 }, 400 401 { 402 code: "testInt8Array.entries().toString()", 403 result: "[object Array Iterator]", 404 }, 405 { 406 code: "testInt8Array.keys().toString()", 407 result: "[object Array Iterator]", 408 }, 409 { 410 code: "testInt8Array.values().toString()", 411 result: "[object Array Iterator]", 412 }, 413 { 414 code: "testInt8Array.every(x => x < 100)", 415 result: true, 416 }, 417 { 418 code: "testInt8Array.some(x => x > 1)", 419 result: true, 420 }, 421 { 422 code: "testInt8Array.filter(x => x % 2 == 0).join()", 423 result: "2", 424 }, 425 { 426 code: "testInt8Array.find(x => x % 2 == 0)", 427 result: 2, 428 }, 429 { 430 code: "testInt8Array.findIndex(x => x % 2 == 0)", 431 result: 1, 432 }, 433 { 434 code: "testInt8Array.forEach(x => x); testInt8Array.join()", 435 result: "1,2,3", 436 }, 437 { 438 code: "testInt8Array.includes(1)", 439 result: true, 440 }, 441 { 442 code: "testInt8Array.lastIndexOf(1)", 443 result: 0, 444 }, 445 { 446 code: "testInt8Array.map(x => x + 1).join()", 447 result: "2,3,4", 448 }, 449 { 450 code: "testInt8Array.reduce((acc,x) => acc + x, 0)", 451 result: 6, 452 }, 453 { 454 code: "testInt8Array.reduceRight((acc,x) => acc + x, 0)", 455 result: 6, 456 }, 457 { 458 code: "testInt8Array.slice(0,1).join()", 459 result: "1", 460 }, 461 { 462 code: "testInt8Array.toReversed().join()", 463 skip: 464 typeof Reflect.getPrototypeOf(Int8Array).prototype.toReversed !== 465 "function", 466 result: "3,2,1", 467 }, 468 { 469 code: "testInt8Array.toSorted().join()", 470 skip: 471 typeof Reflect.getPrototypeOf(Int8Array).prototype.toSorted !== 472 "function", 473 result: "1,2,3", 474 }, 475 { 476 code: "testInt8Array.with(1, 0).join()", 477 skip: 478 typeof Reflect.getPrototypeOf(Int8Array).prototype.with !== "function", 479 result: "1,0,3", 480 }, 481 ]; 482 483 for (const { code, result, skip } of testData) { 484 if (skip) { 485 info(`Skipping evaluation of ${code}`); 486 continue; 487 } 488 489 info(`Evaluating: ${code}`); 490 const response = await commands.scriptCommand.execute(code, { 491 eager: true, 492 }); 493 checkObject(response, { 494 input: code, 495 result, 496 }); 497 498 ok(!response.exception, "no eval exception"); 499 ok(!response.helperResult, "no helper result"); 500 } 501 } 502 503 async function doEagerEvalWithSideEffect(commands) { 504 const testData = [ 505 // Modify environment. 506 "var a = 10; a;", 507 508 // Directly call a funtion with side effect. 509 "prompt();", 510 511 // Call a funtion with side effect inside a scripted function. 512 "(() => { prompt(); })()", 513 514 // Call a funtion with side effect from self-hosted JS function. 515 "[1, 2, 3].map(prompt)", 516 517 // Call a function with Function.prototype.call. 518 "Function.prototype.call.bind(Function.prototype.call)(prompt);", 519 520 // Call a function with Function.prototype.apply. 521 "Function.prototype.apply.bind(Function.prototype.apply)(prompt);", 522 523 // Indirectly call a function with Function.prototype.apply. 524 "Reflect.apply(prompt, null, []);", 525 "'aaaaaaaa'.replace(/(a)(a)(a)(a)(a)(a)(a)(a)/, prompt)", 526 527 // Indirect call on obj[Symbol.iterator]().next. 528 "Array.from(promptIterable)", 529 ]; 530 531 for (const code of testData) { 532 const response = await commands.scriptCommand.execute(code, { 533 eager: true, 534 }); 535 checkObject(response, { 536 input: code, 537 result: { type: "undefined" }, 538 }); 539 540 ok(!response.exception, "no eval exception"); 541 ok(!response.helperResult, "no helper result"); 542 } 543 } 544 545 async function doEagerEvalWithSideEffectIterator(commands) { 546 // Indirect call on %ArrayIterator%.prototype.next, 547 548 // Create an iterable object that reuses iterator across multiple call. 549 let response = await commands.scriptCommand.execute(` 550 var arr = [1, 2, 3]; 551 var iterator = arr[Symbol.iterator](); 552 var iterable = { [Symbol.iterator]() { return iterator; } }; 553 "ok"; 554 `); 555 checkObject(response, { 556 result: "ok", 557 }); 558 ok(!response.exception, "no eval exception"); 559 ok(!response.helperResult, "no helper result"); 560 561 const testData = [ 562 "Array.from(iterable)", 563 "new Map(iterable)", 564 "new Set(iterable)", 565 ]; 566 567 for (const code of testData) { 568 response = await commands.scriptCommand.execute(code, { 569 eager: true, 570 }); 571 checkObject(response, { 572 input: code, 573 result: { type: "undefined" }, 574 }); 575 576 ok(!response.exception, "no eval exception"); 577 ok(!response.helperResult, "no helper result"); 578 } 579 580 // Verify the iterator's internal state isn't modified. 581 response = await commands.scriptCommand.execute(`[...iterator].join(",")`); 582 checkObject(response, { 583 result: "1,2,3", 584 }); 585 ok(!response.exception, "no eval exception"); 586 ok(!response.helperResult, "no helper result"); 587 } 588 589 async function doEagerEvalWithSideEffectMonkeyPatched(commands) { 590 // Patch the built-in function without eager evaluation. 591 let response = await commands.scriptCommand.execute( 592 `RegExp.prototype.exec = prompt; "patched"` 593 ); 594 checkObject(response, { 595 result: "patched", 596 }); 597 ok(!response.exception, "no eval exception"); 598 ok(!response.helperResult, "no helper result"); 599 600 // Test eager evaluation, where the patched built-in is called internally. 601 // This should be aborted. 602 const code = `"abc".match(/a./)[0]`; 603 response = await commands.scriptCommand.execute(code, { eager: true }); 604 checkObject(response, { 605 input: code, 606 result: { type: "undefined" }, 607 }); 608 609 ok(!response.exception, "no eval exception"); 610 ok(!response.helperResult, "no helper result"); 611 612 // Undo the patch without eager evaluation. 613 response = await commands.scriptCommand.execute( 614 `RegExp.prototype.exec = originalExec; "unpatched"` 615 ); 616 checkObject(response, { 617 result: "unpatched", 618 }); 619 ok(!response.exception, "no eval exception"); 620 ok(!response.helperResult, "no helper result"); 621 622 // Test eager evaluation again, without the patch. 623 // This should be evaluated. 624 response = await commands.scriptCommand.execute(code, { eager: true }); 625 checkObject(response, { 626 input: code, 627 result: "ab", 628 }); 629 } 630 631 async function doEagerEvalESGetters(commands) { 632 // [code, expectedResult] 633 const testData = [ 634 // ArrayBuffer 635 ["testArrayBuffer.byteLength", 3], 636 637 // DataView 638 ["testDataView.buffer === testArrayBuffer", true], 639 ["testDataView.byteLength", 1], 640 ["testDataView.byteOffset", 2], 641 642 // Error 643 ["typeof new Error().stack", "string"], 644 645 // Function 646 ["typeof testFunc.arguments", "object"], 647 ["typeof testFunc.caller", "object"], 648 649 // Intl.Locale 650 ["testLocale.baseName", "de-Latn-DE"], 651 ["testLocale.calendar", "gregory"], 652 ["testLocale.caseFirst", ""], 653 ["testLocale.collation", "phonebk"], 654 ["testLocale.hourCycle", "h23"], 655 ["testLocale.numeric", false], 656 ["testLocale.numberingSystem", "latn"], 657 ["testLocale.language", "de"], 658 ["testLocale.script", "Latn"], 659 ["testLocale.region", "DE"], 660 661 // Map 662 ["testMap.size", 4], 663 664 // RegExp 665 ["/a/.dotAll", false], 666 ["/a/giy.flags", "giy"], 667 ["/a/g.global", true], 668 ["/a/g.hasIndices", false], 669 ["/a/g.ignoreCase", false], 670 ["/a/g.multiline", false], 671 ["/a/g.source", "a"], 672 ["/a/g.sticky", false], 673 ["/a/g.unicode", false], 674 675 // Set 676 ["testSet.size", 5], 677 678 // Symbol 679 ["Symbol.iterator.description", "Symbol.iterator"], 680 681 // TypedArray 682 ["testInt8Array.buffer === testArrayBuffer", true], 683 ["testInt8Array.byteLength", 3], 684 ["testInt8Array.byteOffset", 0], 685 ["testInt8Array.length", 3], 686 ["testInt8Array[Symbol.toStringTag]", "Int8Array"], 687 ]; 688 689 for (const [code, expectedResult] of testData) { 690 const response = await commands.scriptCommand.execute(code, { 691 eager: true, 692 }); 693 checkObject( 694 response, 695 { 696 input: code, 697 result: expectedResult, 698 }, 699 code 700 ); 701 702 ok(!response.exception, "no eval exception"); 703 ok(!response.helperResult, "no helper result"); 704 } 705 706 // Test RegExp static properties. 707 // Run preparation code here to avoid interference with other tests, 708 // given RegExp static properties are global state. 709 const regexpPreparationCode = ` 710 /b(c)(d)(e)(f)(g)(h)(i)(j)(k)l/.test("abcdefghijklm") 711 `; 712 713 const prepResponse = await commands.scriptCommand.execute( 714 regexpPreparationCode 715 ); 716 checkObject(prepResponse, { 717 input: regexpPreparationCode, 718 result: true, 719 }); 720 721 ok(!prepResponse.exception, "no eval exception"); 722 ok(!prepResponse.helperResult, "no helper result"); 723 724 const testDataRegExp = [ 725 // RegExp static 726 ["RegExp.input", "abcdefghijklm"], 727 ["RegExp.lastMatch", "bcdefghijkl"], 728 ["RegExp.lastParen", "k"], 729 ["RegExp.leftContext", "a"], 730 ["RegExp.rightContext", "m"], 731 ["RegExp.$1", "c"], 732 ["RegExp.$2", "d"], 733 ["RegExp.$3", "e"], 734 ["RegExp.$4", "f"], 735 ["RegExp.$5", "g"], 736 ["RegExp.$6", "h"], 737 ["RegExp.$7", "i"], 738 ["RegExp.$8", "j"], 739 ["RegExp.$9", "k"], 740 ["RegExp.$_", "abcdefghijklm"], // input 741 ["RegExp['$&']", "bcdefghijkl"], // lastMatch 742 ["RegExp['$+']", "k"], // lastParen 743 ["RegExp['$`']", "a"], // leftContext 744 ["RegExp[`$'`]", "m"], // rightContext 745 ]; 746 747 for (const [code, expectedResult] of testDataRegExp) { 748 const response = await commands.scriptCommand.execute(code, { 749 eager: true, 750 }); 751 checkObject( 752 response, 753 { 754 input: code, 755 result: expectedResult, 756 }, 757 code 758 ); 759 760 ok(!response.exception, "no eval exception"); 761 ok(!response.helperResult, "no helper result"); 762 } 763 764 const testDataWithSideEffect = [ 765 // get Object.prototype.__proto__ 766 // 767 // This can invoke Proxy getPrototypeOf handler, which can be any native 768 // function, and debugger cannot hook the call. 769 `[].__proto__`, 770 `testProxy.__proto__`, 771 ]; 772 773 for (const code of testDataWithSideEffect) { 774 const response = await commands.scriptCommand.execute(code, { 775 eager: true, 776 }); 777 checkObject( 778 response, 779 { 780 input: code, 781 result: { type: "undefined" }, 782 }, 783 code 784 ); 785 786 ok(!response.exception, "no eval exception"); 787 ok(!response.helperResult, "no helper result"); 788 } 789 } 790 791 async function doEagerEvalDOMGetters(commands) { 792 // Getters explicitly marked no-side-effect. 793 // 794 // [code, expectedResult] 795 const testDataExplicit = [ 796 // DOMTokenList 797 ["document.documentElement.classList.length", 1], 798 ["document.documentElement.classList.value", "class1"], 799 800 // Document 801 ["document.URL.startsWith('data:')", true], 802 ["document.documentURI.startsWith('data:')", true], 803 ["document.compatMode", "CSS1Compat"], 804 ["document.characterSet", "UTF-8"], 805 ["document.charset", "UTF-8"], 806 ["document.inputEncoding", "UTF-8"], 807 ["document.contentType", "text/html"], 808 ["document.doctype.constructor.name", "DocumentType"], 809 ["document.documentElement.constructor.name", "HTMLHtmlElement"], 810 ["document.title", "Testcase"], 811 ["document.dir", "ltr"], 812 ["document.body.constructor.name", "HTMLBodyElement"], 813 ["document.head.constructor.name", "HTMLHeadElement"], 814 ["document.images.constructor.name", "HTMLCollection"], 815 ["document.embeds.constructor.name", "HTMLCollection"], 816 ["document.plugins.constructor.name", "HTMLCollection"], 817 ["document.links.constructor.name", "HTMLCollection"], 818 ["document.forms.constructor.name", "HTMLCollection"], 819 ["document.scripts.constructor.name", "HTMLCollection"], 820 ["document.defaultView === window", true], 821 ["typeof document.currentScript", "object"], 822 ["document.anchors.constructor.name", "HTMLCollection"], 823 ["document.applets.constructor.name", "HTMLCollection"], 824 ["document.all.constructor.name", "HTMLAllCollection"], 825 ["document.styleSheetSets.constructor.name", "DOMStringList"], 826 ["typeof document.featurePolicy", "undefined"], 827 ["typeof document.blockedNodeByClassifierCount", "undefined"], 828 ["typeof document.blockedNodesByClassifier", "undefined"], 829 ["typeof document.permDelegateHandler", "undefined"], 830 ["document.children.constructor.name", "HTMLCollection"], 831 ["document.firstElementChild === document.documentElement", true], 832 ["document.lastElementChild === document.documentElement", true], 833 ["document.childElementCount", 1], 834 ["document.location.href.startsWith('data:')", true], 835 836 // Element 837 ["document.body.namespaceURI", "http://www.w3.org/1999/xhtml"], 838 ["document.body.prefix === null", true], 839 ["document.body.localName", "body"], 840 ["document.body.tagName", "BODY"], 841 ["document.body.id", "body1"], 842 ["document.body.className", "class2"], 843 ["document.body.classList.constructor.name", "DOMTokenList"], 844 ["document.body.part.constructor.name", "DOMTokenList"], 845 ["document.body.attributes.constructor.name", "NamedNodeMap"], 846 ["document.body.innerHTML.includes('Body text')", true], 847 ["document.body.outerHTML.includes('Body text')", true], 848 ["document.body.previousElementSibling !== null", true], 849 ["document.body.nextElementSibling === null", true], 850 ["document.body.children.constructor.name", "HTMLCollection"], 851 ["document.body.firstElementChild !== null", true], 852 ["document.body.lastElementChild !== null", true], 853 ["document.body.childElementCount", 1], 854 855 // Node 856 ["document.body.nodeType === Node.ELEMENT_NODE", true], 857 ["document.body.nodeName", "BODY"], 858 ["document.body.baseURI.startsWith('data:')", true], 859 ["document.body.isConnected", true], 860 ["document.body.ownerDocument === document", true], 861 ["document.body.parentNode === document.documentElement", true], 862 ["document.body.parentElement === document.documentElement", true], 863 ["document.body.childNodes.constructor.name", "NodeList"], 864 ["document.body.firstChild !== null", true], 865 ["document.body.lastChild !== null", true], 866 ["document.body.previousSibling !== null", true], 867 ["document.body.nextSibling === null", true], 868 ["document.body.nodeValue === null", true], 869 ["document.body.textContent.includes('Body text')", true], 870 ["typeof document.body.flattenedTreeParentNode", "undefined"], 871 ["typeof document.body.isNativeAnonymous", "undefined"], 872 ["typeof document.body.containingShadowRoot", "undefined"], 873 ["typeof document.body.accessibleNode", "undefined"], 874 875 // Performance 876 ["performance.timeOrigin > 0", true], 877 ["performance.timing.constructor.name", "PerformanceTiming"], 878 ["performance.navigation.constructor.name", "PerformanceNavigation"], 879 ["performance.eventCounts.constructor.name", "EventCounts"], 880 881 // window 882 ["window.window === window", true], 883 ["window.self === window", true], 884 ["window.document.constructor.name", "HTMLDocument"], 885 ["window.performance.constructor.name", "Performance"], 886 ["typeof window.browsingContext", "undefined"], 887 ["typeof window.windowUtils", "undefined"], 888 ["typeof window.windowGlobalChild", "undefined"], 889 ["window.visualViewport.constructor.name", "VisualViewport"], 890 ["typeof window.caches", "undefined"], 891 ["window.location.href.startsWith('data:')", true], 892 ]; 893 if (typeof Scheduler === "function") { 894 // Scheduler is behind a pref. 895 testDataExplicit.push(["window.scheduler.constructor.name", "Scheduler"]); 896 } 897 898 for (const [code, expectedResult] of testDataExplicit) { 899 const response = await commands.scriptCommand.execute(code, { 900 eager: true, 901 }); 902 checkObject( 903 response, 904 { 905 input: code, 906 result: expectedResult, 907 }, 908 code 909 ); 910 911 ok(!response.exception, "no eval exception"); 912 ok(!response.helperResult, "no helper result"); 913 } 914 915 // Getters not-explicitly marked no-side-effect. 916 // All DOM getters are considered no-side-effect in eager evaluation context. 917 const testDataImplicit = [ 918 // NOTE: This is not an exhaustive list. 919 // Document 920 [`document.implementation.constructor.name`, "DOMImplementation"], 921 [`typeof document.domain`, "string"], 922 [`typeof document.referrer`, "string"], 923 [`typeof document.cookie`, "string"], 924 [`typeof document.lastModified`, "string"], 925 [`typeof document.readyState`, "string"], 926 [`typeof document.designMode`, "string"], 927 [`typeof document.onabort`, "object"], 928 929 // Element 930 [`typeof document.documentElement.scrollTop`, "number"], 931 [`typeof document.documentElement.scrollLeft`, "number"], 932 [`typeof document.documentElement.scrollWidth`, "number"], 933 [`typeof document.documentElement.scrollHeight`, "number"], 934 935 // Performance 936 [`typeof performance.onresourcetimingbufferfull`, "object"], 937 938 // window 939 [`typeof window.name`, "string"], 940 [`window.history.constructor.name`, "History"], 941 [`window.customElements.constructor.name`, "CustomElementRegistry"], 942 [`window.locationbar.constructor.name`, "BarProp"], 943 [`window.menubar.constructor.name`, "BarProp"], 944 [`typeof window.status`, "string"], 945 [`window.closed`, false], 946 947 // CanvasRenderingContext2D / CanvasCompositing 948 [`testCanvasContext.globalAlpha`, 1], 949 ]; 950 951 for (const [code, expectedResult] of testDataImplicit) { 952 const response = await commands.scriptCommand.execute(code, { 953 eager: true, 954 }); 955 checkObject( 956 response, 957 { 958 input: code, 959 result: expectedResult, 960 }, 961 code 962 ); 963 964 ok(!response.exception, "no eval exception"); 965 ok(!response.helperResult, "no helper result"); 966 } 967 } 968 969 async function doEagerEvalOtherNativeGetters(commands) { 970 // DOM getter functions are allowed to be eagerly-evaluated. 971 // Test the situation where non-DOM-getter function is called by accessing 972 // getter. 973 // 974 // "being a DOM getter" is tested by checking if the native function has 975 // JSJitInfo and it's marked as getter. 976 const testData = [ 977 // Has no JitInfo. 978 "objWithNativeGetter.print", 979 "objWithNativeGetter.Element", 980 981 // Not marked as getter, but method. 982 "objWithNativeGetter.getAttribute", 983 984 // Not marked as getter, but setter. 985 "objWithNativeGetter.setClassName", 986 987 // Not marked as getter, but static method. 988 "objWithNativeGetter.requestPermission", 989 ]; 990 991 for (const code of testData) { 992 const response = await commands.scriptCommand.execute(code, { 993 eager: true, 994 }); 995 checkObject( 996 response, 997 { 998 input: code, 999 result: { type: "undefined" }, 1000 }, 1001 code 1002 ); 1003 1004 ok(!response.exception, "no eval exception"); 1005 ok(!response.helperResult, "no helper result"); 1006 } 1007 } 1008 1009 async function doEagerEvalDOMMethods(commands) { 1010 // The following "values" methods share single native function with different 1011 // JitInfo, while ReadableStream's "values" isn't side-effect free. 1012 1013 // TODO: See Bug 1910717, this can be removed when we remove the pref for iterator helpers 1014 let constructorName = "Object"; 1015 if (typeof Iterator === "function") { 1016 constructorName = "Iterator"; 1017 } 1018 1019 const testDataAllowed = [ 1020 [`testFormData.values().constructor.name`, constructorName], 1021 [`testHeaders.values().constructor.name`, constructorName], 1022 [`testURLSearchParams.values().constructor.name`, constructorName], 1023 ]; 1024 1025 for (const [code, expectedResult] of testDataAllowed) { 1026 const response = await commands.scriptCommand.execute(code, { 1027 eager: true, 1028 }); 1029 checkObject( 1030 response, 1031 { 1032 input: code, 1033 result: expectedResult, 1034 }, 1035 code 1036 ); 1037 1038 ok(!response.exception, "no eval exception"); 1039 ok(!response.helperResult, "no helper result"); 1040 } 1041 1042 const testDataDisallowed = ["testReadableStream.values()"]; 1043 1044 for (const code of testDataDisallowed) { 1045 const response = await commands.scriptCommand.execute(code, { 1046 eager: true, 1047 }); 1048 checkObject( 1049 response, 1050 { 1051 input: code, 1052 result: { type: "undefined" }, 1053 }, 1054 code 1055 ); 1056 1057 ok(!response.exception, "no eval exception"); 1058 ok(!response.helperResult, "no helper result"); 1059 } 1060 } 1061 1062 async function doEagerEvalAsyncFunctions(commands) { 1063 // [code, expectedResult] 1064 const testData = [["typeof testAsync()", "object"]]; 1065 1066 for (const [code, expectedResult] of testData) { 1067 const response = await commands.scriptCommand.execute(code, { 1068 eager: true, 1069 }); 1070 checkObject( 1071 response, 1072 { 1073 input: code, 1074 result: expectedResult, 1075 }, 1076 code 1077 ); 1078 1079 ok(!response.exception, "no eval exception"); 1080 ok(!response.helperResult, "no helper result"); 1081 } 1082 1083 const testDataWithSideEffect = [ 1084 // await is effectful 1085 "testAsyncAwait()", 1086 1087 // initial yield is effectful 1088 "testAsyncGen()", 1089 "testAsyncGenAwait()", 1090 ]; 1091 1092 for (const code of testDataWithSideEffect) { 1093 const response = await commands.scriptCommand.execute(code, { 1094 eager: true, 1095 }); 1096 checkObject( 1097 response, 1098 { 1099 input: code, 1100 result: { type: "undefined" }, 1101 }, 1102 code 1103 ); 1104 1105 ok(!response.exception, "no eval exception"); 1106 ok(!response.helperResult, "no helper result"); 1107 } 1108 }