summaryrefslogtreecommitdiffstats
path: root/toolkit/components/remotepagemanager/tests/browser/browser_RemotePageManager.js
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/components/remotepagemanager/tests/browser/browser_RemotePageManager.js')
-rw-r--r--toolkit/components/remotepagemanager/tests/browser/browser_RemotePageManager.js570
1 files changed, 570 insertions, 0 deletions
diff --git a/toolkit/components/remotepagemanager/tests/browser/browser_RemotePageManager.js b/toolkit/components/remotepagemanager/tests/browser/browser_RemotePageManager.js
new file mode 100644
index 0000000000..ac0f3cf83d
--- /dev/null
+++ b/toolkit/components/remotepagemanager/tests/browser/browser_RemotePageManager.js
@@ -0,0 +1,570 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+const TEST_URL =
+ "http://www.example.com/browser/toolkit/components/remotepagemanager/tests/browser/testremotepagemanager.html";
+
+var { RemotePages, RemotePageManager } = ChromeUtils.import(
+ "resource://gre/modules/remotepagemanager/RemotePageManagerParent.jsm"
+);
+
+function failOnMessage(message) {
+ ok(false, "Should not have seen message " + message.name);
+}
+
+function waitForMessage(port, message, expectedPort = port) {
+ return new Promise(resolve => {
+ function listener(message) {
+ is(
+ message.target,
+ expectedPort,
+ "Message should be from the right port."
+ );
+
+ port.removeMessageListener(listener);
+ resolve(message);
+ }
+
+ port.addMessageListener(message, listener);
+ });
+}
+
+function waitForPort(url, createTab = true) {
+ return new Promise(resolve => {
+ RemotePageManager.addRemotePageListener(url, port => {
+ RemotePageManager.removeRemotePageListener(url);
+
+ waitForMessage(port, "RemotePage:Load").then(() => resolve(port));
+ });
+
+ if (createTab) {
+ gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser, url);
+ }
+ });
+}
+
+function waitForPage(pages, url = TEST_URL) {
+ return new Promise(resolve => {
+ function listener({ target }) {
+ pages.removeMessageListener("RemotePage:Init", listener);
+
+ waitForMessage(target, "RemotePage:Load").then(() => resolve(target));
+ }
+
+ pages.addMessageListener("RemotePage:Init", listener);
+ gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser, url);
+ });
+}
+
+function swapDocShells(browser1, browser2) {
+ // Swap frameLoaders.
+ browser1.swapDocShells(browser2);
+
+ // Swap permanentKeys.
+ let tmp = browser1.permanentKey;
+ browser1.permanentKey = browser2.permanentKey;
+ browser2.permanentKey = tmp;
+}
+
+add_task(async function sharedData_aka_initialProcessData() {
+ const includesTest = () =>
+ Services.cpmm.sharedData.get("RemotePageManager:urls").has(TEST_URL);
+ is(
+ includesTest(),
+ false,
+ "Shouldn't have test url in initial process data yet"
+ );
+
+ const loadedPort = waitForPort(TEST_URL);
+ is(includesTest(), true, "Should have test url when waiting for it to load");
+
+ await loadedPort;
+ is(includesTest(), false, "Should have test url removed when done listening");
+
+ gBrowser.removeCurrentTab();
+});
+
+// Test that opening a page creates a port, sends the load event and then
+// navigating to a new page sends the unload event. Going back should create a
+// new port
+add_task(async function init_navigate() {
+ let port = await waitForPort(TEST_URL);
+ is(port.browser, gBrowser.selectedBrowser, "Port is for the correct browser");
+
+ let loaded = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
+ BrowserTestUtils.loadURI(gBrowser, "about:blank");
+
+ await waitForMessage(port, "RemotePage:Unload");
+
+ // Port should be destroyed now
+ try {
+ port.addMessageListener("Foo", failOnMessage);
+ ok(false, "Should have seen exception");
+ } catch (e) {
+ ok(true, "Should have seen exception");
+ }
+
+ try {
+ port.sendAsyncMessage("Foo");
+ ok(false, "Should have seen exception");
+ } catch (e) {
+ ok(true, "Should have seen exception");
+ }
+
+ await loaded;
+
+ gBrowser.goBack();
+ port = await waitForPort(TEST_URL, false);
+
+ port.sendAsyncMessage("Ping2");
+ await waitForMessage(port, "Pong2");
+ port.destroy();
+
+ gBrowser.removeCurrentTab();
+});
+
+// Test that opening a page creates a port, sends the load event and then
+// closing the tab sends the unload event
+add_task(async function init_close() {
+ let port = await waitForPort(TEST_URL);
+ is(port.browser, gBrowser.selectedBrowser, "Port is for the correct browser");
+
+ let unloadPromise = waitForMessage(port, "RemotePage:Unload");
+ gBrowser.removeCurrentTab();
+ await unloadPromise;
+
+ // Port should be destroyed now
+ try {
+ port.addMessageListener("Foo", failOnMessage);
+ ok(false, "Should have seen exception");
+ } catch (e) {
+ ok(true, "Should have seen exception");
+ }
+
+ try {
+ port.sendAsyncMessage("Foo");
+ ok(false, "Should have seen exception");
+ } catch (e) {
+ ok(true, "Should have seen exception");
+ }
+});
+
+// Tests that we can send messages to individual pages even when more than one
+// is open
+add_task(async function multiple_ports() {
+ let port1 = await waitForPort(TEST_URL);
+ is(
+ port1.browser,
+ gBrowser.selectedBrowser,
+ "Port is for the correct browser"
+ );
+
+ let port2 = await waitForPort(TEST_URL);
+ is(
+ port2.browser,
+ gBrowser.selectedBrowser,
+ "Port is for the correct browser"
+ );
+
+ port2.addMessageListener("Pong", failOnMessage);
+ port1.sendAsyncMessage("Ping", { str: "foobar", counter: 0 });
+ let message = await waitForMessage(port1, "Pong");
+ port2.removeMessageListener("Pong", failOnMessage);
+ is(message.data.str, "foobar", "String should pass through");
+ is(message.data.counter, 1, "Counter should be incremented");
+
+ port1.addMessageListener("Pong", failOnMessage);
+ port2.sendAsyncMessage("Ping", { str: "foobaz", counter: 5 });
+ message = await waitForMessage(port2, "Pong");
+ port1.removeMessageListener("Pong", failOnMessage);
+ is(message.data.str, "foobaz", "String should pass through");
+ is(message.data.counter, 6, "Counter should be incremented");
+
+ let unloadPromise = waitForMessage(port2, "RemotePage:Unload");
+ gBrowser.removeTab(gBrowser.getTabForBrowser(port2.browser));
+ await unloadPromise;
+
+ try {
+ port2.addMessageListener("Pong", failOnMessage);
+ ok(
+ false,
+ "Should not have been able to add a new message listener to a destroyed port."
+ );
+ } catch (e) {
+ ok(
+ true,
+ "Should not have been able to add a new message listener to a destroyed port."
+ );
+ }
+
+ port1.sendAsyncMessage("Ping", { str: "foobar", counter: 0 });
+ message = await waitForMessage(port1, "Pong");
+ is(message.data.str, "foobar", "String should pass through");
+ is(message.data.counter, 1, "Counter should be incremented");
+
+ unloadPromise = waitForMessage(port1, "RemotePage:Unload");
+ gBrowser.removeTab(gBrowser.getTabForBrowser(port1.browser));
+ await unloadPromise;
+});
+
+// Tests that swapping browser docshells doesn't break the ports
+add_task(async function browser_switch() {
+ let port1 = await waitForPort(TEST_URL);
+ is(
+ port1.browser,
+ gBrowser.selectedBrowser,
+ "Port is for the correct browser"
+ );
+ let browser1 = gBrowser.selectedBrowser;
+ port1.sendAsyncMessage("SetCookie", { value: "om nom" });
+
+ let port2 = await waitForPort(TEST_URL);
+ is(
+ port2.browser,
+ gBrowser.selectedBrowser,
+ "Port is for the correct browser"
+ );
+ let browser2 = gBrowser.selectedBrowser;
+ port2.sendAsyncMessage("SetCookie", { value: "om nom nom" });
+
+ port2.addMessageListener("Cookie", failOnMessage);
+ port1.sendAsyncMessage("GetCookie");
+ let message = await waitForMessage(port1, "Cookie");
+ port2.removeMessageListener("Cookie", failOnMessage);
+ is(message.data.value, "om nom", "Should have the right cookie");
+
+ port1.addMessageListener("Cookie", failOnMessage);
+ port2.sendAsyncMessage("GetCookie", { str: "foobaz", counter: 5 });
+ message = await waitForMessage(port2, "Cookie");
+ port1.removeMessageListener("Cookie", failOnMessage);
+ is(message.data.value, "om nom nom", "Should have the right cookie");
+
+ swapDocShells(browser1, browser2);
+ is(port1.browser, browser2, "Should have noticed the swap");
+ is(port2.browser, browser1, "Should have noticed the swap");
+
+ // Cookies should have stayed the same
+ port2.addMessageListener("Cookie", failOnMessage);
+ port1.sendAsyncMessage("GetCookie");
+ message = await waitForMessage(port1, "Cookie");
+ port2.removeMessageListener("Cookie", failOnMessage);
+ is(message.data.value, "om nom", "Should have the right cookie");
+
+ port1.addMessageListener("Cookie", failOnMessage);
+ port2.sendAsyncMessage("GetCookie", { str: "foobaz", counter: 5 });
+ message = await waitForMessage(port2, "Cookie");
+ port1.removeMessageListener("Cookie", failOnMessage);
+ is(message.data.value, "om nom nom", "Should have the right cookie");
+
+ swapDocShells(browser1, browser2);
+ is(port1.browser, browser1, "Should have noticed the swap");
+ is(port2.browser, browser2, "Should have noticed the swap");
+
+ // Cookies should have stayed the same
+ port2.addMessageListener("Cookie", failOnMessage);
+ port1.sendAsyncMessage("GetCookie");
+ message = await waitForMessage(port1, "Cookie");
+ port2.removeMessageListener("Cookie", failOnMessage);
+ is(message.data.value, "om nom", "Should have the right cookie");
+
+ port1.addMessageListener("Cookie", failOnMessage);
+ port2.sendAsyncMessage("GetCookie", { str: "foobaz", counter: 5 });
+ message = await waitForMessage(port2, "Cookie");
+ port1.removeMessageListener("Cookie", failOnMessage);
+ is(message.data.value, "om nom nom", "Should have the right cookie");
+
+ let unloadPromise = waitForMessage(port2, "RemotePage:Unload");
+ gBrowser.removeTab(gBrowser.getTabForBrowser(browser2));
+ await unloadPromise;
+
+ unloadPromise = waitForMessage(port1, "RemotePage:Unload");
+ gBrowser.removeTab(gBrowser.getTabForBrowser(browser1));
+ await unloadPromise;
+});
+
+// Tests that removeMessageListener in chrome works
+add_task(async function remove_chrome_listener() {
+ let port = await waitForPort(TEST_URL);
+ is(port.browser, gBrowser.selectedBrowser, "Port is for the correct browser");
+
+ // This relies on messages sent arriving in the same order. Pong will be
+ // sent back before Pong2 so if removeMessageListener fails the test will fail
+ port.addMessageListener("Pong", failOnMessage);
+ port.removeMessageListener("Pong", failOnMessage);
+ port.sendAsyncMessage("Ping", { str: "remove_listener", counter: 27 });
+ port.sendAsyncMessage("Ping2");
+ await waitForMessage(port, "Pong2");
+
+ let unloadPromise = waitForMessage(port, "RemotePage:Unload");
+ gBrowser.removeCurrentTab();
+ await unloadPromise;
+});
+
+// Tests that removeMessageListener in content works
+add_task(async function remove_content_listener() {
+ let port = await waitForPort(TEST_URL);
+ is(port.browser, gBrowser.selectedBrowser, "Port is for the correct browser");
+
+ // This relies on messages sent arriving in the same order. Pong3 would be
+ // sent back before Pong2 so if removeMessageListener fails the test will fail
+ port.addMessageListener("Pong3", failOnMessage);
+ port.sendAsyncMessage("Ping3");
+ port.sendAsyncMessage("Ping2");
+ await waitForMessage(port, "Pong2");
+
+ let unloadPromise = waitForMessage(port, "RemotePage:Unload");
+ gBrowser.removeCurrentTab();
+ await unloadPromise;
+});
+
+// Test RemotePages works
+add_task(async function remote_pages_basic() {
+ let pages = new RemotePages(TEST_URL);
+ let port = await waitForPage(pages);
+ is(port.browser, gBrowser.selectedBrowser, "Port is for the correct browser");
+
+ // Listening to global messages should work
+ let unloadPromise = waitForMessage(pages, "RemotePage:Unload", port);
+ gBrowser.removeCurrentTab();
+ await unloadPromise;
+
+ pages.destroy();
+
+ // RemotePages should be destroyed now
+ try {
+ pages.addMessageListener("Foo", failOnMessage);
+ ok(false, "Should have seen exception");
+ } catch (e) {
+ ok(true, "Should have seen exception");
+ }
+
+ try {
+ pages.sendAsyncMessage("Foo");
+ ok(false, "Should have seen exception");
+ } catch (e) {
+ ok(true, "Should have seen exception");
+ }
+});
+
+// Test that properties exist on the target port provided to listeners
+add_task(async function check_port_properties() {
+ let pages = new RemotePages(TEST_URL);
+
+ const expectedProperties = [
+ "addMessageListener",
+ "browser",
+ "destroy",
+ "loaded",
+ "portID",
+ "removeMessageListener",
+ "sendAsyncMessage",
+ "url",
+ ];
+ function checkProperties(port, description) {
+ const expected = [];
+ const unexpected = [];
+ for (const key in port) {
+ (expectedProperties.includes(key) ? expected : unexpected).push(key);
+ }
+ is(
+ `${expected.sort()}`,
+ `${expectedProperties}`,
+ `${description} has expected keys`
+ );
+ is(
+ `${unexpected.sort()}`,
+ "",
+ `${description} should not have unexpected keys`
+ );
+ }
+
+ function portFrom(message, extraFn = () => {}) {
+ return new Promise(resolve => {
+ function onMessage({ target }) {
+ pages.removeMessageListener(message, onMessage);
+ resolve(target);
+ }
+ pages.addMessageListener(message, onMessage);
+ extraFn();
+ });
+ }
+
+ let portFromInit = await portFrom(
+ "RemotePage:Init",
+ () => (gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser, TEST_URL))
+ );
+ checkProperties(portFromInit, "inited port");
+ ok(
+ ["about:blank", TEST_URL].includes(portFromInit.browser.currentURI.spec),
+ `inited port browser is either still blank or already at the target url - got ${portFromInit.browser.currentURI.spec}`
+ );
+ is(portFromInit.loaded, false, "inited port has not been loaded yet");
+ is(portFromInit.url, TEST_URL, "got expected url");
+
+ let portFromLoad = await portFrom("RemotePage:Load");
+ is(portFromLoad, portFromInit, "got the same port from init and load");
+ checkProperties(portFromLoad, "loaded port");
+ is(
+ portFromInit.browser.currentURI.spec,
+ TEST_URL,
+ "loaded port has browser with actual url"
+ );
+ is(portFromInit.loaded, true, "loaded port is now loaded");
+ is(portFromInit.url, TEST_URL, "still got expected url");
+
+ let portFromUnload = await portFrom("RemotePage:Unload", () =>
+ BrowserTestUtils.removeTab(gBrowser.selectedTab)
+ );
+ is(portFromUnload, portFromInit, "got the same port from init and unload");
+ checkProperties(portFromUnload, "unloaded port");
+ is(portFromInit.browser, null, "unloaded port has no browser");
+ is(portFromInit.loaded, false, "unloaded port is now not loaded");
+ is(portFromInit.url, TEST_URL, "still got expected url");
+
+ pages.destroy();
+});
+
+// Test sending messages to all remote pages works
+add_task(async function remote_pages_multiple_pages() {
+ let pages = new RemotePages(TEST_URL);
+ let port1 = await waitForPage(pages);
+ let port2 = await waitForPage(pages);
+
+ let pongPorts = [];
+ await new Promise(resolve => {
+ function listener({ name, target, data }) {
+ is(name, "Pong", "Should have seen the right response.");
+ is(data.str, "remote_pages", "String should pass through");
+ is(data.counter, 43, "Counter should be incremented");
+ pongPorts.push(target);
+ if (pongPorts.length == 2) {
+ resolve();
+ }
+ }
+
+ pages.addMessageListener("Pong", listener);
+ pages.sendAsyncMessage("Ping", { str: "remote_pages", counter: 42 });
+ });
+
+ // We don't make any guarantees about which order messages are sent to known
+ // pages so the pongs could have come back in any order.
+ isnot(
+ pongPorts[0],
+ pongPorts[1],
+ "Should have received pongs from different ports"
+ );
+ ok(pongPorts.includes(port1), "Should have seen a pong from port1");
+ ok(pongPorts.includes(port2), "Should have seen a pong from port2");
+
+ // After destroy we should see no messages
+ pages.addMessageListener("RemotePage:Unload", failOnMessage);
+ pages.destroy();
+
+ gBrowser.removeTab(gBrowser.getTabForBrowser(port1.browser));
+ gBrowser.removeTab(gBrowser.getTabForBrowser(port2.browser));
+});
+
+// Test that RemotePages with multiple urls works
+add_task(async function remote_pages_multiple_urls() {
+ const TEST_URLS = [TEST_URL, TEST_URL.replace(".html", "2.html")];
+ const pages = new RemotePages(TEST_URLS);
+
+ const ports = [];
+ // Load two pages for each url
+ for (const [i, url] of TEST_URLS.entries()) {
+ const port = await waitForPage(pages, url);
+ is(
+ port.browser,
+ gBrowser.selectedBrowser,
+ `port${i} is for the correct browser`
+ );
+ ports.push(port);
+ ports.push(await waitForPage(pages, url));
+ }
+
+ let unloadPromise = waitForMessage(pages, "RemotePage:Unload", ports.pop());
+ gBrowser.removeCurrentTab();
+ await unloadPromise;
+
+ const pongPorts = new Set();
+ await new Promise(resolve => {
+ function listener({ name, target, data }) {
+ is(name, "Pong", "Should have seen the right response.");
+ is(data.str, "FAKE_DATA", "String should pass through");
+ is(data.counter, 1235, "Counter should be incremented");
+ pongPorts.add(target);
+ if (pongPorts.size === ports.length) {
+ resolve();
+ }
+ }
+
+ pages.addMessageListener("Pong", listener);
+ pages.sendAsyncMessage("Ping", { str: "FAKE_DATA", counter: 1234 });
+ });
+
+ ports.forEach(port => ok(pongPorts.has(port)));
+
+ pages.destroy();
+ ports.forEach(port =>
+ gBrowser.removeTab(gBrowser.getTabForBrowser(port.browser))
+ );
+});
+
+// Test sending various types of data across the boundary
+add_task(async function send_data() {
+ let port = await waitForPort(TEST_URL);
+ is(port.browser, gBrowser.selectedBrowser, "Port is for the correct browser");
+
+ let data = {
+ integer: 45,
+ real: 45.78,
+ str: "foobar",
+ array: [1, 2, 3, 5, 27],
+ };
+
+ port.sendAsyncMessage("SendData", data);
+ let message = await waitForMessage(port, "ReceivedData");
+
+ ok(message.data.result, message.data.status);
+
+ gBrowser.removeCurrentTab();
+});
+
+// Test sending an object of data across the boundary
+add_task(async function send_data2() {
+ let port = await waitForPort(TEST_URL);
+ is(port.browser, gBrowser.selectedBrowser, "Port is for the correct browser");
+
+ let data = {
+ integer: 45,
+ real: 45.78,
+ str: "foobar",
+ array: [1, 2, 3, 5, 27],
+ };
+
+ port.sendAsyncMessage("SendData2", { data });
+ let message = await waitForMessage(port, "ReceivedData2");
+
+ ok(message.data.result, message.data.status);
+
+ gBrowser.removeCurrentTab();
+});
+
+add_task(async function get_ports_for_browser() {
+ let pages = new RemotePages(TEST_URL);
+ let port = await waitForPage(pages);
+ // waitForPage creates a new tab and selects it by default, so
+ // the selected tab should be the one hosting this port.
+ let browser = gBrowser.selectedBrowser;
+ let foundPorts = pages.portsForBrowser(browser);
+ is(
+ foundPorts.length,
+ 1,
+ "There should only be one port for this simple page"
+ );
+ is(foundPorts[0], port, "Should find the port");
+
+ pages.destroy();
+ gBrowser.removeCurrentTab();
+});