browser_webextension_inspected_window.js (15121B)
1 /* Any copyright is dedicated to the Public Domain. 2 http://creativecommons.org/publicdomain/zero/1.0/ */ 3 4 "use strict"; 5 6 const TEST_RELOAD_URL = `${URL_ROOT_SSL}/inspectedwindow-reload-target.sjs`; 7 8 async function setup(pageUrl) { 9 // Disable bfcache for Fission for now. 10 // If Fission is disabled, the pref is no-op. 11 await SpecialPowers.pushPrefEnv({ 12 set: [["fission.bfcacheInParent", false]], 13 }); 14 15 const extension = ExtensionTestUtils.loadExtension({ 16 background() { 17 // This is just an empty extension used to ensure that the caller extension uuid 18 // actually exists. 19 }, 20 }); 21 22 await extension.startup(); 23 24 const fakeExtCallerInfo = { 25 url: WebExtensionPolicy.getByID(extension.id).getURL( 26 "fake-caller-script.js" 27 ), 28 lineNumber: 1, 29 addonId: extension.id, 30 }; 31 32 const tab = await addTab(pageUrl); 33 34 const commands = await CommandsFactory.forTab(tab, { isWebExtension: true }); 35 await commands.targetCommand.startListening(); 36 37 const webConsoleFront = 38 await commands.targetCommand.targetFront.getFront("console"); 39 40 return { 41 webConsoleFront, 42 commands, 43 extension, 44 fakeExtCallerInfo, 45 }; 46 } 47 48 async function teardown({ commands, extension }) { 49 await commands.destroy(); 50 gBrowser.removeCurrentTab(); 51 await extension.unload(); 52 } 53 54 function waitForNextTabNavigated(commands) { 55 const target = commands.targetCommand.targetFront; 56 return new Promise(resolve => { 57 target.on("tabNavigated", function tabNavigatedListener(pkt) { 58 if (pkt.state == "stop" && !pkt.isFrameSwitching) { 59 target.off("tabNavigated", tabNavigatedListener); 60 resolve(); 61 } 62 }); 63 }); 64 } 65 66 // Script used as the injectedScript option in the inspectedWindow.reload tests. 67 function injectedScript() { 68 if (!window.pageScriptExecutedFirst) { 69 window.addEventListener( 70 "DOMContentLoaded", 71 function () { 72 if (document.querySelector("pre")) { 73 document.querySelector("pre").textContent = 74 "injected script executed first"; 75 } 76 }, 77 { once: true } 78 ); 79 } 80 } 81 82 // Script evaluated in the target tab, to collect the results of injectedScript 83 // evaluation in the inspectedWindow.reload tests. 84 function collectEvalResults() { 85 const results = []; 86 let iframeDoc = document; 87 88 while (iframeDoc) { 89 if (iframeDoc.querySelector("pre")) { 90 results.push(iframeDoc.querySelector("pre").textContent); 91 } 92 const iframe = iframeDoc.querySelector("iframe"); 93 iframeDoc = iframe ? iframe.contentDocument : null; 94 } 95 return JSON.stringify(results); 96 } 97 98 add_task(async function test_successfull_inspectedWindowEval_result() { 99 const { commands, extension, fakeExtCallerInfo } = await setup(URL_ROOT_SSL); 100 101 const result = await commands.inspectedWindowCommand.eval( 102 fakeExtCallerInfo, 103 "window.location", 104 {} 105 ); 106 107 ok(result.value, "Got a result from inspectedWindow eval"); 108 is( 109 result.value.href, 110 URL_ROOT_SSL, 111 "Got the expected window.location.href property value" 112 ); 113 is( 114 result.value.protocol, 115 "https:", 116 "Got the expected window.location.protocol property value" 117 ); 118 119 await teardown({ commands, extension }); 120 }); 121 122 add_task(async function test_successfull_inspectedWindowEval_resultAsGrip() { 123 const { commands, extension, fakeExtCallerInfo, webConsoleFront } = 124 await setup(URL_ROOT_SSL); 125 126 let result = await commands.inspectedWindowCommand.eval( 127 fakeExtCallerInfo, 128 "window", 129 { 130 evalResultAsGrip: true, 131 toolboxConsoleActorID: webConsoleFront.actor, 132 } 133 ); 134 135 ok(result.valueGrip, "Got a result from inspectedWindow eval"); 136 ok(result.valueGrip.actor, "Got a object actor as expected"); 137 is(result.valueGrip.type, "object", "Got a value grip of type object"); 138 is( 139 result.valueGrip.class, 140 "Window", 141 "Got a value grip which is instanceof Location" 142 ); 143 144 // Test invalid evalResultAsGrip request. 145 result = await commands.inspectedWindowCommand.eval( 146 fakeExtCallerInfo, 147 "window", 148 { 149 evalResultAsGrip: true, 150 } 151 ); 152 153 ok( 154 !result.value && !result.valueGrip, 155 "Got a null result from the invalid inspectedWindow eval call" 156 ); 157 ok( 158 result.exceptionInfo.isError, 159 "Got an API Error result from inspectedWindow eval" 160 ); 161 ok( 162 !result.exceptionInfo.isException, 163 "An error isException is false as expected" 164 ); 165 is( 166 result.exceptionInfo.code, 167 "E_PROTOCOLERROR", 168 "Got the expected 'code' property in the error result" 169 ); 170 is( 171 result.exceptionInfo.description, 172 "Inspector protocol error: %s - %s", 173 "Got the expected 'description' property in the error result" 174 ); 175 is( 176 result.exceptionInfo.details.length, 177 2, 178 "The 'details' array property should contains 1 element" 179 ); 180 is( 181 result.exceptionInfo.details[0], 182 "Unexpected invalid sidebar panel expression request", 183 "Got the expected content in the error results's details" 184 ); 185 is( 186 result.exceptionInfo.details[1], 187 "missing toolboxConsoleActorID", 188 "Got the expected content in the error results's details" 189 ); 190 191 await teardown({ commands, extension }); 192 }); 193 194 add_task(async function test_error_inspectedWindowEval_result() { 195 const { commands, extension, fakeExtCallerInfo } = await setup(URL_ROOT_SSL); 196 197 const result = await commands.inspectedWindowCommand.eval( 198 fakeExtCallerInfo, 199 "window", 200 {} 201 ); 202 203 ok(!result.value, "Got a null result from inspectedWindow eval"); 204 ok( 205 result.exceptionInfo.isError, 206 "Got an API Error result from inspectedWindow eval" 207 ); 208 ok( 209 !result.exceptionInfo.isException, 210 "An error isException is false as expected" 211 ); 212 is( 213 result.exceptionInfo.code, 214 "E_PROTOCOLERROR", 215 "Got the expected 'code' property in the error result" 216 ); 217 is( 218 result.exceptionInfo.description, 219 "Inspector protocol error: %s", 220 "Got the expected 'description' property in the error result" 221 ); 222 is( 223 result.exceptionInfo.details.length, 224 1, 225 "The 'details' array property should contains 1 element" 226 ); 227 ok( 228 result.exceptionInfo.details[0].includes("cyclic object value"), 229 "Got the expected content in the error results's details" 230 ); 231 232 await teardown({ commands, extension }); 233 }); 234 235 add_task(async function test_exception_inspectedWindowEval_result() { 236 const { commands, extension, fakeExtCallerInfo } = await setup(URL_ROOT_SSL); 237 238 const result = await commands.inspectedWindowCommand.eval( 239 fakeExtCallerInfo, 240 "throw Error('fake eval error');", 241 {} 242 ); 243 244 ok(result.exceptionInfo.isException, "Got an exception as expected"); 245 ok(!result.value, "Got an undefined eval value"); 246 ok(!result.exceptionInfo.isError, "An exception should not be isError=true"); 247 ok( 248 result.exceptionInfo.value.includes("Error: fake eval error"), 249 "Got the expected exception message" 250 ); 251 252 const expectedCallerInfo = `called from ${fakeExtCallerInfo.url}:${fakeExtCallerInfo.lineNumber}`; 253 ok( 254 result.exceptionInfo.value.includes(expectedCallerInfo), 255 "Got the expected caller info in the exception message" 256 ); 257 258 const expectedStack = `eval code:1:7`; 259 ok( 260 result.exceptionInfo.value.includes(expectedStack), 261 "Got the expected stack trace in the exception message" 262 ); 263 264 await teardown({ commands, extension }); 265 }); 266 267 add_task(async function test_exception_inspectedWindowReload() { 268 const { commands, extension, fakeExtCallerInfo } = await setup( 269 `${TEST_RELOAD_URL}?test=cache` 270 ); 271 272 // Test reload with bypassCache=false. 273 274 const waitForNoBypassCacheReload = waitForNextTabNavigated(commands); 275 const reloadResult = await commands.inspectedWindowCommand.reload( 276 fakeExtCallerInfo, 277 { 278 ignoreCache: false, 279 } 280 ); 281 282 ok( 283 !reloadResult, 284 "Got the expected undefined result from inspectedWindow reload" 285 ); 286 287 await waitForNoBypassCacheReload; 288 289 const noBypassCacheEval = await commands.scriptCommand.execute( 290 "document.body.textContent" 291 ); 292 293 is( 294 noBypassCacheEval.result, 295 "empty cache headers", 296 "Got the expected result with reload forceBypassCache=false" 297 ); 298 299 // Test reload with bypassCache=true. 300 301 const waitForForceBypassCacheReload = waitForNextTabNavigated(commands); 302 await commands.inspectedWindowCommand.reload(fakeExtCallerInfo, { 303 ignoreCache: true, 304 }); 305 306 await waitForForceBypassCacheReload; 307 308 const forceBypassCacheEval = await commands.scriptCommand.execute( 309 "document.body.textContent" 310 ); 311 312 is( 313 forceBypassCacheEval.result, 314 "no-cache:no-cache", 315 "Got the expected result with reload forceBypassCache=true" 316 ); 317 318 await teardown({ commands, extension }); 319 }); 320 321 add_task(async function test_exception_inspectedWindowReload_customUserAgent() { 322 const { commands, extension, fakeExtCallerInfo } = await setup( 323 `${TEST_RELOAD_URL}?test=user-agent` 324 ); 325 326 // Test reload with custom userAgent. 327 328 const waitForCustomUserAgentReload = waitForNextTabNavigated(commands); 329 await commands.inspectedWindowCommand.reload(fakeExtCallerInfo, { 330 userAgent: "Customized User Agent", 331 }); 332 333 await waitForCustomUserAgentReload; 334 335 const customUserAgentEval = await commands.scriptCommand.execute( 336 "document.body.textContent" 337 ); 338 339 is( 340 customUserAgentEval.result, 341 "Customized User Agent", 342 "Got the expected result on reload with a customized userAgent" 343 ); 344 345 // Test reload with no custom userAgent. 346 347 const waitForNoCustomUserAgentReload = waitForNextTabNavigated(commands); 348 await commands.inspectedWindowCommand.reload(fakeExtCallerInfo, {}); 349 350 await waitForNoCustomUserAgentReload; 351 352 const noCustomUserAgentEval = await commands.scriptCommand.execute( 353 "document.body.textContent" 354 ); 355 356 is( 357 noCustomUserAgentEval.result, 358 window.navigator.userAgent, 359 "Got the expected result with reload without a customized userAgent" 360 ); 361 362 await teardown({ commands, extension }); 363 }); 364 365 add_task(async function test_exception_inspectedWindowReload_injectedScript() { 366 const { commands, extension, fakeExtCallerInfo } = await setup( 367 `${TEST_RELOAD_URL}?test=injected-script&frames=3` 368 ); 369 370 // Test reload with an injectedScript. 371 372 const waitForInjectedScriptReload = waitForNextTabNavigated(commands); 373 await commands.inspectedWindowCommand.reload(fakeExtCallerInfo, { 374 injectedScript: `new ${injectedScript}`, 375 }); 376 await waitForInjectedScriptReload; 377 378 const injectedScriptEval = await commands.scriptCommand.execute( 379 `(${collectEvalResults})()` 380 ); 381 382 const expectedResult = new Array(5).fill("injected script executed first"); 383 384 SimpleTest.isDeeply( 385 JSON.parse(injectedScriptEval.result), 386 expectedResult, 387 "Got the expected result on reload with an injected script" 388 ); 389 390 // Test reload without an injectedScript. 391 392 const waitForNoInjectedScriptReload = waitForNextTabNavigated(commands); 393 await commands.inspectedWindowCommand.reload(fakeExtCallerInfo, {}); 394 await waitForNoInjectedScriptReload; 395 396 const noInjectedScriptEval = await commands.scriptCommand.execute( 397 `(${collectEvalResults})()` 398 ); 399 400 const newExpectedResult = new Array(5).fill("injected script NOT executed"); 401 402 SimpleTest.isDeeply( 403 JSON.parse(noInjectedScriptEval.result), 404 newExpectedResult, 405 "Got the expected result on reload with no injected script" 406 ); 407 408 await teardown({ commands, extension }); 409 }); 410 411 add_task(async function test_exception_inspectedWindowReload_multiple_calls() { 412 const { commands, extension, fakeExtCallerInfo } = await setup( 413 `${TEST_RELOAD_URL}?test=user-agent` 414 ); 415 416 // Test reload with custom userAgent three times (and then 417 // check that only the first one has affected the page reload. 418 419 const waitForCustomUserAgentReload = waitForNextTabNavigated(commands); 420 421 commands.inspectedWindowCommand.reload(fakeExtCallerInfo, { 422 userAgent: "Customized User Agent 1", 423 }); 424 commands.inspectedWindowCommand.reload(fakeExtCallerInfo, { 425 userAgent: "Customized User Agent 2", 426 }); 427 428 await waitForCustomUserAgentReload; 429 430 const customUserAgentEval = await commands.scriptCommand.execute( 431 "document.body.textContent" 432 ); 433 434 is( 435 customUserAgentEval.result, 436 "Customized User Agent 1", 437 "Got the expected result on reload with a customized userAgent" 438 ); 439 440 // Test reload with no custom userAgent. 441 442 const waitForNoCustomUserAgentReload = waitForNextTabNavigated(commands); 443 await commands.inspectedWindowCommand.reload(fakeExtCallerInfo, {}); 444 445 await waitForNoCustomUserAgentReload; 446 447 const noCustomUserAgentEval = await commands.scriptCommand.execute( 448 "document.body.textContent" 449 ); 450 451 is( 452 noCustomUserAgentEval.result, 453 window.navigator.userAgent, 454 "Got the expected result with reload without a customized userAgent" 455 ); 456 457 await teardown({ commands, extension }); 458 }); 459 460 add_task(async function test_exception_inspectedWindowReload_stopped() { 461 const { commands, extension, fakeExtCallerInfo } = await setup( 462 `${TEST_RELOAD_URL}?test=injected-script&frames=3` 463 ); 464 465 // Test reload on a page that calls window.stop() immediately during the page loading 466 467 const waitForPageLoad = waitForNextTabNavigated(commands); 468 await commands.inspectedWindowCommand.eval( 469 fakeExtCallerInfo, 470 "window.location += '&stop=windowStop'" 471 ); 472 473 info("Load a webpage that calls 'window.stop()' while is still loading"); 474 await waitForPageLoad; 475 476 info("Starting a reload with an injectedScript"); 477 const waitForInjectedScriptReload = waitForNextTabNavigated(commands); 478 await commands.inspectedWindowCommand.reload(fakeExtCallerInfo, { 479 injectedScript: `new ${injectedScript}`, 480 }); 481 await waitForInjectedScriptReload; 482 483 const injectedScriptEval = await commands.scriptCommand.execute( 484 `(${collectEvalResults})()` 485 ); 486 487 // The page should have stopped during the reload and only one injected script 488 // is expected. 489 const expectedResult = new Array(1).fill("injected script executed first"); 490 491 SimpleTest.isDeeply( 492 JSON.parse(injectedScriptEval.result), 493 expectedResult, 494 "The injected script has been executed on the 'stopped' page reload" 495 ); 496 497 // Reload again with no options. 498 499 info("Reload the tab again without any reload options"); 500 const waitForNoInjectedScriptReload = waitForNextTabNavigated(commands); 501 await commands.inspectedWindowCommand.reload(fakeExtCallerInfo, {}); 502 await waitForNoInjectedScriptReload; 503 504 const noInjectedScriptEval = await commands.scriptCommand.execute( 505 `(${collectEvalResults})()` 506 ); 507 508 // The page should have stopped during the reload and no injected script should 509 // have been executed during this second reload (or it would mean that the previous 510 // customized reload was still pending and has wrongly affected the second reload) 511 const newExpectedResult = new Array(1).fill("injected script NOT executed"); 512 513 SimpleTest.isDeeply( 514 JSON.parse(noInjectedScriptEval.result), 515 newExpectedResult, 516 "No injectedScript should have been evaluated during the second reload" 517 ); 518 519 await teardown({ commands, extension }); 520 }); 521 522 // TODO: check eval with $0 binding once implemented (Bug 1300590)