diff options
Diffstat (limited to 'toolkit/components/extensions/test/xpcshell/test_ext_runtime_sendMessage.js')
-rw-r--r-- | toolkit/components/extensions/test/xpcshell/test_ext_runtime_sendMessage.js | 462 |
1 files changed, 462 insertions, 0 deletions
diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_runtime_sendMessage.js b/toolkit/components/extensions/test/xpcshell/test_ext_runtime_sendMessage.js new file mode 100644 index 0000000000..2bbc9864d7 --- /dev/null +++ b/toolkit/components/extensions/test/xpcshell/test_ext_runtime_sendMessage.js @@ -0,0 +1,462 @@ +/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set sts=2 sw=2 et tw=80: */ +"use strict"; + +const server = createHttpServer(); +server.registerDirectory("/data/", do_get_file("data")); +const BASE_URL = `http://localhost:${server.identity.primaryPort}/data`; + +add_task(async function runtimeSendMessageReply() { + function background() { + browser.runtime.onMessage.addListener((msg, sender, respond) => { + if (msg == "respond-now") { + respond(msg); + } else if (msg == "respond-soon") { + setTimeout(() => { + respond(msg); + }, 0); + return true; + } else if (msg == "respond-promise") { + return Promise.resolve(msg); + } else if (msg == "respond-promise-false") { + return Promise.resolve(false); + } else if (msg == "respond-false") { + // return false means that respond() is not expected to be called. + setTimeout(() => respond("should be ignored")); + return false; + } else if (msg == "respond-never") { + return undefined; + } else if (msg == "respond-error") { + return Promise.reject(new Error(msg)); + } else if (msg == "throw-error") { + throw new Error(msg); + } else if (msg === "respond-uncloneable") { + return Promise.resolve(window); + } else if (msg === "reject-uncloneable") { + return Promise.reject(window); + } else if (msg == "reject-undefined") { + return Promise.reject(); + } else if (msg == "throw-undefined") { + throw undefined; // eslint-disable-line no-throw-literal + } + }); + + browser.runtime.onMessage.addListener((msg, sender, respond) => { + if (msg == "respond-now") { + respond("hello"); + } else if (msg == "respond-now-2") { + respond(msg); + } + }); + + browser.runtime.onMessage.addListener((msg, sender, respond) => { + if (msg == "respond-now") { + // If a response from another listener is received first, this + // exception should be ignored. Test fails if it is not. + + // All this is of course stupid, but some extensions depend on it. + msg.blah.this.throws(); + } + }); + + let childFrame = document.createElement("iframe"); + childFrame.src = "extensionpage.html"; + document.body.appendChild(childFrame); + } + + function senderScript() { + Promise.all([ + browser.runtime.sendMessage("respond-now"), + browser.runtime.sendMessage("respond-now-2"), + new Promise(resolve => + browser.runtime.sendMessage("respond-soon", resolve) + ), + browser.runtime.sendMessage("respond-promise"), + browser.runtime.sendMessage("respond-promise-false"), + browser.runtime.sendMessage("respond-false"), + browser.runtime.sendMessage("respond-never"), + new Promise(resolve => { + browser.runtime.sendMessage("respond-never", response => { + resolve(response); + }); + }), + + browser.runtime + .sendMessage("respond-error") + .catch(error => Promise.resolve({ error })), + browser.runtime + .sendMessage("throw-error") + .catch(error => Promise.resolve({ error })), + + browser.runtime + .sendMessage("respond-uncloneable") + .catch(error => Promise.resolve({ error })), + browser.runtime + .sendMessage("reject-uncloneable") + .catch(error => Promise.resolve({ error })), + browser.runtime + .sendMessage("reject-undefined") + .catch(error => Promise.resolve({ error })), + browser.runtime + .sendMessage("throw-undefined") + .catch(error => Promise.resolve({ error })), + ]) + .then( + ([ + respondNow, + respondNow2, + respondSoon, + respondPromise, + respondPromiseFalse, + respondFalse, + respondNever, + respondNever2, + respondError, + throwError, + respondUncloneable, + rejectUncloneable, + rejectUndefined, + throwUndefined, + ]) => { + browser.test.assertEq( + "respond-now", + respondNow, + "Got the expected immediate response" + ); + browser.test.assertEq( + "respond-now-2", + respondNow2, + "Got the expected immediate response from the second listener" + ); + browser.test.assertEq( + "respond-soon", + respondSoon, + "Got the expected delayed response" + ); + browser.test.assertEq( + "respond-promise", + respondPromise, + "Got the expected promise response" + ); + browser.test.assertEq( + false, + respondPromiseFalse, + "Got the expected false value as a promise result" + ); + browser.test.assertEq( + undefined, + respondFalse, + "Got the expected no-response when onMessage returns false" + ); + browser.test.assertEq( + undefined, + respondNever, + "Got the expected no-response resolution" + ); + browser.test.assertEq( + undefined, + respondNever2, + "Got the expected no-response resolution" + ); + + browser.test.assertEq( + "respond-error", + respondError.error.message, + "Got the expected error response" + ); + browser.test.assertEq( + "throw-error", + throwError.error.message, + "Got the expected thrown error response" + ); + + browser.test.assertEq( + "Could not establish connection. Receiving end does not exist.", + respondUncloneable.error.message, + "An uncloneable response should be ignored" + ); + browser.test.assertEq( + "An unexpected error occurred", + rejectUncloneable.error.message, + "Got the expected error for a rejection with an uncloneable value" + ); + browser.test.assertEq( + "An unexpected error occurred", + rejectUndefined.error.message, + "Got the expected error for a void rejection" + ); + browser.test.assertEq( + "An unexpected error occurred", + throwUndefined.error.message, + "Got the expected error for a void throw" + ); + + browser.test.notifyPass("sendMessage"); + } + ) + .catch(e => { + browser.test.fail(`Error: ${e} :: ${e.stack}`); + browser.test.notifyFail("sendMessage"); + }); + } + + let extension = ExtensionTestUtils.loadExtension({ + background, + files: { + "senderScript.js": senderScript, + "extensionpage.html": `<!DOCTYPE html><meta charset="utf-8"><script src="senderScript.js"></script>`, + }, + }); + + await extension.startup(); + await extension.awaitFinish("sendMessage"); + await extension.unload(); +}); + +add_task(async function runtimeSendMessageBlob() { + function background() { + browser.runtime.onMessage.addListener(msg => { + // eslint-disable-next-line mozilla/use-isInstance -- this function runs in an extension + browser.test.assertTrue(msg.blob instanceof Blob, "Message is a blob"); + return Promise.resolve(msg); + }); + + let childFrame = document.createElement("iframe"); + childFrame.src = "extensionpage.html"; + document.body.appendChild(childFrame); + } + + function senderScript() { + browser.runtime + .sendMessage({ blob: new Blob(["hello"]) }) + .then(response => { + browser.test.assertTrue( + // eslint-disable-next-line mozilla/use-isInstance -- this function runs in an extension + response.blob instanceof Blob, + "Response is a blob" + ); + browser.test.notifyPass("sendBlob"); + }); + } + + let extension = ExtensionTestUtils.loadExtension({ + background, + files: { + "senderScript.js": senderScript, + "extensionpage.html": `<!DOCTYPE html><meta charset="utf-8"><script src="senderScript.js"></script>`, + }, + }); + + await extension.startup(); + await extension.awaitFinish("sendBlob"); + await extension.unload(); +}); + +add_task(async function sendMessageResponseGC() { + function background() { + let savedResolve, savedRespond; + + browser.runtime.onMessage.addListener((msg, _, respond) => { + browser.test.log(`Got request: ${msg}`); + switch (msg) { + case "ping": + respond("pong"); + return; + + case "promise-save": + return new Promise(resolve => { + savedResolve = resolve; + }); + case "promise-resolve": + savedResolve("saved-resolve"); + return; + case "promise-never": + return new Promise(r => {}); + + case "callback-save": + savedRespond = respond; + return true; + case "callback-call": + savedRespond("saved-respond"); + return; + case "callback-never": + return true; + } + }); + + const frame = document.createElement("iframe"); + frame.src = "page.html"; + document.body.appendChild(frame); + } + + function page() { + browser.test.onMessage.addListener(msg => { + browser.runtime.sendMessage(msg).then( + response => { + if (response) { + browser.test.log(`Got response: ${response}`); + browser.test.sendMessage(response); + } + }, + error => { + browser.test.assertEq( + "Promised response from onMessage listener went out of scope", + error.message, + `Promise rejected with the correct error message` + ); + + browser.test.assertTrue( + /^moz-extension:\/\/[\w-]+\/%7B[\w-]+%7D\.js/.test(error.fileName), + `Promise rejected with the correct error filename: ${error.fileName}` + ); + + browser.test.assertEq( + 4, + error.lineNumber, + `Promise rejected with the correct error line number` + ); + + browser.test.assertTrue( + /moz-extension:\/\/[\w-]+\/%7B[\w-]+%7D\.js:4/.test(error.stack), + `Promise rejected with the correct error stack: ${error.stack}` + ); + browser.test.sendMessage("rejected"); + } + ); + }); + browser.test.sendMessage("ready"); + } + + let extension = ExtensionTestUtils.loadExtension({ + background, + files: { + "page.html": + "<!DOCTYPE html><meta charset=utf-8><script src=page.js></script>", + "page.js": page, + }, + }); + + await extension.startup(); + await extension.awaitMessage("ready"); + + // Setup long-running tasks before GC. + extension.sendMessage("promise-save"); + extension.sendMessage("callback-save"); + + // Test returning a Promise that can never resolve. + extension.sendMessage("promise-never"); + + extension.sendMessage("ping"); + await extension.awaitMessage("pong"); + + Services.prefs.setBoolPref( + "security.allow_parent_unrestricted_js_loads", + true + ); + Services.ppmm.loadProcessScript("data:,Components.utils.forceGC()", false); + await extension.awaitMessage("rejected"); + + // Test returning `true` without holding the response handle. + extension.sendMessage("callback-never"); + + extension.sendMessage("ping"); + await extension.awaitMessage("pong"); + + Services.ppmm.loadProcessScript("data:,Components.utils.forceGC()", false); + Services.prefs.setBoolPref( + "security.allow_parent_unrestricted_js_loads", + false + ); + await extension.awaitMessage("rejected"); + + // Test that promises from long-running tasks didn't get GCd. + extension.sendMessage("promise-resolve"); + await extension.awaitMessage("saved-resolve"); + + extension.sendMessage("callback-call"); + await extension.awaitMessage("saved-respond"); + + ok("Long running tasks responded"); + await extension.unload(); +}); + +add_task(async function sendMessage_async_response_multiple_contexts() { + let extension = ExtensionTestUtils.loadExtension({ + background() { + browser.runtime.onMessage.addListener((msg, _, respond) => { + browser.test.log(`Background got request: ${msg}`); + + switch (msg) { + case "ask-bg-fast": + respond("bg-respond"); + return true; + + case "ask-bg-slow": + return new Promise(r => setTimeout(() => r("bg-promise")), 1000); + } + }); + browser.test.sendMessage("bg-ready"); + }, + + manifest: { + content_scripts: [ + { + matches: ["http://localhost/*/file_sample.html"], + js: ["cs.js"], + }, + ], + }, + + files: { + "page.html": + "<!DOCTYPE html><meta charset=utf-8><script src=page.js></script>", + "page.js"() { + browser.runtime.onMessage.addListener((msg, _, respond) => { + browser.test.log(`Page got request: ${msg}`); + + switch (msg) { + case "ask-page-fast": + respond("page-respond"); + return true; + + case "ask-page-slow": + return new Promise(r => setTimeout(() => r("page-promise")), 500); + } + }); + browser.test.sendMessage("page-ready"); + }, + + "cs.js"() { + Promise.all([ + browser.runtime.sendMessage("ask-bg-fast"), + browser.runtime.sendMessage("ask-bg-slow"), + browser.runtime.sendMessage("ask-page-fast"), + browser.runtime.sendMessage("ask-page-slow"), + ]).then(responses => { + browser.test.assertEq( + responses.join(), + ["bg-respond", "bg-promise", "page-respond", "page-promise"].join(), + "Got all expected responses from correct contexts" + ); + browser.test.notifyPass("cs-done"); + }); + }, + }, + }); + + await extension.startup(); + await extension.awaitMessage("bg-ready"); + + let url = `moz-extension://${extension.uuid}/page.html`; + let page = await ExtensionTestUtils.loadContentPage(url, { extension }); + await extension.awaitMessage("page-ready"); + + let content = await ExtensionTestUtils.loadContentPage( + BASE_URL + "/file_sample.html" + ); + await extension.awaitFinish("cs-done"); + await content.close(); + + await page.close(); + await extension.unload(); +}); |