Use extension's localStorage wrapper function in an injected script - javascript

My extension's content script injects a script into gmail page via <script> element (main.js). The injected script needs some data from the settings stored in the extension's localStorage by options.js script of the options page.
The options page script can successfully use loadDomain() function that reads localStorage.domain value. This function is defined in a common functions script storage.js that is also injected on gmail page via <script> element along with main.js.
The problem is that loadDomain() returns undefined when called in the injected main.js instead of the actual values stored on the options page.
manifest.json:
"permissions": [
"tabs", "https://mail.google.com/*", "http://*/*, https://*/*"
],
"background": {
"scripts": ["js/background.js"],
"persistent": false
},
"browser_action": {
"default_icon": {
"38": "icon.png"
},
"default_title": "SalesUp",
"default_popup": "index.html"
},
"content_scripts": [
{
"matches": ["https://mail.google.com/*"],
"js": ["content.js"]
}
],
"web_accessible_resources": [
"js/jquery-1.10.2.min.js",
"js/gmail.js",
"main.js"
]
}

The chat discussion showed that loadDomain() was invoked from the <script>-injected main.js.
One part of the problem was caused by the fact that Chrome isolates the web page (with its scripts, also the injected ones) from the content scripts, as well as the background page. Another part was that localStorage is different on each domain (actually, origin), so whatever was stored inside the options page of the extension was not available in a content script that runs in the context of the web page and has access to its localStorage only, not the extension's localStorage.
The solution comprises two things:
instead of localStorage use chrome.storage.sync to store the extension settings or chrome.storage.local to store the temporary stuff that shouldn't be synced to Google servers.
use custom DOM-events to communicate between the injected script and the content script.
The code:
Injected main.js:
sending a request (detail key may be used to pass some data):
document.dispatchEvent(new CustomEvent("getDomains", {detail: {something: "hello"}}));
listening for a response:
document.addEventListener("receiveDomains", function(e) {
var domains = e.detail;
console.log("Received domains", domains);
...............
});
Content script content.js:
document.addEventListener("getDomains", function(e) {
chrome.storage.sync.get("domains", function(result) {
document.dispatchEvent(new CustomEvent("receiveDomains", {detail: result.domains}));
});
});
Options page options.js:
function save_options() {
chrome.storage.sync.set({domains: ["domain1", "domain2"]});
}

Related

XSS "Blocked a frame with origin from accessing a cross-origin frame" error in content script for a Chrome extension

I've had this extension in the Google Chrome store for a while now. After doing a maintenance update I noticed that the following line from the content.js (content script):
//Get top document URL (that is the same for all IFRAMEs)
var strTopURL = window.top.document.URL;
is now throwing the following exception when the loaded page has an IFRAME in it:
Blocked a frame with origin "https://www.youtube.com" from accessing a
cross-origin frame.
Like I said, it used to be the way to obtain the top document URL for your extension (from the content script). So what's the accepted way to do it now?
PS. Again, I'm talking about a Google Chrome extension (and not just a regular JS on the page.)
EDIT: This script is running under the content_scripts in the manifest.json that is defined as such:
"content_scripts": [
{
"run_at": "document_end",
"all_frames" : true,
"match_about_blank": true,
"matches": ["http://*/*", "https://*/*"],
"js": ["content.js"]
}
],
The content script should ask your background script to do it via messaging:
chrome.runtime.sendMessage('getTopUrl', url => {
// use the URL here inside the callback or store in a global variable
// to use in another event callback that will be triggered in the future
console.log(url);
});
// can't use it right here - because the callback runs asynchronously
The background script should be declared in manifest.json:
"background": {
"scripts": ["background.js"],
"persistent": false
},
You'll also need need specific URL permissions in manifest.json or allow all URLs:
"permissions": ["<all_urls>"]
And the listener in the background script:
chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
if (msg === 'getTopUrl') {
chrome.tabs.get(sender.tab.id, tab => sendResponse(tab.url));
// keep the message channel open for the asynchronous callback above
return true;
}
});

Getting a token value out of localStorage with a Chrome extension content-script

