Myth of Echelon Posted May 20, 2012 Share Posted May 20, 2012 (edited) Hey guys, long time no see. I have an issue with a Chrome extension I'm developing, that I've had for weeks and no one has been able to answer yet. The Question: When passing messages between a Google Chrome extension's background page and content script, respectively, is there any way to make it asynchronous - that is, to delay/pause the JavaScript until all of the messages are detected as being successfully being passed? The Explanation: I have a function immediately after the message-passing function that makes use of the localStorage data that is passed. On first runs the script always results in an error, due to the data not being passed (and therefore cached) fast enough. Currently, I'm circumventing this with setTimeout(nextFunction, 250); but that's hardly an elegant or practical solution, as the amount and size of the values passed is always going to change and I have no way of knowing how long it needs to pass the values. Plus, I would imagine, that passing times are relative to the browser version and the user's system. In short, I need it to be dynamic. I have considered something like this: function passMessages(){ chrome.extension.sendRequest({method: "methodName"}, function(response) { localStorage["lsName"] = response.data; }); checkPassedMessages(); } function checkPassedMessages(){ if (!localStorage["lsName"]){ setTimeout(checkPassedMessages, 100); //Recheck until data exists } else { continueOn(); } } but I need to pass quite a lot of data (at least 20 values) and, frankly, that "solution" just isn't practical, due to the amount of IF conditions that it would require. Plus I don't even know if that would work or not. Does anyone have any ideas? Thanks. Edited May 20, 2012 by Myth of Echelon Link to comment Share on other sites More sharing options...
__Darknite Posted May 23, 2012 Share Posted May 23, 2012 Could you post the both the content JavaScript and the extension JavaScipt? I have not worked with chrome extensions, but they look like regular js (wrapped inside a custom google framework)? Link to comment Share on other sites More sharing options...
Myth of Echelon Posted May 23, 2012 Author Share Posted May 23, 2012 (edited) Yes, it is just standard JavaScript wrapped in Chrome's framework. But my question is actually about Google's framework, so I don't know how far we'll get, as you said you have no personal experience. I'll post you the basic template of message passing from background to content. There's nothing wrong with the functionality, so there's no reason to include irrelevant bits. background.js function addMessageListeners(){ chrome.extension.onRequest.addListener( function(request, sender, sendResponse) { if (request.method == "methodName1"){ sendResponse({data: localStorage["lsName1"]}); //Send the localStorage data inside "response" if the method name is "methodName1" } if (request.method == "methodName2"){ sendResponse({data: localStorage["lsName2"]}); } //etc } ); } content.js function passMessages(){ chrome.extension.sendRequest({method: "methodName1"}, function(response) { localStorage["lsName1"] = response.data; //Send message request with method name "methodName1". When a response is received enter the "response" data back into localStorage }); chrome.extension.sendRequest({method: "methodName2"}, function(response) { localStorage["lsName2"] = response.data; }); } (localStorage data is not accessible from within the content script unless passed through in a message) Edited May 23, 2012 by Myth of Echelon Link to comment Share on other sites More sharing options...
__Darknite Posted May 23, 2012 Share Posted May 23, 2012 (edited) Ok, I sort of understand, Does each method name map to some sort of Key/Value? if so (and this is off the top of my head): function addMessageListeners(){ chrome.extension.onRequest.addListener( function(request, sender, sendResponse) { var map = {methodName1: "lsName1", methodName2: "lsName2", ......etc};// map your method name to => key if (map[request.method] !==null){ sendResponse({data: localStorage[map[request.method]]}); //Send the localStorage data inside "response" if mapping exists } } ); } something like that perhaps, again this is off the top of my head. Edited May 23, 2012 by __Darknite Link to comment Share on other sites More sharing options...
Myth of Echelon Posted May 23, 2012 Author Share Posted May 23, 2012 I have no idea. It took me bloody ages to get my head around the message passing itself (it's hardly elegant or user-friendly). I've never come across a method like that before, but Google tells me that it's a shorthand way of creating objects. Wouldn't that method be similar to what I considered - statically checking if the data exists? Link to comment Share on other sites More sharing options...
__Darknite Posted May 23, 2012 Share Posted May 23, 2012 (edited) Basically the message passing works on callbacks, so you pass a function as a callback parameter. When your target function executes, it then executes your callback function that you passed. I've had another look at your code, Are you just trying to get a bunch of key value pairs off the extension and then loading them into the content side? If that is the cause, you know you can store all the KV set as a json object and then just push that into and out of "localStorage" so basically all you code would become: extension: function addMessageListeners(){ chrome.extension.onRequest.addListener( function(request, sender, sendResponse) { sendResponse({data: localStorage[request.key]}); } ); } content function passMessages(){ chrome.extension.sendRequest({method: "ObjectKey"}, function(response) { localStorage["ObjectKey"] = response.data; [Call your Function Here Data has now been loaded]; }); } Edited May 23, 2012 by __Darknite Link to comment Share on other sites More sharing options...
Myth of Echelon Posted May 23, 2012 Author Share Posted May 23, 2012 (edited) The localStorage data is set on the options page and then needs to be passed to the functions that use them (which are in the content script). Google disallows the content script to have any form of communication with the extension itself, except through message passing. So I have to pass all the preferences through as messages. Oh, I see. So you're suggesting that I send all of the data as one object it and then separate it on the other side? That could work, I suppose. I just can't use localStorage on the other side (I don't think it supports any data types other than String). If so, that still needs time to pass.. Edited May 23, 2012 by Myth of Echelon Link to comment Share on other sites More sharing options...
__Darknite Posted May 23, 2012 Share Posted May 23, 2012 (edited) Hence the JSON.stringify function, and its opposite function JSON.parse Edited May 23, 2012 by __Darknite Link to comment Share on other sites More sharing options...
Myth of Echelon Posted May 24, 2012 Author Share Posted May 24, 2012 (edited) I really don't see how the mapped method name array thing could work, but funnelling the localStorage data into a JSON object, piping it through and then extracting it on the other end has intrigued me. Plus it would negate the need for the mapped array entirely, as I would only need one message to be passed. The only thing that concerns me, though, is how I would go abouts extracting the values once the JSON object has been passed to the content script. As previously mentioned, I don't think localStorage supports any other data than Strings (based entirely off of me trying to enter a Boolean value into it and only being able to confirm true/false by matching it as a string), so how would I separate the values and differentiate what preference they represent if it's just one long string? Edited May 24, 2012 by Myth of Echelon Link to comment Share on other sites More sharing options...
__Darknite Posted May 24, 2012 Share Posted May 24, 2012 (edited) I really don't see how the mapped method name array thing could work, but funnelling the localStorage data into a JSON object, piping it through and then extracting it on the other end has intrigued me. Plus it would negate the need for the mapped array entirely, as I would only need one message to be passed. Yes that is correct. The only thing that concerns me, though, is how I would go abouts extracting the values once the JSON object has been passed to the content script. As previously mentioned, I don't think localStorage supports any other data than Strings (based entirely off of me trying to enter a Boolean value into it and only being able to confirm true/false by matching it as a string), so how would I separate the values and differentiate what preference they represent if it's just one long string? localStorage is a simple KV store, and yes it only stores strings. This is why: When you put JSON objects into localStorage you convert a json object into a string using JSON.stringyify When you get an string out of localStorage you convert the string back into json object by using the JSON.parse method Does that make more sense? Edited May 24, 2012 by __Darknite Link to comment Share on other sites More sharing options...
Myth of Echelon Posted May 24, 2012 Author Share Posted May 24, 2012 I understand the concept, I just don't understand how it could work. Then again, if it does work then I don't really need to know (although, I would find it interesting). I'll start testing and report back. Thanks. Link to comment Share on other sites More sharing options...
Myth of Echelon Posted May 24, 2012 Author Share Posted May 24, 2012 (edited) So, just something like: background.js var values = JSON.stringify({"lsName1": localStorage["lsName1"], lsName2: localStorage["lsName2"]}); //No idea if that's right function addMessageListeners(){ chrome.extension.onRequest.addListener( function(request, sender, sendResponse) { if (request.method == "getJSONData"){ sendResponse({data: values}); } } ); } content.js function passMessages(){ chrome.extension.sendRequest({method: "getJSONData"}, function(response) { var values = JSON.parse(response.data); localStorage["lsName1"] = values.lsName1; localStorage["lsName2"] = values.lsName2; continueOn(); //I'm guessing that things inside this function are asynchronous, and, therefore, "continueOn()" will only be fired once the above operations have been completed? }); } Sorry, this method and syntax is new to me. The above code was just a wild stab in the dark using what I could grasp. :| Edited May 24, 2012 by Myth of Echelon Link to comment Share on other sites More sharing options...
__Darknite Posted May 24, 2012 Share Posted May 24, 2012 (edited) Yes, that's almost spot on, except: I don't see why you would need to parse the string at that point, you could simply stuff it into localStorage. Its only when you need to access a particular attribute that you would parse the string. Edit: background (localStorage) content (localStorage) k => ConfigData k => ConfigData v => (json as string) <============> v => (json as string) Edited May 24, 2012 by __Darknite Link to comment Share on other sites More sharing options...
Myth of Echelon Posted May 24, 2012 Author Share Posted May 24, 2012 Wow. That worked seamlessly.. Thank you! One last inquiry: Even though I've just gotton rid of damn near 100 lines of code, I still have to manually define every value. Is there any way to get a FOR loop to cycle through a KV store? Link to comment Share on other sites More sharing options...
__Darknite Posted May 24, 2012 Share Posted May 24, 2012 (edited) Not that I know of. Are you storing all the values as a json object that has been turned into a string? Hopefully if you have done that, then once you get this string back, you can turn this into a json object. You can of course cycle through (for each) json element. Does that make sense? Edited May 24, 2012 by __Darknite Link to comment Share on other sites More sharing options...
Myth of Echelon Posted May 25, 2012 Author Share Posted May 25, 2012 I store all of the values as a stringified JSON during transition and briefly before I extract them? And not particularly.. :L Link to comment Share on other sites More sharing options...
__Darknite Posted May 25, 2012 Share Posted May 25, 2012 ok, lets back up a little bit: in your background.js: var values = JSON.stringify({"lsName1": localStorage["lsName1"], lsName2: localStorage["lsName2"]}); this is telling me that you have each element stored separately. I am suggesting, that is not needed. depending on how you are initially storing those elements, instead of storing them one by one. Store them all in big json object then convert it into a string and store that instead. Is that a little clearer? Link to comment Share on other sites More sharing options...
Myth of Echelon Posted May 26, 2012 Author Share Posted May 26, 2012 Never mind, it doesn't matter. I can do without it. :L Thanks for your help, though! Really, really appreciate it. (Feel free to mark this closed now) Link to comment Share on other sites More sharing options...
Thomas Posted May 26, 2012 Share Posted May 26, 2012 Never mind, it doesn't matter. I can do without it. :L Thanks for your help, though! Really, really appreciate it. (Feel free to mark this closed now) As you command *Closes Thread* Link to comment Share on other sites More sharing options...
Recommended Posts