Open Bug 1035579 Opened 10 years ago Updated 2 years ago

sendAsyncMessage callback

Categories

(Core :: DOM: Core & HTML, defect)

31 Branch
x86
Windows XP
defect

Tracking

()

UNCONFIRMED

People

(Reporter: noitidart, Unassigned)

Details

User Agent: Mozilla/5.0 (Windows NT 5.1; rv:31.0) Gecko/20100101 Firefox/31.0 (Beta/Release)
Build ID: 20140703154127

Steps to reproduce:

I used the message manager api to. I used the sendAsyncMessage call back instead of sendSyncMessage because async is always preferred. However with sendAsyncMessage I cannot supply a callback.

Please add callback feature to sendAsyncMessage.

This stackoverflow topic explains more in detail please: http://stackoverflow.com/questions/24619104/message-manager-api-sendasyncmessage-callback



Expected results:

please create callback feature
Component: Untriaged → DOM
Product: Firefox → Core
When would you expect the callback to fire? Why not just send a return asynchronous message from the parent?
Yeah sending the return async message is how I'm having to do it. But it requires a two step setup thats not necessarily related.

Just thought to make it work like callbacks as arguments to a function usually work. I tried to figure it out and evnetually posted on stackoverflow and was told its not available. So people might waste time trying to force figure it out, to realize its not there. People will try to figure it out because I think they expect it. At least I did, and i work with callbacks a bit.
Reason I would like this is because it matches promise logic.

Just like PromiseWorker.jsm:

http://mxr.mozilla.org/mozilla-release/source/toolkit/components/osfile/modules/osfile_async_front.jsm#332

332         let promise = this._worker.post(...message);
333 
334         // Wait for result


Would be awesome if we can get this.
because sendAsyncMessage is  going to be so widely used. Its not like ChromeWorker's and postMessage, people writing ChromeWorkers definitely have a higher then average skill level. And sendAsyncMessage will definitely be used by new comers.


We need a sendAsyncMessageWithCallback

I wrote this postMessageWithCallback for ChromeWorker, its very simple and crude, but i doubt your average coder will do that, but will definitely definitely look for that with the sendAsyncMessage
We don't need sendAsyndMessageWithCallback but I can see it being quite useful.

I need to get back to bug 888600 soon, and after that fixing this should be somewhat simpler.
Thanks Olli. All of google chrome stuff offers with callback, so noobs definitely get it. Its hard for them to set up callback communication but simple for more experienced people.

