Closed Bug 1325831 Opened 9 years ago Closed 5 years ago

Need clear documentation for sending extension messages in WebExtensions (content script <-> extension)

Categories

(Developer Documentation Graveyard :: Add-ons, defect)

defect
Not set
normal

Tracking

(Not tracked)

RESOLVED WONTFIX

People

(Reporter: robwu, Unassigned)

Details

The current docs about extension messaging that I found are: https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API/runtime/sendMessage https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API/runtime/onMessage https://developer.mozilla.org/en-US/Add-ons/WebExtensions/Examples#page-to-extension-messaging Extension messaging is important for gluing different parts of an extension together, yet the above documentation is a bit lacking in my opinion. I was reviewing an add-on today that did the following: - content script: chrome.runtime.sendMessage to notify the background page - background page: in chrome.runtime.onMessage, accept the message and invoke a native app. - background page (via native app's onMessage): call chrome.tabs.executeScript with the response to call a function in the content script with the response. The use of tabs.executeScript to send back a response is a bit unfortunate. I was hoping to point the author to documentation that suggests to do: - content script: Call chrome.runtime.sendMessage to notify the background page, with a response callback. - background page: in chrome.runtime.onMessage, accept the message, call the sendResponse callback and "return true", or a promise. - background page (via native app's onMessage): call sendResponse, or resolve the promise. Now the response callback from the first step is called. I did not find such documentation on MDN. Chrome has lots of details on the callback-based APIs [1], can we have something similar, and also for the promise-based APIs? The documentation for tabs.sendMessage does have an example for tabs.sendMessage with promises [2], but a novice is not going to find it when they are looking for runtime.sendMessage. [1] https://developer.chrome.com/extensions/messaging [2] https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API/tabs/sendMessage
Will, is this something that you can write? I put it in a small note at the end of the big report, but I'm quite surprised that it is not documented that the onMessage handler is not taking a promise as a return value. https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API/runtime/onMessage#addListener_syntax
Flags: needinfo?(wbamberg)
Rob: yes, this is on my radar. I'm still writing WE docs for Firefox 53 at the moment.
Flags: needinfo?(wbamberg)
I've made some updates to this page, could you please check it looks all right? https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API/runtime/onMessage
Flags: needinfo?(rob)
(Either way, I would still like to keep this bug open for a dedicated guide to messaging.)
- Need browser compatibility note: Chrome does not support a Promise as return value. - Be explicit that sendResponse may be called only once. The current statement ("If you have more than one onMessage listener in the same document, then only one may send a response.") does not specify what is expected to happen when sendResponse is called repeatedly in a single message listener. When using tabs.sendMessage, listeners in different documents (of child frames) can be triggered. - What happens when "return true" is used but the listener never invoked (FYI in Chrome sendResponse is implicitly called when sendResponse is garbage-collected). - (See https://github.com/mozilla/webextension-polyfill/pull/23 for subtleties in having multiple listeners). - The promise example can greatly be simplified to: return browser.bookmarks.search({ url: message.url }).then(function(results) { return results.length > 0; }); And if you like to use ES6: return browser.bookmarks.search({ url: message.url }).then(results => results.length > 0); I think that there is value in showing an explicit way to construct the Promise, so consider keeping your current example as an example to explicitly construct the Promise, and describe the alternative method with the explanation that the browser.* APIs return a promise. ... or stick to the existing contrived example that was used for the sendResponse example and use: function handleMessage(request, sender, sendResponse) { console.log(`content script sent a message: ${request.content}`); return new Promise(resolve => { setTimeout(() => { resolve({response: "async response from background script"}); }, 1000); }); }
Flags: needinfo?(rob)
Thanks for your comments Rob. (In reply to Rob Wu [:robwu] from comment #5) > - Need browser compatibility note: Chrome does not support a Promise as > return value. > > - Be explicit that sendResponse may be called only once. The current > statement ("If you have more than one onMessage listener in the same > document, then only one may send a response.") does not specify what is > expected to happen when sendResponse is called repeatedly in a single > message listener. When using tabs.sendMessage, listeners in different > documents (of child frames) can be triggered. It seems like: * if you call sendResponse more than once from a single listener, then only the first call is received by the other end * if you call sendResponse from multiple documents (for example, content scripts loaded into multiple frames) then only the first call is received by the other end Is that what you expect? > - What happens when "return true" is used but the listener never invoked > (FYI in Chrome sendResponse is implicitly called when sendResponse is > garbage-collected). I tried this with a content script that returned `true` but never sent a response. When I close the tab I get "Error: Message manager disconnected" in the console, I don't think sendResponse was called. Is that what you expect to see? > > - (See https://github.com/mozilla/webextension-polyfill/pull/23 for > subtleties in having multiple listeners). > > > - The promise example can greatly be simplified to: > > return browser.bookmarks.search({ > url: message.url > }).then(function(results) { > return results.length > 0; > }); > > And if you like to use ES6: > > return browser.bookmarks.search({ > url: message.url > }).then(results => results.length > 0); > > I think that there is value in showing an explicit way to construct the > Promise, so consider keeping your current example as an example to > explicitly construct the Promise, and describe the alternative method with > the explanation that the browser.* APIs return a promise. > > ... or stick to the existing contrived example that was used for the > sendResponse example and use: > > function handleMessage(request, sender, sendResponse) { > console.log(`content script sent a message: ${request.content}`); > return new Promise(resolve => { > setTimeout(() => { > resolve({response: "async response from background script"}); > }, 1000); > }); > } Sounds great, I've done that.
Flags: needinfo?(rob)
(In reply to Will Bamberg [:wbamberg] from comment #6) > Thanks for your comments Rob. > > (In reply to Rob Wu [:robwu] from comment #5) > > - Need browser compatibility note: Chrome does not support a Promise as > > return value. This is still relevant. Is this based on the browser-compat-data repo on Github? > > - Be explicit that sendResponse may be called only once. The current > > statement ("If you have more than one onMessage listener in the same > > document, then only one may send a response.") does not specify what is > > expected to happen when sendResponse is called repeatedly in a single > > message listener. When using tabs.sendMessage, listeners in different > > documents (of child frames) can be triggered. > > It seems like: > * if you call sendResponse more than once from a single listener, then only > the first call is received by the other end > * if you call sendResponse from multiple documents (for example, content > scripts loaded into multiple frames) then only the first call is received by > the other end > > Is that what you expect? Yes. Preferably the documentation should be explicit that sending a response more than once does not make any sense. > > - What happens when "return true" is used but the listener never invoked > > (FYI in Chrome sendResponse is implicitly called when sendResponse is > > garbage-collected). > > I tried this with a content script that returned `true` but never sent a > response. When I close the tab I get "Error: Message manager disconnected" > in the console, I don't think sendResponse was called. Is that what you > expect to see? Yes. The callback of chrome.runtime.sendMessage should be called without arguments (and chrome.runtime.lastError should be set). Or equivalently, the promise returned by browser.runtime.sendMessage should be rejected. (this also applies to tabs.sendMessage).
Flags: needinfo?(rob)
MDN Web Docs' bug reporting has now moved to GitHub. From now on, please file content bugs at https://github.com/mdn/sprints/issues/ and platform bugs at https://github.com/mdn/kuma/issues/.
Status: NEW → RESOLVED
Closed: 5 years ago
Resolution: --- → WONTFIX
You need to log in before you can comment on or make changes to this bug.