diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
commit | 36d22d82aa202bb199967e9512281e9a53db42c9 (patch) | |
tree | 105e8c98ddea1c1e4784a60a5a6410fa416be2de /toolkit/modules/tests/browser/browser_web_channel.js | |
parent | Initial commit. (diff) | |
download | firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip |
Adding upstream version 115.7.0esr.upstream/115.7.0esrupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'toolkit/modules/tests/browser/browser_web_channel.js')
-rw-r--r-- | toolkit/modules/tests/browser/browser_web_channel.js | 587 |
1 files changed, 587 insertions, 0 deletions
diff --git a/toolkit/modules/tests/browser/browser_web_channel.js b/toolkit/modules/tests/browser/browser_web_channel.js new file mode 100644 index 0000000000..9dfa59485b --- /dev/null +++ b/toolkit/modules/tests/browser/browser_web_channel.js @@ -0,0 +1,587 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +ChromeUtils.defineESModuleGetters(this, { + WebChannel: "resource://gre/modules/WebChannel.sys.mjs", +}); + +const HTTP_PATH = "http://example.com"; +const HTTP_ENDPOINT = + getRootDirectory(gTestPath).replace("chrome://mochitests/content", "") + + "file_web_channel.html"; +const HTTP_MISMATCH_PATH = "http://example.org"; +const HTTP_IFRAME_PATH = "http://mochi.test:8888"; +const HTTP_REDIRECTED_IFRAME_PATH = "http://example.org"; + +requestLongerTimeout(2); // timeouts in debug builds. + +// Keep this synced with /mobile/android/tests/browser/robocop/testWebChannel.js +// as much as possible. (We only have that since we can't run browser chrome +// tests on Android. Yet?) +var gTests = [ + { + desc: "WebChannel generic message", + run() { + return new Promise(function (resolve, reject) { + let tab; + let channel = new WebChannel("generic", Services.io.newURI(HTTP_PATH)); + channel.listen(function (id, message, target) { + is(id, "generic"); + is(message.something.nested, "hello"); + channel.stopListening(); + gBrowser.removeTab(tab); + resolve(); + }); + + tab = BrowserTestUtils.addTab( + gBrowser, + HTTP_PATH + HTTP_ENDPOINT + "?generic" + ); + }); + }, + }, + { + desc: "WebChannel generic message in a private window.", + async run() { + let promiseTestDone = new Promise(function (resolve, reject) { + let channel = new WebChannel("generic", Services.io.newURI(HTTP_PATH)); + channel.listen(function (id, message, target) { + is(id, "generic"); + is(message.something.nested, "hello"); + channel.stopListening(); + resolve(); + }); + }); + + const url = HTTP_PATH + HTTP_ENDPOINT + "?generic"; + let privateWindow = await BrowserTestUtils.openNewBrowserWindow({ + private: true, + }); + await BrowserTestUtils.openNewForegroundTab(privateWindow.gBrowser, url); + await promiseTestDone; + await BrowserTestUtils.closeWindow(privateWindow); + }, + }, + { + desc: "WebChannel two way communication", + run() { + return new Promise(function (resolve, reject) { + let tab; + let channel = new WebChannel("twoway", Services.io.newURI(HTTP_PATH)); + + channel.listen(function (id, message, sender) { + is(id, "twoway", "bad id"); + ok(message.command, "command not ok"); + + if (message.command === "one") { + channel.send({ data: { nested: true } }, sender); + } + + if (message.command === "two") { + is(message.detail.data.nested, true); + channel.stopListening(); + gBrowser.removeTab(tab); + resolve(); + } + }); + + tab = BrowserTestUtils.addTab( + gBrowser, + HTTP_PATH + HTTP_ENDPOINT + "?twoway" + ); + }); + }, + }, + { + desc: "WebChannel two way communication in an iframe", + async run() { + let parentChannel = new WebChannel("echo", Services.io.newURI(HTTP_PATH)); + let iframeChannel = new WebChannel( + "twoway", + Services.io.newURI(HTTP_IFRAME_PATH) + ); + let promiseTestDone = new Promise(function (resolve, reject) { + parentChannel.listen(function (id, message, sender) { + reject(new Error("WebChannel message incorrectly sent to parent")); + }); + + iframeChannel.listen(function (id, message, sender) { + is(id, "twoway", "bad id (2)"); + ok(message.command, "command not ok (2)"); + + if (message.command === "one") { + iframeChannel.send({ data: { nested: true } }, sender); + } + + if (message.command === "two") { + is(message.detail.data.nested, true); + resolve(); + } + }); + }); + await BrowserTestUtils.withNewTab( + { + gBrowser, + url: HTTP_PATH + HTTP_ENDPOINT + "?iframe", + }, + async function () { + await promiseTestDone; + parentChannel.stopListening(); + iframeChannel.stopListening(); + } + ); + }, + }, + { + desc: "WebChannel response to a redirected iframe", + async run() { + /** + * This test checks that WebChannel responses are only sent + * to an iframe if the iframe has not redirected to another origin. + * Test flow: + * 1. create a page, embed an iframe on origin A. + * 2. the iframe sends a message `redirecting`, then redirects to + * origin B. + * 3. the iframe at origin B is set up to echo any messages back to the + * test parent. + * 4. the test parent receives the `redirecting` message from origin A. + * the test parent creates a new channel with origin B. + * 5. when origin B is ready, it sends a `loaded` message to the test + * parent, letting the test parent know origin B is ready to echo + * messages. + * 5. the test parent tries to send a response to origin A. If the + * WebChannel does not perform a valid origin check, the response + * will be received by origin B. If the WebChannel does perform + * a valid origin check, the response will not be sent. + * 6. the test parent sends a `done` message to origin B, which origin + * B echoes back. If the response to origin A is not echoed but + * the message to origin B is, then hooray, the test passes. + */ + + let preRedirectChannel = new WebChannel( + "pre_redirect", + Services.io.newURI(HTTP_IFRAME_PATH) + ); + let postRedirectChannel = new WebChannel( + "post_redirect", + Services.io.newURI(HTTP_REDIRECTED_IFRAME_PATH) + ); + + let promiseTestDone = new Promise(function (resolve, reject) { + preRedirectChannel.listen(function (id, message, preRedirectSender) { + if (message.command === "redirecting") { + postRedirectChannel.listen(function ( + aId, + aMessage, + aPostRedirectSender + ) { + is(aId, "post_redirect"); + isnot(aMessage.command, "no_response_expected"); + + if (aMessage.command === "loaded") { + // The message should not be received on the preRedirectChannel + // because the target window has redirected. + preRedirectChannel.send( + { command: "no_response_expected" }, + preRedirectSender + ); + postRedirectChannel.send( + { command: "done" }, + aPostRedirectSender + ); + } else if (aMessage.command === "done") { + resolve(); + } else { + reject(new Error(`Unexpected command ${aMessage.command}`)); + } + }); + } else { + reject(new Error(`Unexpected command ${message.command}`)); + } + }); + }); + + await BrowserTestUtils.withNewTab( + { + gBrowser, + url: HTTP_PATH + HTTP_ENDPOINT + "?iframe_pre_redirect", + }, + async function () { + await promiseTestDone; + preRedirectChannel.stopListening(); + postRedirectChannel.stopListening(); + } + ); + }, + }, + { + desc: "WebChannel multichannel", + run() { + return new Promise(function (resolve, reject) { + let tab; + let channel = new WebChannel( + "multichannel", + Services.io.newURI(HTTP_PATH) + ); + + channel.listen(function (id, message, sender) { + is(id, "multichannel"); + gBrowser.removeTab(tab); + resolve(); + }); + + tab = BrowserTestUtils.addTab( + gBrowser, + HTTP_PATH + HTTP_ENDPOINT + "?multichannel" + ); + }); + }, + }, + { + desc: "WebChannel unsolicited send, using system principal", + async run() { + let channel = new WebChannel("echo", Services.io.newURI(HTTP_PATH)); + + // an unsolicted message is sent from Chrome->Content which is then + // echoed back. If the echo is received here, then the content + // received the message. + let messagePromise = new Promise(function (resolve, reject) { + channel.listen(function (id, message, sender) { + is(id, "echo"); + is(message.command, "unsolicited"); + + resolve(); + }); + }); + + await BrowserTestUtils.withNewTab( + { + gBrowser, + url: HTTP_PATH + HTTP_ENDPOINT + "?unsolicited", + }, + async function (targetBrowser) { + channel.send( + { command: "unsolicited" }, + { + browsingContext: targetBrowser.browsingContext, + principal: Services.scriptSecurityManager.getSystemPrincipal(), + } + ); + await messagePromise; + channel.stopListening(); + } + ); + }, + }, + { + desc: "WebChannel unsolicited send, using target origin's principal", + async run() { + let targetURI = Services.io.newURI(HTTP_PATH); + let channel = new WebChannel("echo", targetURI); + + // an unsolicted message is sent from Chrome->Content which is then + // echoed back. If the echo is received here, then the content + // received the message. + let messagePromise = new Promise(function (resolve, reject) { + channel.listen(function (id, message, sender) { + is(id, "echo"); + is(message.command, "unsolicited"); + + resolve(); + }); + }); + + await BrowserTestUtils.withNewTab( + { + gBrowser, + url: HTTP_PATH + HTTP_ENDPOINT + "?unsolicited", + }, + async function (targetBrowser) { + channel.send( + { command: "unsolicited" }, + { + browsingContext: targetBrowser.browsingContext, + principal: Services.scriptSecurityManager.createContentPrincipal( + targetURI, + {} + ), + } + ); + + await messagePromise; + channel.stopListening(); + } + ); + }, + }, + { + desc: "WebChannel unsolicited send with principal mismatch", + async run() { + let targetURI = Services.io.newURI(HTTP_PATH); + let channel = new WebChannel("echo", targetURI); + + // two unsolicited messages are sent from Chrome->Content. The first, + // `unsolicited_no_response_expected` is sent to the wrong principal + // and should not be echoed back. The second, `done`, is sent to the + // correct principal and should be echoed back. + let messagePromise = new Promise(function (resolve, reject) { + channel.listen(function (id, message, sender) { + is(id, "echo"); + + if (message.command === "done") { + resolve(); + } else { + reject(new Error(`Unexpected command ${message.command}`)); + } + }); + }); + + await BrowserTestUtils.withNewTab( + { + gBrowser, + url: HTTP_PATH + HTTP_ENDPOINT + "?unsolicited", + }, + async function (targetBrowser) { + let mismatchURI = Services.io.newURI(HTTP_MISMATCH_PATH); + let mismatchPrincipal = + Services.scriptSecurityManager.createContentPrincipal( + mismatchURI, + {} + ); + + // send a message to the wrong principal. It should not be delivered + // to content, and should not be echoed back. + channel.send( + { command: "unsolicited_no_response_expected" }, + { + browsingContext: targetBrowser.browsingContext, + principal: mismatchPrincipal, + } + ); + + let targetPrincipal = + Services.scriptSecurityManager.createContentPrincipal( + targetURI, + {} + ); + + // send the `done` message to the correct principal. It + // should be echoed back. + channel.send( + { command: "done" }, + { + browsingContext: targetBrowser.browsingContext, + principal: targetPrincipal, + } + ); + + await messagePromise; + channel.stopListening(); + } + ); + }, + }, + { + desc: "WebChannel non-window target", + async run() { + /** + * This test ensures messages can be received from and responses + * sent to non-window elements. + * + * First wait for the non-window element to send a "start" message. + * Then send the non-window element a "done" message. + * The non-window element will echo the "done" message back, if it + * receives the message. + * Listen for the response. If received, good to go! + */ + let channel = new WebChannel( + "not_a_window", + Services.io.newURI(HTTP_PATH) + ); + + let testDonePromise = new Promise(function (resolve, reject) { + channel.listen(function (id, message, sender) { + if (message.command === "start") { + channel.send({ command: "done" }, sender); + } else if (message.command === "done") { + resolve(); + } else { + reject(new Error(`Unexpected command ${message.command}`)); + } + }); + }); + + await BrowserTestUtils.withNewTab( + { + gBrowser, + url: HTTP_PATH + HTTP_ENDPOINT + "?bubbles", + }, + async function () { + await testDonePromise; + channel.stopListening(); + } + ); + }, + }, + { + desc: "WebChannel disallows non-string message from non-whitelisted origin", + async run() { + /** + * This test ensures that non-string messages can't be sent via WebChannels. + * We create a page (on a non-whitelisted origin) which should send us two + * messages immediately. The first message has an object for it's detail, + * and the second has a string. We check that we only get the second + * message. + */ + let channel = new WebChannel("objects", Services.io.newURI(HTTP_PATH)); + let testDonePromise = new Promise((resolve, reject) => { + channel.listen((id, message, sender) => { + is(id, "objects"); + is(message.type, "string"); + resolve(); + }); + }); + await BrowserTestUtils.withNewTab( + { + gBrowser, + url: HTTP_PATH + HTTP_ENDPOINT + "?object", + }, + async function () { + await testDonePromise; + channel.stopListening(); + } + ); + }, + }, + { + desc: "WebChannel allows both string and non-string message from whitelisted origin", + async run() { + /** + * Same process as above, but we whitelist the origin before loading the page, + * and expect to get *both* messages back (each exactly once). + */ + let channel = new WebChannel("objects", Services.io.newURI(HTTP_PATH)); + + let testDonePromise = new Promise((resolve, reject) => { + let sawObject = false; + let sawString = false; + channel.listen((id, message, sender) => { + is(id, "objects"); + if (message.type === "object") { + ok(!sawObject); + sawObject = true; + } else if (message.type === "string") { + ok(!sawString); + sawString = true; + } else { + reject(new Error(`Unknown message type: ${message.type}`)); + } + if (sawObject && sawString) { + resolve(); + } + }); + }); + const webchannelWhitelistPref = "webchannel.allowObject.urlWhitelist"; + let origWhitelist = Services.prefs.getCharPref(webchannelWhitelistPref); + let newWhitelist = origWhitelist + " " + HTTP_PATH; + Services.prefs.setCharPref(webchannelWhitelistPref, newWhitelist); + await BrowserTestUtils.withNewTab( + { + gBrowser, + url: HTTP_PATH + HTTP_ENDPOINT + "?object", + }, + async function () { + await testDonePromise; + Services.prefs.setCharPref(webchannelWhitelistPref, origWhitelist); + channel.stopListening(); + } + ); + }, + }, + { + desc: "WebChannel errors handling the message are delivered back to content", + async run() { + const ERRNO_UNKNOWN_ERROR = 999; // WebChannel.sys.mjs doesn't export this. + + // The channel where we purposely fail responding to a command. + let channel = new WebChannel("error", Services.io.newURI(HTTP_PATH)); + // The channel where we see the response when the content sees the error + let echoChannel = new WebChannel("echo", Services.io.newURI(HTTP_PATH)); + + let testDonePromise = new Promise((resolve, reject) => { + // listen for the confirmation that content saw the error. + echoChannel.listen((id, message, sender) => { + is(id, "echo"); + is(message.error, "oh no"); + is(message.errno, ERRNO_UNKNOWN_ERROR); + resolve(); + }); + + // listen for a message telling us to simulate an error. + channel.listen((id, message, sender) => { + is(id, "error"); + is(message.command, "oops"); + throw new Error("oh no"); + }); + }); + await BrowserTestUtils.withNewTab( + { + gBrowser, + url: HTTP_PATH + HTTP_ENDPOINT + "?error_thrown", + }, + async function () { + await testDonePromise; + channel.stopListening(); + echoChannel.stopListening(); + } + ); + }, + }, + { + desc: "WebChannel errors due to an invalid channel are delivered back to content", + async run() { + const ERRNO_NO_SUCH_CHANNEL = 2; // WebChannel.sys.mjs doesn't export this. + // The channel where we see the response when the content sees the error + let echoChannel = new WebChannel("echo", Services.io.newURI(HTTP_PATH)); + + let testDonePromise = new Promise((resolve, reject) => { + // listen for the confirmation that content saw the error. + echoChannel.listen((id, message, sender) => { + is(id, "echo"); + is(message.error, "No Such Channel"); + is(message.errno, ERRNO_NO_SUCH_CHANNEL); + resolve(); + }); + }); + await BrowserTestUtils.withNewTab( + { + gBrowser, + url: HTTP_PATH + HTTP_ENDPOINT + "?error_invalid_channel", + }, + async function () { + await testDonePromise; + echoChannel.stopListening(); + } + ); + }, + }, +]; // gTests + +function test() { + waitForExplicitFinish(); + + (async function () { + await SpecialPowers.pushPrefEnv({ + set: [["dom.security.https_first_pbm", false]], + }); + + for (let testCase of gTests) { + info("Running: " + testCase.desc); + await testCase.run(); + } + })().then(finish, ex => { + ok(false, "Unexpected Exception: " + ex); + finish(); + }); +} |