I'm trying to code a simple chrome extension but I'm experiencing some difficulty when trying to access the options.html's local storage from my content script "auto.js".
From what i've gathered (googling and reading Chrome's confusing documentation) this is only possible using:
chrome.runtime.sendMessage & chrome.runtime.onMessage.addListener
Content script "auto.js":
var quantity = ""
var shoe_size = ""
function addToCart() {
chrome.runtime.sendMessage({localstorage: "qty"}), function(response){
var quantity = response.qty;
}
chrome.runtime.sendMessage({localstorage: "size"}), function(response){
var shoe_size = response.size;
}
...
My listener in "options.js":
chrome.runtime.onMessage.addListener(
function(request, sender, sendResponse){
if(request.localstorage == "qty")
sendResponse({qty: localStorage.qty});
else if(request.localstorage == "size")
sendResponse({size: localStorage.size});
else
sendResponse({});
});
...
The problem is my vars quantity & shoe_size are never set to the 'returned' values from the html local storage.
There are no errors given in my js console and i'm not sure on how to debug this. Any feedback is greatly appreciated.
That is because codes after sendMessage is run right after sendMessage is triggered. They ARE set but it wont wait for response, they are set after the time you need them. I had the same problem with this here Synchronously get Stored data from content scripts. To solve it you can put whatever function you want inside
chrome.runtime.sendMessage({localstorage: "qty"}), function(response){
var quantity = response.qty;
}
in order to see that it is set, test this:
chrome.runtime.sendMessage({localstorage: "qty"}), function(response){
var quantity = response.qty;
}
alert("waiting for a second, to make sure that response is ready ...");
alert("Not it is set:" + quantity);
You're writing var quantity = response.qty; with a preceding var inside the sendMessage callback function. That will create a local variable with, coincidentally, the same name as the global variable. But it's a different variable. And the memory storing it will be freed as soon as sendMessage is finished, because this is only a local scope.
Leave the var away inside sendMessage.
Also, you're missing the semicolons on those global vars.
var quantity = "";
var shoe_size = "";
Other than that, you can use console.log in your content script 'auto.js', inside the sendMessage callback function. It will land in the console of whatever webpage is loading that script.
Related
UPDATE:
I stopped using an array (As I originally was when I first posted this)
I seem to be having issues with my script. I don't know why, but when the web page unloads, the window.SpecificURL doesn't save. I know the script to get it is fine, the script to store it is as well. Is there something I am missing? Here is my code:
var UWDV = window.UWDV = "UserWindowDataV2"; // What Version Is The User Window Data Stored As
setTimeout(function(){ // Wait So Stuff Can Load
if (!window.SpecificURL){ // If Not A Special URL To Load
window.SpecificURL = GetCookie(UWDV); // Get The User's Last Known State
var WSURL = window.SpecificURL; // Set Variable
// if (typeof WSURL[0]!=="string" || WSURL[0].length < 2){SetCookie(UWDV, ["home"]); WSURL = ["home"];} // If It Glitched, Fix Automatically
console.log(WSURL);
fillpage(WSURL); // Load Page PC
mobileload(WSURL); // Load Page Mobile
window.SpecificURLReady = true;
}
}, 100);
window.addEventListener("unload", function(){SetCookie(window.UWDV, window.SpecificURL);}); // Add Save Page Status Function
(FYI: The fillpage & mobileload functions set window.SpecificURL to whatever the choice was.)
(FYI2: This is fired 2 seconds after the website loads)
I fixed it by switching from Arrays to save data, to a string. Sorry for any inconvenience to someone trying to solve this!
I simply have to access an object that is a variable on the page that I am running my content script on from my Chrome Extension.
I know about the environments and their isolated worlds in which the content scripts and injected scripts run and that it's possible to get some variables using the injected scripts and then send them back.
I have searched for other answers regarding this question and most work for other type of variables and are the basic way of doing it but none currently work for accessing objects.
Any current solutions or workarounds?
EDIT: The solution that I used:
Content script:
//Sends an object from the page to the background page as a string
window.addEventListener("message", function(message) {
if (message.data.from == "myCS") {
chrome.runtime.sendMessage({
siteObject: message.data.prop
});
}
});
var myScript = document.createElement("script");
myScript.innerHTML = 'window.postMessage({from: "myCS", prop: JSON.stringify(OBJECT)},"*");';
document.body.appendChild(myScript);
Background.js:
//Info receiver
chrome.runtime.onMessage.addListener(function(message, sender, sendResponse) {
//When the content script sends the sites object to extract the needed data
if (message.siteObject !== undefined) {
console.log(message.siteObject);
//Process the data
}
});
You can try to inject a script tag in the page to access the object. If needed, you could use messaging to communicate with your extension. For example, assuming the object you want to access in your page is called pageObject:
content1.js
//this code will add a new property to the page's object
var myOwnData = "createdFromContentScript";
var myScript = document.createElement("script");
myScript.innerHTML = "pageObject.myOwnData = " + myOwnData;
document.body.appendChild(myScript);
content2.js
//this code will read a property from the existing object and send it to background page
window.addEventListener("message", function(message) {
if (message.data.from == "myCS") {
chrome.runtime.sendMessage({theProperty: message.data.prop});
}
});
var myScript = document.createElement("script");
myScript.innerHTML = 'window.postMessage({from: "myCS", prop: pageObject.existingProperty},"*");';
document.body.appendChild(myScript);
No, there is no way. There is no point having the isolated worlds for security and then there being a workaround whereby an extension can hack the content script and variables if it really needs to.
Presumably the object on the page interacts with the page or has some effect on the page or something on the page affects the state of the variable. You can trigger actions on the page (via the DOM) that might change the state of that variable but you should stop looking for ways to access variables directly.
Of course if the page author is cooperative then it's a different ball game - a mechanism could be provided in the author's script, a getter and setter mechanism. But somehow I doubt that's what you're after.
How to access background script objects form a content script inside chrome extension?
In content script I have:
// this will store settings
var settings = {};
// load settings from background
chrome.extension.sendMessage({
name: "get-settings"
}, function(response) {
debugger;
settings = response.data.settings;
});
Inside the background script I have:
var Settings = function() {
var me = this;
// internal, default
var settingList = {
serverUrl : "http://automatyka-pl.p4",
isRecordingEnabled : true,
isScanEnabled : true
};
this.get = function( key ) {
return settingList[key];
};
this.set = function( key , value ) {
if (settingList[key] != value) {
var setting = {};
setting[key] = value;
chrome.storage.sync.set(setting, function() {
settingList[key] = value;
});
}
return true;
};
chrome.extension.onMessage.addListener(function(request, sender, sendResponse) {
if (request.name == 'get-settings') {
sendResponse({
data : {
settings : settings
}
});
return true;
}
});
var settings = new Settings();
Messaging works, i mean response is send but returned object is empty. Do you know how to solve that?
EDIT
Based on your comments and answer will try to add different light to my question.
The actual problem is:
How to access background "model" from content script.
Lets assume that content script continuously responds to page DOM changes. Any time changes are detected some processing is made inside content script. But this processing is depended on extension setting. Those setting can be set via page action popup script which informs background model what those settings are.
So, any time page is processed with content script it should be aware of current settings stored inside background script.
As already described pulling settings from background is an asynchronous process, so i need a callback for further processing inside content script. Further processing must wait for settings (so this should be handled synchronously?).
It's hard for my to imagine what program flow should look like in this case.
background loads (setting initialized)
page loads -> content script loads
content script requests settings -> further processing is done inside callback function.
user changes setting, background settings are changed
page change is triggered and content script responds
content script requests settings -> further processing is done inside callback function - but it cannot be the same function like in pt. 3 (content "model" does not have to be initialized)?
sendMessage doesn't transfer the object itself, but only its JSON-ifiable representation,
effectively objReceived = JSON.parse(JSON.stringify(objSent)), so since your object's settingList is invisible outside function context it's lost during serialization.
You can make it public by exposing a stringifiable property
this.settingList = { foo: 'bar' };
that would be transferred to your content script successfully.
Since messaging is asynchronous, to use the response in the content script you should do it inside the response callback:
// this will store the settings
var settings = {};
// load settings from background
chrome.runtime.sendMessage({
name: "get-settings"
}, function(response) {
settings = response.data.settings;
onSettingsReady();
});
function onSettingsReady() {
// put your logic here, settings are set at this point
}
To know if settings changed outside your content-script, in settings setter in background.js send messages to your tab's content-script:
this.set = function( key , value ) {
...
// notify active tab if settings changed
chrome.tabs.query({"windowType":"normal"}, function(tabs){
for( id in tabs ){
if("id" in tabs[id]){
chrome.tabs.sendMessage(tabs[id].id,{"action":"update-settings","settings":settings});
}
}
});
return true;
};
And in content-script listen and process this message:
chrome.runtime.onMessage.addListener(function(msg){
if("action" in msg && msg.action == 'update-settings'){
// You are setting global settings variable, so on it will be visible in another functions too
settings = msg.settings;
}
});
More details: https://developer.chrome.com/extensions/runtime#method-sendMessage.
P.S. Use chrome.runtime.sendMessage instead of chrome.extension.sendMessage as the latter is deprecated in Chrome API and totally unsupported in WebExtensions API (Firefox/Edge).
It would probably make more sense to have another instance of Settings in your content script.
After all, chrome.storage API is available in content scripts.
Of course, you need to watch for changes made in other parts of the extension - but you should be doing so anyway, since you're using chrome.storage.sync and its value can change independently by Chrome Sync.
So, proposed solution:
Add a listener to chrome.storage.onChanged and process changes to update your settingList as needed.
Move the Storage logic to a separate JS "library", e.g. storage.js
Load storage.js in your content script and use it normally.
You may also want to adjust your storage logic so that saved data is actually taken into account - right now it's always the default. You can do something like this:
var defaultSettingList = {
serverUrl : "http://automatyka-pl.p4",
isRecordingEnabled : true,
isScanEnabled : true
};
var settingList = Object.assign({}, defaultSettingList);
chrome.storage.sync.get(defaultSettingList, function(data) {
settingList = Object.assign(settingList, data);
// At this point you probably should call the "ready" callback - initial
// load has to be async, no way around it
});
I am puzzling my way through my first 'putting it all together' Chrome extension, I'll describe what I am trying to do and then how I have been going about it with some script excerpts:
I have an options.html page and an options.js script that lets the user set a url in a textfield -- this gets stored using localStorage.
function load_options() {
var repl_adurl = localStorage["repl_adurl"];
default_img.src = repl_adurl;
tf_default_ad.value = repl_adurl;
}
function save_options() {
var tf_ad = document.getElementById("tf_default_ad");
localStorage["repl_adurl"] = tf_ad.value;
}
document.addEventListener('DOMContentLoaded', function () {
document.querySelector('button').addEventListener('click', save_options);
});
document.addEventListener('DOMContentLoaded', load_options );
My contentscript injects a script 'myscript' into the page ( so it can have access to the img elements from the page's html )
var s = document.createElement('script');
s.src = chrome.extension.getURL("myscript.js");
console.log( s.src );
(document.head||document.documentElement).appendChild(s);
s.parentNode.removeChild(s);
myscript.js is supposed to somehow grab the local storage data and that determines how the image elements are manipulated.
I don't have any trouble grabbing the images from the html source, but I cannot seem to access the localStorage data. I realize it must have to do with the two scripts having different environments but I am unsure of how to overcome this issue -- as far as I know I need to have myscript.js injected from contentscript.js because contentscript.js doesn't have access to the html source.
Hopefully somebody here can suggest something I am missing.
Thank you, I appreciate any help you can offer!
-Andy
First of all: You do not need an injected script to access the page's DOM (<img> elements). The DOM is already available to the content script.
Content scripts cannot directly access the localStorage of the extension's process, you need to implement a communication channel between the background page and the content script in order to achieve this. Fortunately, Chrome offers a simple message passing API for this purpose.
I suggest to use the chrome.storage API instead of localStorage. The advantage of chrome.storage is that it's available to content scripts, which allows you to read/set values without a background page. Currently, your code looks quite manageable, so switching from the synchronous localStorage to the asynchronous chrome.storage API is doable.
Regardless of your choice, the content script's code has to read/write the preferences asynchronously:
// Example of preference name, used in the following two content script examples
var key = 'adurl';
// Example using message passing:
chrome.extension.sendMessage({type:'getPref',key:key}, function(result) {
// Do something with result
});
// Example using chrome.storage:
chrome.storage.local.get(key, function(items) {
var result = items[key];
// Do something with result
});
As you can see, there's hardly any difference between the two. However, to get the first to work, you also have to add more logic to the background page:
// Background page
chrome.extension.onMessage.addListener(function(message, sender, sendResponse) {
if (message.type === 'getPref') {
var result = localStorage.getItem(message.key);
sendResponse(result);
}
});
On the other hand, if you want to switch to chrome.storage, the logic in your options page has to be slightly rewritten, because the current code (using localStorage) is synchronous, while chrome.storage is asynchronous:
// Options page
function load_options() {
chrome.storage.local.get('repl_adurl', function(items) {
var repl_adurl = items.repl_adurl;
default_img.src = repl_adurl;
tf_default_ad.value = repl_adurl;
});
}
function save_options() {
var tf_ad = document.getElementById('tf_default_ad');
chrome.storage.local.set({
repl_adurl: tf_ad.value
});
}
Documentation
chrome.storage (method get, method set)
Message passing (note: this page uses chrome.runtime instead chrome.extension. For backwards-compatibility with Chrome 25-, use chrome.extension (example using both))
A simple and practical explanation of synchronous vs asynchronous ft. Chrome extensions
I'm making a websites that displays noise measurement data from different locations. The data for each location is captured on a sound level meter device and it is then read with a windows-based application. The application then uploads data on a web server as a .js file with an array variable in it. This .js files are refreshed every 5 minutes.
I first created a javascript application that displays live data for a single measuring unit. But now I need to display data on a map for all the locations. The problem is that the windows application on each location makes a file with the same name and same variables only on another location. I'm having some trouble with reading the correct data.
This is what I did so far:
function removejscssfile(filename, filetype){
var targetelement=(filetype=="js")? "script" : (filetype=="css")? "link" : "none" //determine element type to create nodelist from
var targetattr=(filetype=="js")? "src" : (filetype=="css")? "href" : "none" //determine corresponding attribute to test for
var allsuspects=document.getElementsByTagName(targetelement)
for (var i=allsuspects.length; i>=0; i--){ //search backwards within nodelist for matching elements to remove
if (allsuspects[i] && allsuspects[i].getAttribute(targetattr)!=null && allsuspects[i].getAttribute(targetattr).indexOf(filename)!=-1)
allsuspects[i].parentNode.removeChild(allsuspects[i]) //remove element by calling parentNode.removeChild()
}
}
function updateData(){
var numberOfNoiseSniffers = noiseSniffers.length-1;
var j = 0;
for (i=0;i<=numberOfNoiseSniffers;i++) {
file = '../'+ noiseSniffers[i] + "/" + "CurrentMeasurement.js";
$.include(file,function(){
laeq[j] = currentMeas[1][1];
lastUpdate[j] = currentMeas[0][1];
if (j==numberOfNoiseSniffers){
updateMarkers();
}
removejscssfile(file[0], "js");
j++;
});
}
t=setTimeout(function() { updateData() }, 300000);
}
$(function (){
map = new google.maps.Map(document.getElementById("gMap"), myOptions);
//noiseSniffers is an array where I have save all the folder names of different measurement locations
var numberOfNoiseSniffers = noiseSniffers.length-1;
var j = 0;
for (i=0;i<=numberOfNoiseSniffers;i++) {
var file = '../'+ noiseSniffers[i] + "/" + "CurrentMeasurement.js";
//I am using include plugin for jquery to include files because it has a callback for when a file is actually loaded
$.include(file,function(){
//a set of global arrays that keep the data from the loaded file and this data is then displayed in google maps markers
laeq[j] = currentMeas[1][1];
lastUpdate[j] = currentMeas[0][2];
latitude[j] = systemstats[12][5];
longitude[j] = systemstats[11][6];
//checking to see if I am in the process of including the last file
if (j==numberOfNoiseSniffers){
//a function that creates google maps markers
createMarkers();
}
//after that I remove the files that were just included and read
removejscssfile(file, "js");
j++;
});
}
setTimeout(function() { updateData() }, 300000);
});
I got the function for removing my .js file here: Dynamically removing an external JavaScript or CSS file.
And this is the jquery plugin for loading the .js file: Include File On Demand.
The initial load usually works (sometimes it happens that only one or no markers get loaded. But the update function mostly returns the same data for both locations.
So what I want to know is, how can I firstly make my code working and how to optimize it. I posted just the main parts of the javascript code, but I can provide all the code if it is needed. Thanks for any help.
I think you need some sort of JSONP-like solution.
Basically load data on the server side, then wrap it in a method call before returning it to client side. Your response should look something like this:
var location_data = [1,2,3,4]
updateLocation('location_id', location_data)
Now you define an updateLocation() function in your client side script. Now, every time you need new data, you create new 'script' tag with src pointing to your server side. When the response is loaded, your updateLocation() will be invoked with correct params.
I hope this is clear enough
You can maybe try some form of namespacing
i exactly dont understood your problem, but you may try this
//put your code inside an anonymous function and execute it immediately
(function(){
//your javascript codes
//create variable with same names here
//
})();