diff options
Diffstat (limited to 'devtools/shared/commands/inspected-window/tests')
4 files changed, 681 insertions, 0 deletions
diff --git a/devtools/shared/commands/inspected-window/tests/browser.ini b/devtools/shared/commands/inspected-window/tests/browser.ini new file mode 100644 index 0000000000..69c027e9bc --- /dev/null +++ b/devtools/shared/commands/inspected-window/tests/browser.ini @@ -0,0 +1,11 @@ +[DEFAULT] +tags = devtools +subsuite = devtools +support-files = + !/devtools/client/shared/test/shared-head.js + !/devtools/client/shared/test/telemetry-test-helpers.js + !/devtools/client/shared/test/highlighter-test-actor.js + head.js + inspectedwindow-reload-target.sjs + +[browser_webextension_inspected_window.js] diff --git a/devtools/shared/commands/inspected-window/tests/browser_webextension_inspected_window.js b/devtools/shared/commands/inspected-window/tests/browser_webextension_inspected_window.js new file mode 100644 index 0000000000..7d44981639 --- /dev/null +++ b/devtools/shared/commands/inspected-window/tests/browser_webextension_inspected_window.js @@ -0,0 +1,569 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const TEST_RELOAD_URL = `${URL_ROOT_SSL}/inspectedwindow-reload-target.sjs`; + +async function setup(pageUrl) { + // Disable bfcache for Fission for now. + // If Fission is disabled, the pref is no-op. + await SpecialPowers.pushPrefEnv({ + set: [["fission.bfcacheInParent", false]], + }); + + 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 tab = await addTab(pageUrl); + + const commands = await CommandsFactory.forTab(tab, { isWebExtension: true }); + await commands.targetCommand.startListening(); + + const webConsoleFront = await commands.targetCommand.targetFront.getFront( + "console" + ); + + return { + webConsoleFront, + commands, + extension, + fakeExtCallerInfo, + }; +} + +async function teardown({ commands, extension }) { + await commands.destroy(); + gBrowser.removeCurrentTab(); + await extension.unload(); +} + +function waitForNextTabNavigated(commands) { + const target = commands.targetCommand.targetFront; + 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 { commands, extension, fakeExtCallerInfo } = await setup(URL_ROOT_SSL); + + const result = await commands.inspectedWindowCommand.eval( + fakeExtCallerInfo, + "window.location", + {} + ); + + ok(result.value, "Got a result from inspectedWindow eval"); + is( + result.value.href, + URL_ROOT_SSL, + "Got the expected window.location.href property value" + ); + is( + result.value.protocol, + "https:", + "Got the expected window.location.protocol property value" + ); + + await teardown({ commands, extension }); +}); + +add_task(async function test_successfull_inspectedWindowEval_resultAsGrip() { + const { + commands, + extension, + fakeExtCallerInfo, + webConsoleFront, + } = await setup(URL_ROOT_SSL); + + let result = await commands.inspectedWindowCommand.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 commands.inspectedWindowCommand.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({ commands, extension }); +}); + +add_task(async function test_error_inspectedWindowEval_result() { + const { commands, extension, fakeExtCallerInfo } = await setup(URL_ROOT_SSL); + + const result = await commands.inspectedWindowCommand.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({ commands, extension }); +}); + +add_task( + async function test_system_principal_denied_error_inspectedWindowEval_result() { + const { commands, extension, fakeExtCallerInfo } = await setup( + "about:addons" + ); + + const result = await commands.inspectedWindowCommand.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({ commands, extension }); + } +); + +add_task(async function test_exception_inspectedWindowEval_result() { + const { commands, extension, fakeExtCallerInfo } = await setup(URL_ROOT_SSL); + + const result = await commands.inspectedWindowCommand.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({ commands, extension }); +}); + +add_task(async function test_exception_inspectedWindowReload() { + const { commands, extension, fakeExtCallerInfo } = await setup( + `${TEST_RELOAD_URL}?test=cache` + ); + + // Test reload with bypassCache=false. + + const waitForNoBypassCacheReload = waitForNextTabNavigated(commands); + const reloadResult = await commands.inspectedWindowCommand.reload( + fakeExtCallerInfo, + { + ignoreCache: false, + } + ); + + ok( + !reloadResult, + "Got the expected undefined result from inspectedWindow reload" + ); + + await waitForNoBypassCacheReload; + + const noBypassCacheEval = await commands.scriptCommand.execute( + "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(commands); + await commands.inspectedWindowCommand.reload(fakeExtCallerInfo, { + ignoreCache: true, + }); + + await waitForForceBypassCacheReload; + + const forceBypassCacheEval = await commands.scriptCommand.execute( + "document.body.textContent" + ); + + is( + forceBypassCacheEval.result, + "no-cache:no-cache", + "Got the expected result with reload forceBypassCache=true" + ); + + await teardown({ commands, extension }); +}); + +add_task(async function test_exception_inspectedWindowReload_customUserAgent() { + const { commands, extension, fakeExtCallerInfo } = await setup( + `${TEST_RELOAD_URL}?test=user-agent` + ); + + // Test reload with custom userAgent. + + const waitForCustomUserAgentReload = waitForNextTabNavigated(commands); + await commands.inspectedWindowCommand.reload(fakeExtCallerInfo, { + userAgent: "Customized User Agent", + }); + + await waitForCustomUserAgentReload; + + const customUserAgentEval = await commands.scriptCommand.execute( + "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(commands); + await commands.inspectedWindowCommand.reload(fakeExtCallerInfo, {}); + + await waitForNoCustomUserAgentReload; + + const noCustomUserAgentEval = await commands.scriptCommand.execute( + "document.body.textContent" + ); + + is( + noCustomUserAgentEval.result, + window.navigator.userAgent, + "Got the expected result with reload without a customized userAgent" + ); + + await teardown({ commands, extension }); +}); + +add_task(async function test_exception_inspectedWindowReload_injectedScript() { + const { commands, extension, fakeExtCallerInfo } = await setup( + `${TEST_RELOAD_URL}?test=injected-script&frames=3` + ); + + // Test reload with an injectedScript. + + const waitForInjectedScriptReload = waitForNextTabNavigated(commands); + await commands.inspectedWindowCommand.reload(fakeExtCallerInfo, { + injectedScript: `new ${injectedScript}`, + }); + await waitForInjectedScriptReload; + + const injectedScriptEval = await commands.scriptCommand.execute( + `(${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(commands); + await commands.inspectedWindowCommand.reload(fakeExtCallerInfo, {}); + await waitForNoInjectedScriptReload; + + const noInjectedScriptEval = await commands.scriptCommand.execute( + `(${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({ commands, extension }); +}); + +add_task(async function test_exception_inspectedWindowReload_multiple_calls() { + const { commands, extension, fakeExtCallerInfo } = 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(commands); + + commands.inspectedWindowCommand.reload(fakeExtCallerInfo, { + userAgent: "Customized User Agent 1", + }); + commands.inspectedWindowCommand.reload(fakeExtCallerInfo, { + userAgent: "Customized User Agent 2", + }); + + await waitForCustomUserAgentReload; + + const customUserAgentEval = await commands.scriptCommand.execute( + "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(commands); + await commands.inspectedWindowCommand.reload(fakeExtCallerInfo, {}); + + await waitForNoCustomUserAgentReload; + + const noCustomUserAgentEval = await commands.scriptCommand.execute( + "document.body.textContent" + ); + + is( + noCustomUserAgentEval.result, + window.navigator.userAgent, + "Got the expected result with reload without a customized userAgent" + ); + + await teardown({ commands, extension }); +}); + +add_task(async function test_exception_inspectedWindowReload_stopped() { + const { commands, extension, fakeExtCallerInfo } = 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(commands); + await commands.inspectedWindowCommand.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(commands); + await commands.inspectedWindowCommand.reload(fakeExtCallerInfo, { + injectedScript: `new ${injectedScript}`, + }); + await waitForInjectedScriptReload; + + const injectedScriptEval = await commands.scriptCommand.execute( + `(${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(commands); + await commands.inspectedWindowCommand.reload(fakeExtCallerInfo, {}); + await waitForNoInjectedScriptReload; + + const noInjectedScriptEval = await commands.scriptCommand.execute( + `(${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({ commands, extension }); +}); + +// TODO: check eval with $0 binding once implemented (Bug 1300590) diff --git a/devtools/shared/commands/inspected-window/tests/head.js b/devtools/shared/commands/inspected-window/tests/head.js new file mode 100644 index 0000000000..227e8ae9d9 --- /dev/null +++ b/devtools/shared/commands/inspected-window/tests/head.js @@ -0,0 +1,13 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +/* eslint no-unused-vars: [2, {"vars": "local"}] */ +/* import-globals-from ../../../../client/shared/test/shared-head.js */ + +Services.scriptloader.loadSubScript( + "chrome://mochitests/content/browser/devtools/client/shared/test/shared-head.js", + this +); diff --git a/devtools/shared/commands/inspected-window/tests/inspectedwindow-reload-target.sjs b/devtools/shared/commands/inspected-window/tests/inspectedwindow-reload-target.sjs new file mode 100644 index 0000000000..fb1e468389 --- /dev/null +++ b/devtools/shared/commands/inspected-window/tests/inspectedwindow-reload-target.sjs @@ -0,0 +1,88 @@ +"use strict"; + +Cu.importGlobalProperties(["URLSearchParams"]); + +function handleRequest(request, response) { + const params = new URLSearchParams(request.queryString); + + switch (params.get("test")) { + case "cache": + handleCacheTestRequest(request, response); + break; + + case "user-agent": + handleUserAgentTestRequest(request, response); + break; + + case "injected-script": + handleInjectedScriptTestRequest(request, response, params); + break; + } +} + +function handleCacheTestRequest(request, response) { + response.setHeader("Content-Type", "text/plain; charset=UTF-8", false); + + if (request.hasHeader("pragma") && request.hasHeader("cache-control")) { + response.write( + `${request.getHeader("pragma")}:${request.getHeader("cache-control")}` + ); + } else { + response.write("empty cache headers"); + } +} + +function handleUserAgentTestRequest(request, response) { + response.setHeader("Content-Type", "text/plain; charset=UTF-8", false); + + if (request.hasHeader("user-agent")) { + response.write(request.getHeader("user-agent")); + } else { + response.write("no user agent header"); + } +} + +function handleInjectedScriptTestRequest(request, response, params) { + response.setHeader("Content-Type", "text/html; charset=UTF-8", false); + + const frames = parseInt(params.get("frames"), 10); + let content = ""; + + if (frames > 0) { + // Output an iframe in seamless mode, so that there is an higher chance that in case + // of test failures we get a screenshot where the nested iframes are all visible. + content = `<iframe seamless src="?test=injected-script&frames=${frames - + 1}"></iframe>`; + } else { + // Output an about:srcdoc frame to be sure that inspectedWindow.eval is able to + // evaluate js code into it. + const srcdoc = ` + <pre>injected script NOT executed</pre> + <script>window.pageScriptExecutedFirst = true</script> + `; + content = `<iframe style="height: 30px;" srcdoc="${srcdoc}"></iframe>`; + } + + if (params.get("stop") == "windowStop") { + content = "<script>window.stop();</script>" + content; + } + + response.write(`<!DOCTYPE html> + <html> + <head> + <meta charset="utf-8"> + <style> + iframe { width: 100%; height: ${frames * 150}px; } + </style> + </head> + <body> + <h1>IFRAME ${frames}</h1> + <pre>injected script NOT executed</pre> + <script> + window.pageScriptExecutedFirst = true; + </script> + ${content} + </body> + </html> + `); +} |