i set up postMessageWithCallback here: https://github.com/Noitidart/IdleStateLocked/blob/master/bootstrap.js#L73
(In reply to Olli Pettay [:smaug] from comment #5)
> We don't need sendAsyndMessageWithCallback but I can see it being quite
> useful.
> 
> I need to get back to bug 888600 soon, and after that fixing this should be
> somewhat simpler.

Hi there Olli I was wondering if you had any update on this? Users are trying to write their own versions:

http://stackoverflow.com/a/31073360/1828637
Flags: needinfo?(bugs)
Don't have updates as of now, sorry.

Of course one can always write a small js wrapper on top of sendAsyncMessage to deal with
this kind of stuff.
Flags: needinfo?(bugs)
(In reply to Olli Pettay [:smaug] from comment #8)
> Don't have updates as of now, sorry.
> 
> Of course one can always write a small js wrapper on top of sendAsyncMessage
> to deal with
> this kind of stuff.

Oh no problem no sorries please thanks for your hard work. Thanks for fast reply :)
I hooked one up that allows setting up callback on both sides. I call it the sendAsyncMessageWithCallback

But the listener is modified so that it calls a function with the first element in the array. Its probably not as simple as we want for production. I'm used to PromiseWorker so I like being able to send to my functions multiple arguments, so if we land this we should probably do it like Promise.resolve, allow only one argument (and devusers can just make that an object with multiple fields if they need multiple arguments).

I found its important to set up return array or promise. Because what if one side is doing async stuff, so this works pretty good.


///////////// parentscript side (ie: bootstrap)

const SAM_CB_PREFIX = '_sam_gen_cb_';
function sendAsyncMessageWithCallback(aMessageManager, aGroupId, aMessageArr, aCallbackScope, aCallback) {
	var thisCallbackId = SAM_CB_PREFIX + new Date().getTime();
	aCallbackScope = aCallbackScope ? aCallbackScope : bootstrap;
	aCallbackScope[thisCallbackId] = function(aMessageArr) {
		delete aCallbackScope[thisCallbackId];
		aCallback.apply(null, aMessageArr);
	}
	aMessageArr.push(thisCallbackId);
	aMessageManager.sendAsyncMessage(aGroupId, aMessageArr);
}

var listener = { // framescript msg listener
	funcScope: myFuncstionsObj,
	receiveMessage: function(aMsgEvent) {
		var aMsgEventData = aMsgEvent.data;
		console.log('ICGenWorkerFuncs.fwInstances[aId] getting aMsgEventData:', aMsgEventData);
		// aMsgEvent.data should be an array, with first item being the unfction name in bootstrapCallbacks
		
		var callbackPendingId;
		if (typeof aMsgEventData[aMsgEventData.length-1] == 'string' && aMsgEventData[aMsgEventData.length-1].indexOf(SAM_CB_PREFIX) == 0) {
			callbackPendingId = aMsgEventData.pop();
		}
		
		aMsgEventData.push(aMsgEvent); // this is special for server side, so the function can do aMsgEvent.target.messageManager to send a response
		
		var funcName = aMsgEventData.shift();
		if (funcName in this.funcScope) {
			var rez_parentscript_call = this.funcScope[funcName].apply(null, aMsgEventData);
			
			if (callbackPendingId) {
				// rez_parentscript_call must be an array or promise that resolves with an array
				if (rez_parentscript_call.constructor.name == 'Promise') {
					rez_parentscript_call.then(
						function(aVal) {
							// aVal must be an array
							aMsgEvent.target.messageManager.sendAsyncMessage(core.addon.id, [callbackPendingId, aVal]);
						},
						function(aReason) {
							aMsgEvent.target.messageManager.sendAsyncMessage(core.addon.id, [callbackPendingId, ['promise_rejected', aReason]]);
						}
					).catch(
						function(aCatch) {
							aMsgEvent.target.messageManager.sendAsyncMessage(core.addon.id, [callbackPendingId, ['promise_rejected', aReason]]);
						}
					);
				} else {
					// assume array
					aMsgEvent.target.messageManager.sendAsyncMessage(core.addon.id, [callbackPendingId, rez_parentscript_call]);
				}
			}
		}
		else { console.warn('funcName', funcName, 'not in scope of this.funcScope') } // else is intentionally on same line with console. so on finde replace all console. lines on release it will take this out
		
	}
};

Services.mm.addMessageListener(core.addon.id, listener);



//////////////////// framescript side
const SAM_CB_PREFIX = '_sam_gen_cb_';
function sendAsyncMessageWithCallback(aMessageManager, aGroupId, aMessageArr, aCallbackScope, aCallback) {
	var thisCallbackId = SAM_CB_PREFIX + new Date().getTime();
	aCallbackScope = aCallbackScope ? aCallbackScope : bootstrap; // todo: figure out how to get global scope here, as bootstrap is undefined
	aCallbackScope[thisCallbackId] = function(aMessageArr) {
		delete aCallbackScope[thisCallbackId];
		aCallback.apply(null, aMessageArr);
	}
	aMessageArr.push(thisCallbackId);
	aMessageManager.sendAsyncMessage(aGroupId, aMessageArr);
}
var bootstrapMsgListener = {
	funcScope: bootstrapCallbacks,
	receiveMessage: function(aMsgEvent) {
		var aMsgEventData = aMsgEvent.data;
		console.log('framescript getting aMsgEvent:', aMsgEventData);
		// aMsgEvent.data should be an array, with first item being the unfction name in this.funcScope
		
		var callbackPendingId;
		if (typeof aMsgEventData[aMsgEventData.length-1] == 'string' && aMsgEventData[aMsgEventData.length-1].indexOf(SAM_CB_PREFIX) == 0) {
			callbackPendingId = aMsgEventData.pop();
		}
		
		var funcName = aMsgEventData.shift();
		if (funcName in this.funcScope) {
			var rez_fs_call = this.funcScope[funcName].apply(null, aMsgEventData);
			
			if (callbackPendingId) {
				// rez_fs_call must be an array or promise that resolves with an array
				if (rez_fs_call.constructor.name == 'Promise') {
					rez_fs_call.then(
						function(aVal) {
							// aVal must be an array
							contentMMFromContentWindow_Method2(content).sendAsyncMessage(core.addon.id, [callbackPendingId, aVal]);
						},
						function(aReason) {
							contentMMFromContentWindow_Method2(content).sendAsyncMessage(core.addon.id, [callbackPendingId, ['promise_rejected', aReason]]);
						}
					).catch(
						function(aCatch) {
							contentMMFromContentWindow_Method2(content).sendAsyncMessage(core.addon.id, [callbackPendingId, ['promise_rejected', aReason]]);
						}
					);
				} else {
					// assume array
					contentMMFromContentWindow_Method2(content).sendAsyncMessage(core.addon.id, [callbackPendingId, rez_fs_call]);
				}
			}
		}
		else { console.warn('funcName', funcName, 'not in scope of this.funcScope') } // else is intentionally on same line with console. so on finde replace all console. lines on release it will take this out
		
	}
};
contentMMFromContentWindow_Method2(content).addMessageListener(core.addon.id, bootstrapMsgListener);






///////////////////////////////// EXAMPLE USAGE
// framescript side
sendAsyncMessageWithCallback(contentMMFromContentWindow_Method2(content), core.addon.id, ['testSettingCbInFramescript', 'ARG1'], bootstrapMsgListener.funcScope, function(aRetArg1){
	console.log('back in framescript side callback aRetArg1:', aRetArg1);
});

///// parentscript side
var callbacks = {
	testSettingCbInFramescript: function(arg1) {
		console.log('in testSettingCbInFramescript on parentscript side, arg1:', arg1);
		return ['parentscript testSettingCbInFramescript returning THISSSS'];
	}
}

///////////////////////////////// EXAMPLE USAGE - setting up callback in parent script - opposite of above
///// parentscript side


///// framescript side
Component: DOM → DOM: Core & HTML
Severity: normal → S3
You need to log in before you can comment on or make changes to this bug.