Chrome extensions: chrome.storage.local.get headache - javascript

Trying to build a simple Chrome extension to output the whole contents of localStorage.
How difficult should it be? Spent a few hours researching and trying, still doesn't work :(
Appreciate assistance!
For example:
function load() {
chrome.storage.local.get(null, function(items) {
var allKeys = Object.keys(items);
alert(allKeys.count);
});
}
document.addEventListener('DOMContentLoaded', () => {
load();
});
outputs 'undefined'
Some other ways I tried actually contained an object, but I couldn't do anything with it (no count/length, no JSON.stringify, no toString() etc).
Manifest:
"permissions": [
"activeTab",
"storage",
"tabs",
"http://*/*",
"https://*/*",
"chrome://favicon/"
]
How can I output the whole localStorage within my extension popup?
Please help!

Could you try allKeys.length?
Object.keys(items) would return you an array. Array by default doesn't have a property count. I think you mean length.
If you need the webpage's local storage (the domain loaded in your tab), instead of the extension page's local storage, I don't think you can do that.
Iván Nokonoko had a nice point that you need to inject the script in order to interact with the loaded web page, however the problem is the injected script is forced to run in an isolated environment, in which you only have access to the DOM tree, but no access to the original JS environment, thus the localStorage variable that you can get, is the one in your own isolated env, rather than the original one.
Quoted from Chrome Content Scripts API:
However, content scripts have some limitations. They cannot:
Use variables or functions defined by web pages or by other content scripts
And I believe injecting code has the same restrication as content script.
I think this restriction is for safety consideration. It's OK to expose the DOM tree to extensions, in the worst case the extension would mess up the view. But if expose the JS env, such as the local storage to extension, then it could be dangerous, as extension is capable of accessing/tampering data.

To show the localStorage of currently active tab, try:
chrome.tabs.executeScript({code:'JSON.stringify(localStorage)'},function(result) {
console.log(JSON.parse(result[0]));
});
Working example:
popup.html
<body>
<div id=ls></div>
<script src="popup.js"></script>
</body>
popup.js
chrome.tabs.executeScript({code:'JSON.stringify(localStorage)'}, res => {
var ls = document.querySelector("#ls"), lsObj = JSON.parse(res[0]), lsHtml = "";
for (var key in lsObj)
lsHtml += "<p>" + key + " : " + lsObj[key] + "</p>";
ls.innerHTML = lsHtml ? lsHtml : "The active tab has nothing in localStorage";
});

Related

Dynamically inject content scripts - Chrome Extension

