diff options
Diffstat (limited to 'toolkit/components/remotepagemanager/tests/browser/browser_RemotePageManager.js')
-rw-r--r-- | toolkit/components/remotepagemanager/tests/browser/browser_RemotePageManager.js | 570 |
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(); +}); |