Bug 1295473 - Fix return type of {tabs,runtime}.sendMessage r=billm

The tabs.sendMessage and runtime.sendMessage implementations behave like
an async function: They take a callback parameter and return a promise.
So they should be handled by |callAsyncFunction|, not
|callFunctionNoReturn|.

This fixes the issue for background pages, but not for content scripts
because sendMessage is not implemented as a schema at the moment. This
will also be fixed once content script APIs are generated via Schemas.

MozReview-Commit-ID: 9p1hvOP0KSm

--HG--
extra : rebase_source : 7fc804e52184d59cc1dae2f299c644ed13d8a3c7
This commit is contained in:
Rob Wu 2016-08-15 23:53:24 -07:00
Родитель 66098b3d43
Коммит 0077f92ec3
6 изменённых файлов: 128 добавлений и 2 удалений

Просмотреть файл

@ -255,6 +255,7 @@
"name": "sendMessage",
"type": "function",
"description": "Sends a single message to the content script(s) in the specified tab, with an optional callback to run when a response is sent back. The $(ref:runtime.onMessage) event is fired in each content script running in the specified tab for the current extension.",
"async": "sendResponse",
"parameters": [
{
"type": "integer",

Просмотреть файл

@ -1122,6 +1122,11 @@ class CallEntry extends Entry {
if (this.allowAmbiguousOptionalArguments) {
// When this option is set, it's up to the implementation to
// parse arguments.
// The last argument for asynchronous methods is either a function or null.
// This is specifically done for runtime.sendMessage.
if (this.hasAsyncCallback && typeof(args[args.length - 1]) != "function") {
args.push(null);
}
return args;
}
let success = check(0, 0);
@ -1430,8 +1435,11 @@ this.Schemas = {
if (parameters && parameters.length && parameters[parameters.length - 1].name == type.async) {
hasAsyncCallback = true;
}
if (type.returns || type.allowAmbiguousOptionalArguments) {
throw new Error(`Internal error: Async functions must not have return values or ambiguous arguments.`);
if (type.returns) {
throw new Error("Internal error: Async functions must not have return values.");
}
if (type.allowAmbiguousOptionalArguments && !hasAsyncCallback) {
throw new Error("Internal error: Async functions with ambiguous arguments must declare the callback as the last parameter");
}
}

Просмотреть файл

@ -295,6 +295,7 @@
"type": "function",
"allowAmbiguousOptionalArguments": true,
"description": "Sends a single message to event listeners within your extension/app or a different extension/app. Similar to $(ref:runtime.connect) but only sends a single message, with an optional response. If sending to your extension, the $(ref:runtime.onMessage) event will be fired in each page, or $(ref:runtime.onMessageExternal), if a different extension. Note that extensions cannot send messages to content scripts using this method. To send messages to content scripts, use $(ref:tabs.sendMessage).",
"async": "responseCallback",
"parameters": [
{"type": "string", "name": "extensionId", "optional": true, "description": "The ID of the extension/app to send the message to. If omitted, the message will be sent to your own extension/app. Required if sending messages from a web page for $(topic:manifest/externally_connectable)[web messaging]."},
{ "type": "any", "name": "message" },

Просмотреть файл

@ -74,6 +74,7 @@ skip-if = (os == 'android' || buildapp == 'b2g') # sender.tab is undefined on b2
skip-if = (os == 'android' || buildapp == 'b2g') # sender.tab is undefined on b2g. Bug 1258975 on android.
[test_ext_sendmessage_doublereply.html]
skip-if = (os == 'android' || buildapp == 'b2g') # sender.tab is undefined on b2g. Bug 1258975 on android.
[test_ext_sendmessage_no_receiver.html]
[test_ext_storage_content.html]
[test_ext_storage_tab.html]
skip-if = os == 'android' # Android does not currently support tabs.

Просмотреть файл

@ -0,0 +1,85 @@
<!DOCTYPE html>
<html>
<head>
<title>WebExtension test</title>
<meta charset="utf-8">
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="text/javascript" src="/tests/SimpleTest/SpawnTask.js"></script>
<script type="text/javascript" src="/tests/SimpleTest/ExtensionTestUtils.js"></script>
<script type="text/javascript" src="head.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css">
</head>
<body>
<script>
"use strict";
function loadContentScriptExtension(contentScript) {
let extensionData = {
manifest: {
"content_scripts": [{
"js": ["contentscript.js"],
"matches": ["http://mochi.test/*/file_sample.html"],
}],
},
files: {
"contentscript.js": `(${contentScript})();`,
},
};
return ExtensionTestUtils.loadExtension(extensionData);
}
add_task(function* test_content_script_sendMessage_without_listener() {
function contentScript() {
browser.runtime.sendMessage("msg").then(reply => {
browser.test.assertEq(undefined, reply);
browser.test.notifyFail("Did not expect a reply to sendMessage");
}, error => {
browser.test.assertEq("Could not establish connection. Receiving end does not exist.", error.message);
browser.test.notifyPass("sendMessage callback was invoked");
});
}
let extension = loadContentScriptExtension(contentScript);
yield extension.startup();
let win = window.open("file_sample.html");
yield extension.awaitFinish("sendMessage callback was invoked");
win.close();
yield extension.unload();
});
add_task(function* test_content_script_chrome_sendMessage_without_listener() {
function contentScript() {
/* globals chrome */
browser.test.assertEq(null, chrome.runtime.lastError, "no lastError before call");
let retval = chrome.runtime.sendMessage("msg");
browser.test.assertEq(null, chrome.runtime.lastError, "no lastError after call");
// TODO(robwu): Fix the implementation and uncomment the next expectation.
// When content script APIs are schema-based (bugzil.la/1287007) this bug will be fixed for free.
// browser.test.assertEq(undefined, retval, "return value of chrome.runtime.sendMessage without callback");
browser.test.assertTrue(retval instanceof Promise, "TODO: chrome.runtime.sendMessage should return undefined, not a promise");
let isAsyncCall = false;
retval = chrome.runtime.sendMessage("msg", reply => {
browser.test.assertEq(undefined, reply, "no reply");
browser.test.assertTrue(isAsyncCall, "chrome.runtime.sendMessage's callback must be called asynchronously");
browser.test.assertEq(undefined, retval, "return value of chrome.runtime.sendMessage with callback");
browser.test.assertEq("Could not establish connection. Receiving end does not exist.", chrome.runtime.lastError.message);
browser.test.notifyPass("finished chrome.runtime.sendMessage");
});
isAsyncCall = true;
}
let extension = loadContentScriptExtension(contentScript);
yield extension.startup();
let win = window.open("file_sample.html");
yield extension.awaitFinish("finished chrome.runtime.sendMessage");
win.close();
yield extension.unload();
});
</script>
</body>
</html>

Просмотреть файл

@ -23,3 +23,33 @@ add_task(function* test_sendMessage_without_listener() {
yield extension.unload();
});
add_task(function* test_chrome_sendMessage_without_listener() {
function background() {
/* globals chrome */
browser.test.assertEq(null, chrome.runtime.lastError, "no lastError before call");
let retval = chrome.runtime.sendMessage("msg");
browser.test.assertEq(null, chrome.runtime.lastError, "no lastError after call");
browser.test.assertEq(undefined, retval, "return value of chrome.runtime.sendMessage without callback");
let isAsyncCall = false;
retval = chrome.runtime.sendMessage("msg", reply => {
browser.test.assertEq(undefined, reply, "no reply");
browser.test.assertTrue(isAsyncCall, "chrome.runtime.sendMessage's callback must be called asynchronously");
browser.test.assertEq(undefined, retval, "return value of chrome.runtime.sendMessage with callback");
browser.test.assertEq("Could not establish connection. Receiving end does not exist.", chrome.runtime.lastError.message);
browser.test.notifyPass("finished chrome.runtime.sendMessage");
});
isAsyncCall = true;
}
let extensionData = {
background,
};
let extension = ExtensionTestUtils.loadExtension(extensionData);
yield extension.startup();
yield extension.awaitFinish("finished chrome.runtime.sendMessage");
yield extension.unload();
});