How to make Promises work between background and foreground scripts?


(David Spector) #1

Foreground and background scripts use a messaging system to communicate back and forth. But sometimes one side starts an asynchronous operation that the other side should pledge to honor. This requires making Promises work across this communications channel.

Does anyone have any idea how to do this? Since I don’t understand the details of the internal implementation of Promises, and can barely make them work, I haven’t the foggiest idea how to accomplish this.

Perhaps a use case will motivate this question better. Since console.log() does not work reliably in the WebExtensions environment (at least, that is my experience over several days of experimentation), it would be nice to display modal notification boxes containing debugging messages, analogous to debugging by using the alert() function.

This is done by browser.notifications.create(), but this function requires some Promise wrapping to wait for 500 msec after the box is created so it can be displayed before moving on to doing something else, and to wait for 3000 msec or more after the box is created before destroying it (in case the user fails to click the box to dismiss it). I’ve posted my solution to this problem elsewhere (https://stackoverflow.com/questions/53869972/how-to-wrap-webextensions-notifications-in-a-promise).

So, say the foreground script needs to send you some text. Since foreground scripts are not allowed to create their own windows, and probably should not create their own modal DIVs either, the foreground script sends a message to the background script to display the box. But when I try to implement this, the box displays only for a fraction of a second, then disappears. The idea that this is a two-environment asynchronous process has not been implemented, so waiting is not coordinated between the two environments.

We need a way of envisioning and supporting multi-environment Promises, where the environments can only be coordinated via the browser.runtime messaging facility.


(Martin Giger) #2

sendMessage returns a promise. You can send a value for this promise from the onMessage listener as a response. See https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/runtime/onMessage#Sending_an_asynchronous_response_using_a_Promise


(David Spector) #3

Just a final response to say that I tried the MDN sync example and it failed with error message “sendResponse is not defined”. I found another posting on the Web about this error. It looks like nobody has ever tried to make asynch calls through sendMessage work!

browser.runtime.onMessage.addListener(function(message) // Not a Promise
	{
	if (message.msg)
		{
		S(message.msg).then(function()
			{sendResponse({response: "response from background script"});});
		return true;
		}
	});

(David Spector) #4

So how am I supposed to create a Web extension for Firefox when the debugger isn’t good enough to use and MDN is not complete enough? It’s very frustrating when I try to do anything other than what’s in the examples, which I have to do since none of the examples is a password manager.

Maybe I should get it all working on Chrome first, then port to Firefox? Or should I install Bluebird to get good asych error messages? I could use some real help with this stuff, not just fast answers.


(Martin Giger) #5

You are clearly not taking the sendResponse parameter in here. Further, as MDN says you can also return a promise from the listener, but as you commented you’re not trying to do that.

See crucially the example at https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/runtime/onMessage#Sending_an_asynchronous_response_using_sendResponse

Edit:
Also, a “password manager” is a very broad application. That can do a range of things. But usually it consists of a content script and a storage mechanism in the background. I think there’s plenty of examples around for that, on MDN and in the example webextensions repo. And lastly there are a couple of password manager extensions that are open source, so you should be able to find even example password manager code.

To me it sounds more like you’re having trouble with the options that the API gives, with multiple styles being possible, though the documentation tries to mainly document the promise based styles.


(Juraj Masiar) #6

I would like to contribute two “good to know” things here:
First - don’t user “sendMessage” function at all. It’s already not supported by webextension-polyfill library and it may get removed from W3C spec:

Returning a Promise is now preferred way as sendResponse will be removed from W3C spec. The popular webextension-polyfill library already removed this function from its implementation.

Second - don’t make your “frontend” message handler async - else you may get very strange bugs when message send from “frontend” script is actually caught by the same script, instead of background script! (this is because async function always returns a promise)
However in background script it seems to be fine.

Else things should be pretty straightforward - return Promise if you want to send a response or undefined if not. If you want to send response using synchronous function, just wrap it into a Promise - Promise.resolve('this is a reply').


(David Spector) #7

Martin and juraj, thank you for your attempts to help, however, I do not understand what either of you is saying. Martin, I don’t know enough to use the async examples, and the sync example gives the error message I already mentioned. I think I understand async functions, whether using callbacks, Promises, or async/await. But the MDN examples are difficult for me to understand, as there are no comments explaining what each line of code does!

You also claim that there are PM examples, but I haven’t found them and your clues as to their locations are vague.

Note that there are many files in a real extension–install/update file, popup file, context menu, foreground/content scripts for obtaining and inserting login data, and the background script that has to coordinate everything. It’s quite complex, with messages flying back and forth between many of these files! I need much more in-depth help. Without good help I am taking weeks to get familiar with extension code–weeks that I am not able to work on the actual guts of a PM.

Juraj-- You say not to use ‘sendMessage’ but you don’t explain how to send messages without it. Is there some magic to communicating that you can explain to me?

You also say not to use an async message handler. I’m not. But how to coordinate messages between files and the background script? A message is an async operation!!

I am very confused and need much more help.


(Martin Giger) #8

It does not, the code you pasted only has one argument for the listener. Whereas you need to take three arguments to get the sendResponse callback. function(message) vs. handleMessage(request, sender, sendResponse).

They usually do what the text around them explains on a higher level. After that it is mostly English and reading code, sorry. A comment can only explain so much more, that a call to addListener will add a listener etc. I do not think that the examples for the extension API should have to introduce you to standard web APIs or JS concepts.

I haven’t tried to look for them, but I know I’ve looked at github repos of pw managers before. Usually you’ll find their github repo somewhere in their listing on AMO.

You seem to need help with how to lay out your code and how to design the communication patterns? Sadly that’s something that’s very hard to help with without knowing your exact goals and without investing a lot of thinking into it.


(David Spector) #9

Thanks for correcting my handler arguments. This was the help I needed this time.

Yes, each problem I’m having has a simple solution. The problem is that extension writing is very specialized. The tutorials I’ve read explain only part of what one needs to know.

I don’t have problems just writing code. I’ve been doing that in various languages since 1965. The problem is that when something doesn’t work, as often as not the debugger is of no help (for example because its error message is in obscure system code), and the MDN is not quite clear enough.

I’m sure a year from now I will be an expert in all this, and even able to help others with it, but for now it’s very, very frustrating to try to get an extension working. Meanwhile, I have lots of other projects that are waiting for me to be free.

I don’t want to stop and ask for help with every new feature as I add it to my code, but something like this almost seems unavoidable.

Finally, again, does anyone have advice as to whether I should be doing this in Chrome or with Bluebird promises? Would either of these environments help me find my bugs better?


(Juraj Masiar) #10

Let’s have an example:


// code in background script
const result = await browser.tabs.sendMessage(tabId, {type: 'bar'});
// result is now 'this is a response' text
// NOTE: this code  needs to be executed in async function


// code in your frontend
browser.runtime.onMessage.addListener((data, sender) => {
  console.log('received message', data);
  switch (data.type) {
    case 'foo': return;   // returns undefined, sender will not receive a response
    case 'bar': return Promise.resolve('this is a response');  // returns a Promise - when this promise is resolved, the result will be send back to the sender
  }
});


Make sure to read as much MDN as possible, especially the async / await part. It’s very handy to use these.



(Martin Giger) #11

I would recommend looking into what the API for chrome looks like if you plan to be compatible with it at the very least. So at least consider https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Chrome_incompatibilities

I don’t think using bluebird will help much, since that only affects promises you create in user-space code.

Lastly, the error message you got in this case - “sendResponse is not defined” - points out the issue very clearly. My way of figuring out what’s up with that message is to see where I’m using the thing that’s not defined, and crucially where I’m defining it. In your case I couldn’t find any place that it would be defined (and I also knew that it probably referred to the third parameter of a listener). Next, since we’re working against examples is to check the example. Where does it define it? Ah, in the third parameter!