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
});
Related
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.
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 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.
I have a chrome extension that have a settings page. In the settings page I want to have preview and save buttons. I want to be able to pass temporary options object to my APPLICATION. How can I do it so that i don't have to rewrite APPLICATION twice? How to pass information to html page that this time it should access LocalStorage['permanent_settings'] and other LocalStorage['temporary_settings'], and render content using that object as a settings object. Also I want my code to execute locally, so I don't want any PHP etc.
You can add your conditions to this method, which allows you to dynamically add an external javascript file:
loadExternalScriptFile = function(filename) {
var fileref = document.createElement("script");
if (fileref){
fileref.setAttribute("type","text/javascript");
fileref.setAttribute("src", filename);
if (typeof fileref != "undefined")
document.getElementsByTagName("head")[0].appendChild(fileref);
}
}
//dynamically load and add this .js file
loadExternalScriptFile("myscript.js");
You should try holding the read source in local storage as well, in a similar manner to:
// At startup, defaulting to permanent_settings
function onLoad() {
LocalStorage['read_from'] = 'permanent_settings';
// Other initialization work
// ...
}
// When pressing Save
function onSaveClick() {
LocalStorage['read_from'] = 'permanent_settings';
}
// When pressing Preview
function onPreviewClick() {
LocalStorage['read_from'] = 'temporary_settings';
}
// When accessing the settings, read their source
function getSettingForKey(var key) {
var source = LocalStorage['read_from'];
// It can be either permanent_settings, or temporary_settings
var settingsArray = LocalStorage[source];
return settingsArray[key];
}
You can use jQuery to load the script and even execute code when it's loaded. You don't even have to wrap the code in a $(function(){ ... }) block:
$.getScript(chrome.extension.getURL("myscript.js"), function() {
alert("myscript.js has finished loading");
});
I'm attempting to override the default functionality for webkitNotifications.createNotification and via a Chrome extension I'm able to inject a script in the pages DOM that does so. Problem I'm having now is I need access to chrome.extension.sendRequest from the pages DOM in order to push my request to the NPAPI I have embedded in the background page. I previously had the embed object rendered on each page during the execution of the content-script - but believe it's more effective (and safe) if the NPAPI is embedded within the extension not injected on every page.
if (window.webkitNotifications)
{
(function()
{
window.webkitNotifications.originalCreateNotification = window.webkitNotifications.createNotification;
window.webkitNotifications.createNotification = function (iconUrl, title, body) {
var n = window.webkitNotifications.originalCreateNotification(iconUrl, title, body);
n.original_show = n.show;
n.show = function ()
{
console.log("Chrome object", chrome);
console.log("Chrome.extension object", chrome.extension);
chrome.extension.sendRequest({'title' : title, 'body' : body, 'icon' : iconUrl});
}
return n;
}
})();
}
That is what is injected in the DOM as a script element. The background page is as follows:
<embed type="application/x-npapiplugz" id="plugz">
<script>
var osd = document.getElementById('plugz');
function processReq(req, sender, callback)
{
osd.notify(req.title, req.body, req.image);
console.log("NOTIFY!", req.title, req.body, req.image);
};
chrome.extension.onRequest.addListener(processReq);
</script>
Once your extension includes a NPAPI plugin, it is no longer safe :) But your correct, instead of allowing every single page have access to the plugin, it is better to let your extension have it. I assume you know about the "public" property which specifies whether your plugin can be accessed by regular web pages, the default is false.
Below, I will explain whats your problem, it isn't a accessing NPAPI from DOM pages problem, it is basically why can't your notifications access your content script or extension pages.
As you noticed, access to the content scripts and the pages DOM are isolated from each other. The only thing they share, is the DOM. If you want your notifications override to communicate to your content script, you must do so within a shared DOM. This is explained in Communication with the embedding page in the Content Scripts documentation.
You could do it the event way, where your content script listens on such event for data coming from your DOM, something like the following:
var exportEvent = document.createEvent('Event');
exportEvent.initEvent('notificationCallback', true, true);
window.webkitNotifications.createNotification = function (iconUrl, title, body) {
var n = window.webkitNotifications.createNotification(iconUrl, title, body);
n.show = function() {
var data = JSON.stringify({title: title, body: body, icon: iconUrl});
document.getElementById('transfer-dom-area').innerText = data;
window.dispatchEvent(exportEvent);
};
return n;
}
window.webkitNotifications.createHTMLNotification = function (url) {
var n = window.webkitNotifications.createHTMLNotification(url);
n.show = function() {
var data = JSON.stringify({'url' : url});
document.getElementById('transfer-dom-area').innerText = data;
window.dispatchEvent(exportEvent);
};
return n;
};
Then your event listener can send that to the background page:
// Listen for that notification callback from your content script.
window.addEventListener('notificationCallback', function(e) {
var transferObject = JSON.parse(transferDOM.innerText);
chrome.extension.sendRequest({NotificationCallback: transferObject});
});
I added that to my gist on GitHub for the whole extension (https://gist.github.com/771033), Within your background page, you can call your NPAPI plugin.
I hope that helps you out, I smell a neat idea from this :)