/* -*- 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 => { 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": ``, }, }); 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": ``, }, }); 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(() => {}); 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": "", "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": "", "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(); });