I'm trying to make a chrome extension that receives javascript code from a backend and saves it in localStorage (as base64) so I can later inject it as a content script when the right page is loaded, it does work most of the time except there are a few issues... First issue (not that important) is that I cannot access Chrome APIs (like chrome.storage or chrome.runtime.sendMessage), second issue is that it doesn't inject the correct code to child iframes... because location.href returns the URL of the top webpage and I couldn't find a way to access current URL of iframe within the iframe itself.
This is my code so far:
manifest.json
//....
"content_scripts": [{
"run_at": "document_end",
"all_frames": true,
"matches": [
"<all_urls>"
],
"js": [
"src/inject/InjectManager.js"
]
}],
//...
InjectManager.js:
// Some functions were not included for brevity
chrome.runtime.sendMessage({ action: "get_supported_urls" }, function(supported_urls) {
let current_url = window.location.href;
// Check if we support current_url
let js_code_to_inject = isWebsiteSupported(supported_urls, current_url); // this function returns string that is javascript code.
if(js_code_to_inject){
// Append the code to the body
let script = document.createElement("script");
script.type = "text/javascript";
script.innerHTML = js_code_to_inject;
document.body.appendChild(script);
}
});
As you see, I'm kind of trying to recreate what chrome already does in manifest.json's "content_script" section because my javascript code is dynamic.
Note: I know this is not allowed on chrome store and such, this extension is not to be shared with anyone.
Thanks for reading.
Any help would be greatly appreciated.
I cannot access Chrome APIs (like chrome.storage or chrome.runtime.sendMessage)
Your code currently makes a page script, not a content script. For the latter you need to use chrome.tabs.executeScript (see also content script documentation) in the background script.
location.href returns the URL of the top webpage and I couldn't find a way to access current URL of iframe within the iframe itself.
No, what you're describing simply cannot happen, it would be a doomsday-level violation of URL origin security, so something else is happening. For example, your manifest.json doesn't have match_about_blank meaning InjectManager.js doesn't process the dynamically added about:blank frames at all.
manifest.json:
"content_scripts": [{
"match_about_blank": true,
.....................
InjectManager.js:
chrome.runtime.sendMessage({ action: 'inject', data: location.href });
background script:
chrome.runtime.onMessage.addListener(({ action, data }, sender, sendResponse) => {
if (action === 'inject') {
chrome.tabs.executeScript(sender.tab.id, {
code: getInjectionCode(data),
frameId: sender.frameId,
});
}
});
Note that some iframes like javascript: or srcdoc: won't run content scripts in Chrome at all so you'll have to process them directly in InjectManager.js as they cannot be injected by executeScript(). For example you can find all iframes by using document.querySelectorAll('iframe') and create DOM script element inside like you do currently but you'll use frameElement.contentDocument instead of document and of course you'll check whether the iframe's real URL (frameElement.contentWindow.location.href) doesn't start with http because frames can be navigated inside without changing their src attribute on the outside. Do the check inside try/catch because accessing an iframe from a different origin will throw.

Chrome extension express url

So At the moment, My code is listening in, and I want it to listen and then see any url which belongs under "https://www.stackoverflow.com/questions/*"
How do I go about using the asterisk in my current code to make sure that all of the website can use my function ?
var regex = /^((ftp|http|https):\/\/)?(www.)?(?!.*(ftp|http|https|www.))[a-zA-Z0-9_-]+(\.[a-zA-Z]+)+((\/)[\w#]+)*(\/\w+\?[a-zA-Z0-9_]+=\w+(&[a-zA-Z0-9_]+=\w+)*)?$/gm;
var str = ["http://www.whatevershop this is/jackets/#", "https://www.whatevershop this is/shoes/#"];
var matches_array = str.match(regex);
chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab) {
chrome.extension.getBackgroundPage().console.log(tab.url);
if (matches_array == tab.url) {
chrome.tabs.executeScript(null, {
file: "test.js"
});
}
});
The issue is when I use the asterisk (*) at the end of the slash (/) it doesn't actually let me use every single url, only the one which I enter ?
Any fixes ?
UPDATE:
Ive edited it as told, including the url and function still wont run :(
var regex = /^((ftp|http|https):\/\/)?(www.)?(?!.*(ftp|http|https|www.))[a-zA-Z0-9_-]+(\.[a-zA-Z]+)+((\/[\w#-]+)*(\/\w+\?[a-zA-Z0-9_]+=\w+(&[a-zA-Z0-9_]+=\w+)*)?$/gm;
And the str code is var str = ["http://www.whateversiteyouneed.com/shop/#", "https://www.whateversiteyouneed.com/shop/#"];
Bare in mind on the site, there are different categories, hence why the # is in place to allow it to use all of the different locations.
I'm still wondering why the script wont run though.
It's something in the script which isn't actually seeing the new url ?
If you only want your script executed on specific urls then in your manifest add a section for content_scripts with an array of urls allowed. This is a lot simpler than using tab update events and regex and has wildcards
e.g.
"content_scripts": [
{
"matches": ["*://*.whatever.com/dogs/*"],
"js": ["test.js"],
"run_at": "document_end",
}
]
Note it's an array of arrays, so you can put as many urls in 'matches' as you wish and with the outer array, you could have different scripts on different domains. Not that that's a common use case. See extension match patterns
With this approach, the script is injected automatically and by default is inject once the page is idle, which is usually equivalent to after document ready. I usually use document_end if my code is to execute immediately. If the code will be invoked by a message from the background, e.g. by context menus, you can load at start.

opening window and navigating through page

I am trying to navigate a webpage with a content script. However the function keeps running because every time the page changes it runs through the script. I was wondering if there was a better way to do this as it is a resource hog and also disallows the user to interact with the page because of the constant refreshing.
Here's the code in question, sorry if it looks weird. I do not have more then a few weeks of knowledge in jQuery.
$(document).ready(function(){
console.log("Made it here" + window.location.href);
loc = window.location.href;
match = loc.match('http://url.com/uc/');
if (match) {
window.location = "http://url.com/uc/health/index/1";
console.log("2 here");
window.location = "http://url.com/uc/health/communication/createSelectTemplate";
console.log("3 here");
chrome.storage.local.get('formOption', function(result) {
document.getElementById('formTemplate').value = result.formOption;
document.forms[0].submit();
});
}
});
The reason i have to navigate three windows before using values because whoever made this website has timeout cookies and the pages can not be called before the previous one loads.
It is a content script tho, so all the code is on the next page. Maybe if there was a way to check exact url? But when i was playing with that awhile back the computer didn't distinguish between.
urlhere.com/uc/
and
urlhere.com/uc/health/index/1
Every time you navigate (e.g. immediately after assigning window.location), your script stops executing is unloaded with the page, and when the next page loads, the content scripts are indeed loaded again. If the same script is loaded, with the same initial state, it will of course execute the same operation.
Possible solutions (there are many):
Be more precise with your matching (= better notice the actually changing state).
loc.match('http://url.com/uc/') will only check that the address includes that string - which all URLs you show do. Why not just use loc == 'http://url.com/uc/' (and check for intermediate pages)?
Use fine-grained content scripts (= load different scripts).
Manifest defines which pages get what scripts on load. I assume you have something like this:
"content_scripts" : [{
"js" : ["jquery.js", "content1.js"],
"matches": ["http://*"]
}]
You can make several scripts and let Chrome parse the URLs. For example, content1.js will do the first redirect, content2.js will do the second.
"content_scripts" : [{
"js" : ["jquery.js", "content1.js"],
"matches": ["http://url.com/uc/"]
}, {
"js" : ["jquery.js", "content2.js"],
"matches": ["http://url.com/uc/health/index/1"]
}]
Use some persistent state (that persists between navigation) to indicate which stage of redirect are you on (= control changing state yourself).
The page's sessionStorage is ideal for this, as it's only persistent within the tab:
if (match) {
switch (sessionStorage.redirectStage) {
case 3:
// We're at the final page, do actual work
break;
case 2:
sessionStorage.redirectStage = 3;
window.location = "http://url.com/uc/health/communication/createSelectTemplate";
break;
default: // Includes initial state when it's unset
window.location = "http://url.com/uc/health/index/1";
}
}

chrome.runtime.sendMessage raises "Uncaught TypeError: Cannot call method 'sendMessage' of undefined "

I am writing a google chrome extension and trying to send information from a piece of code that is injected into a web page to my content script.
According to http://developer.chrome.com/extensions/messaging#external-webpage, I should use something like :
// The ID of the extension we want to talk to.
var editorExtensionId = "abcdefghijklmnoabcdefhijklmnoabc";
// Make a simple request:
chrome.runtime.sendMessage(editorExtensionId, {openUrlInEditor: url},
function(response) {
if (!response.success)
handleError(url);
});
The problem is, when I do :
var script_code = "var msg = {message : 'plop'};\n";
script_code += "chrome.runtime.sendMessage('" + chrome.i18n.getMessage("##extension_id") + "', msg, function(response) {\n";
script_code += " console.log('response received');\n";
script_code += "});\n";
An then inject this to the webpage, when it is executed, I get :
Uncaught TypeError: Cannot call method 'sendMessage' of undefined
Can anyone help me through this ?
Thanks
javaScript code in Chrome extensions can be divided in the following groups:
Extension code - Full access to all permitted chrome.* APIs.
This includes all extension pages(background page, popup page ,option page, and so on)
Content scripts (via the manifest file or chrome.tabs.executeScript) - Partial access to some of the chrome APIs
Full access to the page's DOM.
Injected scripts (via this method in a Content script) - Full access to all properties in the page. No access to any of the chrome. APIs.*
Execute on the actual page, can't access any of the chrome.* APIs.**.
In your case, code is execute on the actual page, can't call chrome.runtime.* APIs.
Maybe, you can try window.postMessage().
Please check your manifest file and if you are using localhost ensure that matches are correct
Incorrect:
"externally_connectable": {
"matches": ["https://localhost:PORT_NUMBER/\*"]
}
Correct:
"externally_connectable": {
"matches": ["\*://localhost/\*"]
}
if its not localhost, please ensure that matches contains mask of at least 2 levels of domains as described in documentation
matches (array of string) - optional
The URL patterns for web pages that are allowed to connect. This does not affect content scripts. If left empty or unspecified, no web pages can connect.
Patterns cannot include wildcard domains nor subdomains of (effective) top level >domains; *://google.com/* and http://*.chromium.org/* are valid, while <all_urls>, >http://*/*, *://*.com/*, and even http://*.appspot.com/* are not.
Well, in fact, I included the wrong version of the manifest.json file.
My bad, this is where you specify that you want to expose the messaging API to certain sites.
Problem solved.

Chrome extension regarding injected script + localstorage

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

Categories