I am new to JavaScript and chrome extensions. I am writing an extension that needs to get a value out of the local storage of the web page to which it is a page action. Here is a snippet of manifest.
manifest.json:
{
"content_scripts": [
{
"matches": ["http://url/*"],
"js": ["user_story_template.js"]
}
],
"page_action": {
"default_title": "Promotify",
"default_icon": "promotify_20.png",
"default_popup": "popup.html"
},
"permissions": [
"tabs",
"http://url/*",
"declarativeContent",
"storage"
]
}
Here is a snippet of the content script:
function get_token(){
//alert("Starting processing...");
var auth_token = "";
chrome.storage.local.get('token', function(result){
auth_token = result.token;
});
I have looked for answers and a lot of them say that this way should work. I should be able to do this from the content script. However, am not getting any value. I have tried using HTML also to no avail. When I go into the inspector on the page and look under resources there is a value for token in local storage.Can anyone see what I might be doing wrong? I am sure that I am missing something.
Some of the places I have looked for the answer:
Easy way to get Local Storage Value to Content Script [Chrome Extension]
Access chrome local storage used in both content script and popup?
chrome.storage will only get your extension's storage, to get the storage belonging to the current page, you need to use Window.localStorage in content scripts
var token = localStorage.getItem("token");

Can I share code between different parts of Chrome Extension?

Let's say, I have a function:
var rand = function(n) {
return Math.floor(Math.random() * n);
}
Can I use this function in both Content Script and Background Script without copypaste?
Thank you.
Yes.
You could have an external JS file which is loaded as part of the background and the content script (like any normal JS file). Just add it to the background and content script file arrays in the manifest and it will be loaded for you.
For example if our shared function reside in sharedFunctions.js, the content script using them is in mainContentScript.js and the background code in mainBackground.js (all in the js subfolder) we can do something like this in the manifest:
"background": {
"scripts": [ "js/sharedFunctions.js", "js/mainBackground.js" ]
},
"content_scripts": [
{
"matches": ["*://*/*"],
"js": ["js/sharedFunctions.js", "js/mainContentScript.js"]
}
]
Note: Make sure to load it in the right order, before other files using it.
Or you can also add it as a script tag (with a relative URL) in the background.html and in the manifest only add it to the content script's JS array. So the manifest will look like:
"background": {
"page": "background.html"
},
"content_scripts": [
{
"matches": ["*://*/*"],
"js": ["js/sharedFunctions.js", "js/mainContentScript.js"]
}
]
and the background.html file will have this script tag:
<script type="text/javascript" src="js/sharedFunctions.js"></script>
Edit: Also, For sharing with other scopes in other parts of the extension (unfortunately not available in the content script).
You can also have the functions you want to share reside in the background script and reach them via chrome.runtime.getBackgroundPage() which you can read more about here:
https://developer.chrome.com/extensions/runtime#method-getBackgroundPage
So, let's say you have your rand() function declared as a global variable in the background script (which means it's a property on the background's window object), you can do something of this sort at the beginning the script in the other scope (this can be a popup context like a browserAction window):
var background, newRandomNumber;
chrome.runtime.getBackgroundPage(function(backgroundWindow){
background = backgroundWindow;
newRandomNumber = background.rand();
})
This way you can also use the variable background to access any property or method set on the background's window object. Be mindful, that this function runs asynchrounusly, meaning that only after the callback is called will the variables background and newRandomNumber will be defined.
Yes, you can, by putting it in a separate JS file and loading it in both.
Say, you have a file utils.js that contain all such functions.
Then you can load the background page like this:
"background": {
"scripts": [ "utils.js", "background.js" ]
},
And the content script like this:
"content_scripts": [
{
"matches": ["..."],
"js": ["utils.js", "content.js"]
}
],
Or, if you're using programmatic injection, chain the calls like this:
chrome.tabs.executeScript(tabId, {file: "utils.js"}, function(){
chrome.tabs.executeScript(tabId, {file: "content.js"}, yourActualCallback);
});

How to query external domain, store it within chrome extension, and call it to display in popup.html?

When I load an https file within popup.html, it cases a significant delay.
To circumvent this issue, how can I put a function in background.js which, each time the browser is opened, fetches the version number and stores it locally?
Then, once stored locally, there is no latency when trying to fetch this internal resource (as opposed to trying to load an external https javascript file every time you click the extension popup button).
And after that, how can I call said internal resource inside the popup.html?
SUMMARY:
* how to fetch remote text value on each initial browser load?
* how to store that value locally?
* how to fetch the now locally stored number to display in popup.html?
The entire external file is simply this:
function myVersion1(){ return "1.0"; }
In other words, I want to store the value of 1.0, locally, to be later called in the popup.html. Then say I changed the number in the external file to 2.0, then I want the extension to query this and update the previous 1.0 which is stored locally, to 2.0 the next time the user opens their browser.
You would use Chrome local storage, you would store a value like this:
chrome.storage.local.set({'foo': bar});
and retrieve it like this:
chrome.storage.local.get('foo', function (result) {
var foo = result['foo'];
if(typeof foo === 'undefined') // not set
});
If you do this, you will need to add this into your manifest file:
"permissions" : ["storage"],
I'm far from a Chrome extension expert, but I having a partially similar problem with a 3rd party simple remote HTTP based service that I want to integrate into a Chrome extension. This might help with the external domain query part, you can combine it with the local storage suggestions from Matthew Riches.
From my popup.js I include an AJAX request to the remote server. You can perform it from a background.js on a timed interval if you need to.
var status = "http://192.168.1.28:8081/";
window.onload = function(){
$.ajax({
type: 'GET',
url: status,
dataType: 'html',
success: function(data) {
try {
// Do what you want with the data, like local storage, etc...
} catch(e) {
$('#status').text("Error: " + e.message);
return;
}
}
});
}
You'll need to add "permissions" for the remote server's domain/IP in your manifest.json though. If you don't an AJAX request like this would be considered a cross-domain request security violation. e.g. you're not typically allowed to, under the covers, request data from another domain like www.google.com loading AJAX from www.mybadsite.com.
{
"name": "Test",
"version": "0",
"background": {"page": "background.html"},
"manifest_version": 2,
"browser_action": {
"default_title": "Test",
"default_popup": "status.html",
"icons": ["icon.png"],
"default_icon": "icon.png"
},
"content_scripts": [ {
"js": [ "jquery.js", "content.js" ],
"css": ["customStyles.css"],
"matches": [ "http://*"]
}],
"permissions": [
"http://192.168.1.28/"
]
}
There are other ways of sharing data between content scripts. You can add an event listener to the DOM/document, then dispatch to it from somewhere else that's running, like background.js and popup.js. Here's another SO post with a simple example that works: (Note that it has nothing to do with external querying, see my note above for more on it, or search for CORS)
How do I pass back data from a remote window to a chrome extension's background page?

How to log fetched network resources in JavaScript?

Is there a way to access the list of resources that the browser requested (the ones found in this Chrome inspector's network panel)?
I would like to be able to iterate through these fetched resources to show the domains that have been accessed, something like:
for (var i = 0; i < window.navigator.resources.length; i++) {
var resource = window.navigator.resources[i];
console.log(resource); //=> e.g. `{domain: "www.google-analytics.com", name: "ga.js"}`
}
Or, maybe there is some event to write a handler for, such as:
window.navigator.onrequest = function(resource) {
console.log(resource); //=> e.g. `{domain: "www.google-analytics.com", name: "ga.js"}`
}
It doesn't need to work cross browser, or even be possible using client-side JavaScript. Just being able to access this information in any way would work (maybe there's some way to do this using phantomjs or watching network traffic from a shell/node script). Any ideas?
You can do this, but you will need to use Chrome extensions.
Chrome extensions have a lot of sandbox-style security. Communication between the Chrome extension and the web page is a multi-step process. Here's the most concise explanation I can offer with a full working example at the end:
A Chrome extension has full access to the chrome.* APIs, but a Chrome extension cannot communicate directly with the web page JS nor can the web page JS communicate directly with the Chrome extension.
To bridge the gap between the Chrome extension and the web page, you need to use a content script . A content script is essentially JavaScript that is injected at the window scope of the targeted web page. The content script cannot invoke functions nor access variables that are created by the web page JS, but they do share access to the same DOM and therefore events as well.
Because directly accessing variables and invoking functions is not allowed, the only way the web page and the content script can communicate is through firing custom events.
For example, if I wanted to pass a message from the Chrome extension to the page I could do this:
content_script.js
document.getElementById("theButton").addEventListener("click", function() {
window.postMessage({ type: "TO_PAGE", text: "Hello from the extension!" }, "*");
}, false);
web_page.js
window.addEventListener("message", function(event) {
// We only accept messages from ourselves
if (event.source != window)
return;
if (event.data.type && (event.data.type == "TO_PAGE")) {
alert("Received from the content script: " + event.data.text);
}
}, false);
`4. Now that you can send a message from the content script to the web page, you now need the Chrome extension gather up all the network info you want. You can accomplish this through a couple different modules, but the most simple option is the webRequest module (see background.js below).
`5. Use message passing to relay the info on the web requests to the content script and then on to the web page JavaScript.
Visually, you can think of it like this:
Full working example:
The first three files comprise your Google Chrome Extension and the last file is the HTML file you should upload to http:// web space somewhere.
icon.png
Use any 16x16 PNG file.
manifest.json
{
"name": "webRequest Logging",
"description": "Displays the network log on the web page",
"version": "0.1",
"permissions": [
"tabs",
"debugger",
"webRequest",
"http://*/*"
],
"background": {
"scripts": ["background.js"]
},
"browser_action": {
"default_icon": "icon.png",
"default_title": "webRequest Logging"
},
"content_scripts": [
{
"matches": ["http://*/*"],
"js": ["content_script.js"]
}
],
"manifest_version": 2
}
background.js
var aNetworkLog = [];
chrome.webRequest.onCompleted.addListener(function(oCompleted) {
var sCompleted = JSON.stringify(oCompleted);
aNetworkLog.push(sCompleted);
}
,{urls: ["http://*/*"]}
);
chrome.extension.onConnect.addListener(function (port) {
port.onMessage.addListener(function (message) {
if (message.action == "getNetworkLog") {
port.postMessage(aNetworkLog);
}
});
});
content_script.js
var port = chrome.extension.connect({name:'test'});
document.getElementById("theButton").addEventListener("click", function() {
port.postMessage({action:"getNetworkLog"});
}, false);
port.onMessage.addListener(function(msg) {
document.getElementById('outputDiv').innerHTML = JSON.stringify(msg);
});
And use the following for the web page (named whatever you want):
<!doctype html>
<html>
<head>
<title>webRequest Log</title>
</head>
<body>
<input type="button" value="Retrieve webRequest Log" id="theButton">
<div id="outputDiv"></div>
</head>
</html>
Big shoutout to #Elliot B.
I essentially used what he did but I wanted events to trigger in the content script rather than listeners triggering in the background. For whatever reason, I was unable to connect to the port from the background script so this is what I came up with.
PS: you need jquery.js in the extension folder to make this work.
manifest.json
{
"manifest_version": 2,
"name": "MNC",
"version": "0.0.1",
"description": "Monitor Network Comms",
"permissions":["webRequest","*://*/"],
"content_scripts": [{
"matches": ["<all_urls>"],
"run_at": "document_start",
"js": ["content.js",
"jquery.js"]
}],
"background": {
"scripts": ["background.js"]
}
}
background.js
var aNetworkLog = [];
chrome.webRequest.onResponseStarted.addListener(
function(oCompleted) {
var sCompleted = JSON.stringify(oCompleted);
aNetworkLog.push(sCompleted);
},{urls: ["https://*/*"]}
);
chrome.extension.onConnect.addListener(function (port) {
chrome.webRequest.onResponseStarted.addListener(
function(){
port.postMessage({networkLog:JSON.stringify(aNetworkLog)});
},{urls: ["https://*/*"]}
);
port.onMessage.addListener(function (message) {
if (message.disconnect==true) {
port.disconnect();
}
});
});
content.js
div = $('<div id="outputDiv" style="float:left;max-width:fit-content;position:fixed;display:none;"></div>').appendTo(document.body);
var port = chrome.extension.connect({name:'networkLogging'});
port.onMessage.addListener(function (message) {
if (message.networkLog) {
div[0].innerHTML = message.networkLog;
}
});
observer = new WebKitMutationObserver(function(mutation,observer){
JSON.parse(mutation[0]['target'].innerHTML).forEach(function(item){
JSON.parse(item);
})
});
observer.observe(div[0],{childList:true});
This is definitely not the most efficient way of doing things but it works for me. Thought that I would add it in here just in case someone is needing it.

Categories