/* Any copyright is dedicated to the Public Domain. http://creativecommons.org/publicdomain/zero/1.0/ */ "use strict"; const TEST_RELOAD_URL = `${MAIN_DOMAIN}/inspectedwindow-reload-target.sjs`; async function setup(pageUrl) { const extension = ExtensionTestUtils.loadExtension({ background() { // This is just an empty extension used to ensure that the caller extension uuid // actually exists. }, }); await extension.startup(); const fakeExtCallerInfo = { url: WebExtensionPolicy.getByID(extension.id).getURL( "fake-caller-script.js" ), lineNumber: 1, addonId: extension.id, }; const target = await addTabTarget(pageUrl); await target.attach(); const { client } = target; const webConsoleFront = await target.getFront("console"); const inspectedWindowFront = await target.getFront( "webExtensionInspectedWindow" ); return { client, target, webConsoleFront, inspectedWindowFront, extension, fakeExtCallerInfo, }; } async function teardown({ client, extension }) { await client.close(); DevToolsServer.destroy(); gBrowser.removeCurrentTab(); await extension.unload(); } function waitForNextTabNavigated(target) { return new Promise(resolve => { target.on("tabNavigated", function tabNavigatedListener(pkt) { if (pkt.state == "stop" && !pkt.isFrameSwitching) { target.off("tabNavigated", tabNavigatedListener); resolve(); } }); }); } // Script used as the injectedScript option in the inspectedWindow.reload tests. function injectedScript() { if (!window.pageScriptExecutedFirst) { window.addEventListener( "DOMContentLoaded", function() { if (document.querySelector("pre")) { document.querySelector("pre").textContent = "injected script executed first"; } }, { once: true } ); } } // Script evaluated in the target tab, to collect the results of injectedScript // evaluation in the inspectedWindow.reload tests. function collectEvalResults() { const results = []; let iframeDoc = document; while (iframeDoc) { if (iframeDoc.querySelector("pre")) { results.push(iframeDoc.querySelector("pre").textContent); } const iframe = iframeDoc.querySelector("iframe"); iframeDoc = iframe ? iframe.contentDocument : null; } return JSON.stringify(results); } add_task(async function test_successfull_inspectedWindowEval_result() { const { client, inspectedWindowFront, extension, fakeExtCallerInfo, } = await setup(MAIN_DOMAIN); const result = await inspectedWindowFront.eval( fakeExtCallerInfo, "window.location", {} ); ok(result.value, "Got a result from inspectedWindow eval"); is( result.value.href, MAIN_DOMAIN, "Got the expected window.location.href property value" ); is( result.value.protocol, "http:", "Got the expected window.location.protocol property value" ); await teardown({ client, extension }); }); add_task(async function test_successfull_inspectedWindowEval_resultAsGrip() { const { client, inspectedWindowFront, extension, fakeExtCallerInfo, webConsoleFront, } = await setup(MAIN_DOMAIN); let result = await inspectedWindowFront.eval(fakeExtCallerInfo, "window", { evalResultAsGrip: true, toolboxConsoleActorID: webConsoleFront.actor, }); ok(result.valueGrip, "Got a result from inspectedWindow eval"); ok(result.valueGrip.actor, "Got a object actor as expected"); is(result.valueGrip.type, "object", "Got a value grip of type object"); is( result.valueGrip.class, "Window", "Got a value grip which is instanceof Location" ); // Test invalid evalResultAsGrip request. result = await inspectedWindowFront.eval(fakeExtCallerInfo, "window", { evalResultAsGrip: true, }); ok( !result.value && !result.valueGrip, "Got a null result from the invalid inspectedWindow eval call" ); ok( result.exceptionInfo.isError, "Got an API Error result from inspectedWindow eval" ); ok( !result.exceptionInfo.isException, "An error isException is false as expected" ); is( result.exceptionInfo.code, "E_PROTOCOLERROR", "Got the expected 'code' property in the error result" ); is( result.exceptionInfo.description, "Inspector protocol error: %s - %s", "Got the expected 'description' property in the error result" ); is( result.exceptionInfo.details.length, 2, "The 'details' array property should contains 1 element" ); is( result.exceptionInfo.details[0], "Unexpected invalid sidebar panel expression request", "Got the expected content in the error results's details" ); is( result.exceptionInfo.details[1], "missing toolboxConsoleActorID", "Got the expected content in the error results's details" ); await teardown({ client, extension }); }); add_task(async function test_error_inspectedWindowEval_result() { const { client, inspectedWindowFront, extension, fakeExtCallerInfo, } = await setup(MAIN_DOMAIN); const result = await inspectedWindowFront.eval( fakeExtCallerInfo, "window", {} ); ok(!result.value, "Got a null result from inspectedWindow eval"); ok( result.exceptionInfo.isError, "Got an API Error result from inspectedWindow eval" ); ok( !result.exceptionInfo.isException, "An error isException is false as expected" ); is( result.exceptionInfo.code, "E_PROTOCOLERROR", "Got the expected 'code' property in the error result" ); is( result.exceptionInfo.description, "Inspector protocol error: %s", "Got the expected 'description' property in the error result" ); is( result.exceptionInfo.details.length, 1, "The 'details' array property should contains 1 element" ); ok( result.exceptionInfo.details[0].includes("cyclic object value"), "Got the expected content in the error results's details" ); await teardown({ client, extension }); }); add_task( async function test_system_principal_denied_error_inspectedWindowEval_result() { const { client, inspectedWindowFront, extension, fakeExtCallerInfo, } = await setup("about:addons"); const result = await inspectedWindowFront.eval( fakeExtCallerInfo, "window", {} ); ok(!result.value, "Got a null result from inspectedWindow eval"); ok( result.exceptionInfo.isError, "Got an API Error result from inspectedWindow eval on a system principal page" ); is( result.exceptionInfo.code, "E_PROTOCOLERROR", "Got the expected 'code' property in the error result" ); is( result.exceptionInfo.description, "Inspector protocol error: %s", "Got the expected 'description' property in the error result" ); is( result.exceptionInfo.details.length, 1, "The 'details' array property should contains 1 element" ); is( result.exceptionInfo.details[0], "This target has a system principal. inspectedWindow.eval denied.", "Got the expected content in the error results's details" ); await teardown({ client, extension }); } ); add_task(async function test_exception_inspectedWindowEval_result() { const { client, inspectedWindowFront, extension, fakeExtCallerInfo, } = await setup(MAIN_DOMAIN); const result = await inspectedWindowFront.eval( fakeExtCallerInfo, "throw Error('fake eval error');", {} ); ok(result.exceptionInfo.isException, "Got an exception as expected"); ok(!result.value, "Got an undefined eval value"); ok(!result.exceptionInfo.isError, "An exception should not be isError=true"); ok( result.exceptionInfo.value.includes("Error: fake eval error"), "Got the expected exception message" ); const expectedCallerInfo = `called from ${fakeExtCallerInfo.url}:${fakeExtCallerInfo.lineNumber}`; ok( result.exceptionInfo.value.includes(expectedCallerInfo), "Got the expected caller info in the exception message" ); const expectedStack = `eval code:1:7`; ok( result.exceptionInfo.value.includes(expectedStack), "Got the expected stack trace in the exception message" ); await teardown({ client, extension }); }); add_task(async function test_exception_inspectedWindowReload() { const { client, webConsoleFront, inspectedWindowFront, extension, fakeExtCallerInfo, target, } = await setup(`${TEST_RELOAD_URL}?test=cache`); // Test reload with bypassCache=false. const waitForNoBypassCacheReload = waitForNextTabNavigated(target); const reloadResult = await inspectedWindowFront.reload(fakeExtCallerInfo, { ignoreCache: false, }); ok( !reloadResult, "Got the expected undefined result from inspectedWindow reload" ); await waitForNoBypassCacheReload; const noBypassCacheEval = await webConsoleFront.evaluateJSAsync( "document.body.textContent" ); is( noBypassCacheEval.result, "empty cache headers", "Got the expected result with reload forceBypassCache=false" ); // Test reload with bypassCache=true. const waitForForceBypassCacheReload = waitForNextTabNavigated(target); await inspectedWindowFront.reload(fakeExtCallerInfo, { ignoreCache: true }); await waitForForceBypassCacheReload; const forceBypassCacheEval = await webConsoleFront.evaluateJSAsync( "document.body.textContent" ); is( forceBypassCacheEval.result, "no-cache:no-cache", "Got the expected result with reload forceBypassCache=true" ); await teardown({ client, extension }); }); add_task(async function test_exception_inspectedWindowReload_customUserAgent() { const { client, webConsoleFront, inspectedWindowFront, extension, fakeExtCallerInfo, target, } = await setup(`${TEST_RELOAD_URL}?test=user-agent`); // Test reload with custom userAgent. const waitForCustomUserAgentReload = waitForNextTabNavigated(target); await inspectedWindowFront.reload(fakeExtCallerInfo, { userAgent: "Customized User Agent", }); await waitForCustomUserAgentReload; const customUserAgentEval = await webConsoleFront.evaluateJSAsync( "document.body.textContent" ); is( customUserAgentEval.result, "Customized User Agent", "Got the expected result on reload with a customized userAgent" ); // Test reload with no custom userAgent. const waitForNoCustomUserAgentReload = waitForNextTabNavigated(target); await inspectedWindowFront.reload(fakeExtCallerInfo, {}); await waitForNoCustomUserAgentReload; const noCustomUserAgentEval = await webConsoleFront.evaluateJSAsync( "document.body.textContent" ); is( noCustomUserAgentEval.result, window.navigator.userAgent, "Got the expected result with reload without a customized userAgent" ); await teardown({ client, extension }); }); add_task(async function test_exception_inspectedWindowReload_injectedScript() { const { client, webConsoleFront, inspectedWindowFront, extension, fakeExtCallerInfo, target, } = await setup(`${TEST_RELOAD_URL}?test=injected-script&frames=3`); // Test reload with an injectedScript. const waitForInjectedScriptReload = waitForNextTabNavigated(target); await inspectedWindowFront.reload(fakeExtCallerInfo, { injectedScript: `new ${injectedScript}`, }); await waitForInjectedScriptReload; const injectedScriptEval = await webConsoleFront.evaluateJSAsync( `(${collectEvalResults})()` ); const expectedResult = new Array(5).fill("injected script executed first"); SimpleTest.isDeeply( JSON.parse(injectedScriptEval.result), expectedResult, "Got the expected result on reload with an injected script" ); // Test reload without an injectedScript. const waitForNoInjectedScriptReload = waitForNextTabNavigated(target); await inspectedWindowFront.reload(fakeExtCallerInfo, {}); await waitForNoInjectedScriptReload; const noInjectedScriptEval = await webConsoleFront.evaluateJSAsync( `(${collectEvalResults})()` ); const newExpectedResult = new Array(5).fill("injected script NOT executed"); SimpleTest.isDeeply( JSON.parse(noInjectedScriptEval.result), newExpectedResult, "Got the expected result on reload with no injected script" ); await teardown({ client, extension }); }); add_task(async function test_exception_inspectedWindowReload_multiple_calls() { const { client, webConsoleFront, inspectedWindowFront, extension, fakeExtCallerInfo, target, } = await setup(`${TEST_RELOAD_URL}?test=user-agent`); // Test reload with custom userAgent three times (and then // check that only the first one has affected the page reload. const waitForCustomUserAgentReload = waitForNextTabNavigated(target); inspectedWindowFront.reload(fakeExtCallerInfo, { userAgent: "Customized User Agent 1", }); inspectedWindowFront.reload(fakeExtCallerInfo, { userAgent: "Customized User Agent 2", }); await waitForCustomUserAgentReload; const customUserAgentEval = await webConsoleFront.evaluateJSAsync( "document.body.textContent" ); is( customUserAgentEval.result, "Customized User Agent 1", "Got the expected result on reload with a customized userAgent" ); // Test reload with no custom userAgent. const waitForNoCustomUserAgentReload = waitForNextTabNavigated(target); await inspectedWindowFront.reload(fakeExtCallerInfo, {}); await waitForNoCustomUserAgentReload; const noCustomUserAgentEval = await webConsoleFront.evaluateJSAsync( "document.body.textContent" ); is( noCustomUserAgentEval.result, window.navigator.userAgent, "Got the expected result with reload without a customized userAgent" ); await teardown({ client, extension }); }); add_task(async function test_exception_inspectedWindowReload_stopped() { const { client, webConsoleFront, inspectedWindowFront, extension, fakeExtCallerInfo, target, } = await setup(`${TEST_RELOAD_URL}?test=injected-script&frames=3`); // Test reload on a page that calls window.stop() immediately during the page loading const waitForPageLoad = waitForNextTabNavigated(target); await inspectedWindowFront.eval( fakeExtCallerInfo, "window.location += '&stop=windowStop'" ); info("Load a webpage that calls 'window.stop()' while is still loading"); await waitForPageLoad; info("Starting a reload with an injectedScript"); const waitForInjectedScriptReload = waitForNextTabNavigated(target); await inspectedWindowFront.reload(fakeExtCallerInfo, { injectedScript: `new ${injectedScript}`, }); await waitForInjectedScriptReload; const injectedScriptEval = await webConsoleFront.evaluateJSAsync( `(${collectEvalResults})()` ); // The page should have stopped during the reload and only one injected script // is expected. const expectedResult = new Array(1).fill("injected script executed first"); SimpleTest.isDeeply( JSON.parse(injectedScriptEval.result), expectedResult, "The injected script has been executed on the 'stopped' page reload" ); // Reload again with no options. info("Reload the tab again without any reload options"); const waitForNoInjectedScriptReload = waitForNextTabNavigated(target); await inspectedWindowFront.reload(fakeExtCallerInfo, {}); await waitForNoInjectedScriptReload; const noInjectedScriptEval = await webConsoleFront.evaluateJSAsync( `(${collectEvalResults})()` ); // The page should have stopped during the reload and no injected script should // have been executed during this second reload (or it would mean that the previous // customized reload was still pending and has wrongly affected the second reload) const newExpectedResult = new Array(1).fill("injected script NOT executed"); SimpleTest.isDeeply( JSON.parse(noInjectedScriptEval.result), newExpectedResult, "No injectedScript should have been evaluated during the second reload" ); await teardown({ client, extension }); }); // TODO: check eval with $0 binding once implemented (Bug 1300590)