How should Urls from User Options be matched in Content_Script? [duplicate] - javascript

This question already has answers here:
Optionally inject Content Script
(3 answers)
Closed 7 years ago.
I am writing an extension that has options which let users decide which sites they want the extension to run on.
Say the user has this site in the options
site action
stackoverflow.com/* change background css to blue
google.com/* change background css to green
I store these string in the options. When the content_script runs should I retrieve these strings from options, loop through each one, parse with a urlParser into parts, turn each part into a regex (escaping everything but *), and compare it with document.URL? I recently read that this kind of user options validation for Urls should be done through a background script too so I'm not sure which way to go or if there's a more obvious way to do it.
I think extensions like Adblocker and Vimium seem to have this functionality but for deciding which sites not to run on. I want to figure out how to decide which sites to run on.
Update to the Question: Since my content_script needs to run at document_start (before the page is loaded since it deals with editting the page appearance) as a content_script, will the background page be able to execute the content_script before the webpage is loaded at all?

Validated a web page's url should be "validated" via a background page because the user's options will be hosted in local storage in the context of the background page. Here is what I would do... (although it's more of a suggestion that an answer).
I am not sure how the actions on the right column of your list factor into your question, sorry.
(Also note, you would need to incorporate a library (external or self-written) that can parse globs into regex.)
manifest.json
permissions: ["tabs", "storage", "webRequest", "<all_urls>"]
background.js
//allow the webrequest to run on all urls
var filter = { urls: "<all_urls>" };
//receives url information from webrequest listener
function listen(details) {
getUserOpts()
.then(function(arrayOfWhitelistUrls) {
//you can't use globs here, need to use more powerful filtering mechanisms
if (arrayOfWhitelistUrls.indexOf(details.url) > -1) {
message();
}
});
}
//returns a promise containing user defined whitelist urls from background local storage
function getUserOpts() {
return new Promise(function(res, rej) {
chrome.storage.get("whitelist", function(data) {
//you are saving all localhost data as a string, so you need to parse it first
res(JSON.parse(data));
});
});
}
//messages content script and allows execution
function message() {
chrome.tabs.query({active: true}, function(tabs) {
chrome.tabs.sendMessage(tabs[0].id, {permission: true});
});
}
chrome.webRequest.onBeforeRequest.addListener(listen, filter)
contentscript.js
function listen(message) {
if (message.permission) {
if (message.permission === true) {
init();
}
}
}
//listen for message
chrome.runtime.onMessage.addEventListener(listen);
So the order in which things run is like:
background page listens to each web request
on each web request, the background page asynchronously fetches user options from local storage
if the url of the current tab passes your filter, message your content script
the content script receives the message and then runs
There might be an easier way to do this; the downside to this method is that you need to include the permission gateway in every content script you have.

Related

Accessing the DOM of a programatically opened tab that contains a different domain than the active tab

Overview of the problem
I cannot access the DOM of a window that I open programmatically in my chrome extension. I believe this may be due to origin/cross-site scripting restrictions. I have tried to give it the maximal permissions I can, including "<all_urls>", "activeTab" and "tabs"
I also played around with "content_security_policy_" settings but from the documentation I was hoping rather than expecting that to help.
I have a simple extension that runs code when the button is clicked, I want to open a few different tabs (but from different domains) and then access the DOM of each. However, it only seems to work if I'm on a tab of a domain and then I open another tab of the same of domain. Then I can access, for example, window onload of the new tab. I have no luck when it is a different domain.
e.g. if I press the button with activeTab "foo.com" then if it window.opens a new tab "foo.com/something" then I can access the document of that opened tab. But if it was "bar.com" then I wouldn't be able to access "foo.com/something"'s DOM
p.s. please note that executeScripts is used instead of manifest content scripts because it is necessary for my code to work. I must inject at least some of the files there this way, otherwise my code will not work (for reasons that are not completely apparent in the example)
my Question
What's way(s) can I get around this - I mean be able to access the DOM of any tab that I open, regardless of what site is in the active tab when the extension button is pressed in the toolbar?
Should I inject content scripts into a a tab that has been opened with window.open and somehow pass its document to Code.js? If so, how could I go about doing that? Can I somehow pass the document to the background.js? and somehow pass it to the injected Code.js?
If this will work (get around security restrictions) then can these content scripts be injected programatically (I don't know exactly what sites they will be until runtime, so I can't really specify them in the manifest)
or is there some way I can just relax the security restrictions and be able to directly access window.open's returned window's document? (which as I mentioned above currently only works on same-domain sites)
background.js
// this is the background code...
// listen for our browerAction to be clicked
chrome.browserAction.onClicked.addListener(function (tab) {
executeScripts(null, [ // jquery can be inserted here: https://stackoverflow.com/questions/21317476/how-to-use-jquery-in-chrome-extension
{ file: "lib/jquery-3.2.1.js" },
{ file: "lib/kotlin.js" },
{ file: "Code.js" } // don't include it in manifest "content_scripts" because it gives an error about kotlin.js not present that way
])
});
/*
* https://stackoverflow.com/questions/21535233/injecting-multiple-scripts-through-executescript-in-google-chrome
*/
function executeScripts(tabId, injectDetailsArray)
{
function createCallback(tabId, injectDetails, innerCallback) {
return function () {
chrome.tabs.executeScript(tabId, injectDetails, innerCallback);
};
}
var callback = null;
for (var i = injectDetailsArray.length - 1; i >= 0; --i)
callback = createCallback(tabId, injectDetailsArray[i], callback);
if (callback !== null)
callback(); // execute outermost function
}
code flow
background.js
Code.jscalls a function that starts with var w = window.open(url)
then w.addEventListener("load", ...), which is the problem. It only fires if the url belongs to the same domain as the active tab. (onLoad can't be used any-which-way, it never fires that's why I use addEventListener)
notes
I have "manifest_version": 2
I need to inject this way rather than include content scripts in the manifest (I guess I could put the jquery library in the manifest but the others I can't and I prefer to keep all injections together in the "code")

How do I inject certain files into certain websites from a chrome extension

In my chrome extension I have multiple files that inject script into websites, I want to know how I can inject 1 file for a certain website. To better explain
My chrome extension has 1 file to inject into Github and another file to inject into a different website how would I inject each one into there own website without having both files injecting into each website. So each file will be injected into there own website.
Injecting JavaScript files is done either through an entry in the content_scripts key in your manifest.json, or through chrome.tabs.executeScript().
Using chrome.tabs.executeScript()
You should use chrome.tabs.executeScript() if you don't need your script injected every single time the matching URL is loaded. This is particularly the case when interaction with your extension begins with the user clicking on a browserAction, or pageAction button.
Given that chrome.tabs.executeScript() is a direct JavaScript API call, I am going to assume that you understand how to make a choice as to what to inject based on the URL from the tab.url property.
Injecting stackContent.js could look like:
chrome.tabs.executeScript(tabId,{
file: "stackContent.js"
}, result => {
handleExecuteScriptAndInsertCSSErrors(tabId);
});
Injecting both jquery.js and stackContent.js could look like:
chrome.tabs.executeScript(tabId,{
file: "jquery.js"
}, result1 => {
handleExecuteScriptAndInsertCSSErrors(tabId);
chrome.tabs.executeScript(tabId,{
file: "stackContent.js"
}, result2 => {
handleExecuteScriptAndInsertCSSErrors(tabId);
});
});
You should also be checking for errors reported by chrome.runtime.lastError. A function I use for doing so in both Chrome and Firefox WebExtensions is:
function handleExecuteScriptAndInsertCSSErrors(tabId) {
if(chrome.runtime.lastError) {
let message = chrome.runtime.lastError.message;
let isFirefox = !!window.InstallTrigger;
let extraMessage = tabId ? 'in tab ' + tabId + '.' : 'on this page.';
if((!isFirefox && message.indexOf('Cannot access a chrome:') > -1) //Chrome
|| (isFirefox && message.indexOf('No window matching') > -1) //Firefox
) {
//The current tab is one into which we are not allowed to inject scripts.
// This is most likely because we are injecting based on a action button
// click. You should disable the action button when a tab is active on
// which you can not perform the task that is expected by the user.
let messageText= 'This extension, ' + chrome.runtime.id
+ ', does not work ' + extraMessage;
//alert(messageText); //Use for testing
console.log(messageText);
} else {
//Report the error
if(isFirefox) {
//In Firefox, runtime.lastError is an Error Object including a stack trace.
console.error(chrome.runtime.lastError);
} else {
console.error(message);
}
}
}
}
Using the content_scripts key in manifest.json
In your manifest.json you have a content_scripts key which describes scripts or CSS that are always injected into matching URLs. You should use this if you need a script, or CSS to be always injected into matching URLs.
While it might be more convenient to always have your script injected, you should seriously consider not doing so unless it is needed. You should particularly stay away from doing so if you are loading large scripts and/or on a large number of websites (e.g. all URLs). Indiscriminately injecting your script(s) (including libraries) on a large number of websites can take significant resources on the user's computer which can cause a loss of performance, affecting user experience. If you need to have interaction begin from within the website, try loading a smaller script that waits for that interaction to begin, then messages your background script to inject the rest of your functionality. Sometimes you do need your script loaded all the time, but try to think about it from your user's perspective, not just what is convenient in writing your extension. As an example of going too far injecting scripts using content_scripts, the extension about which this question was asked injects 78 different scripts into every https:// page.
The content_scripts key contains an array of objects. Each object describes a single injection directive. The object can contain multiple conditions and multiple files, but each one is all-or-nothing; either all files described in that object are injected, or none, depending on if the URL matches. If you want some file(s) injected to some URLs and some other file(s) injected to other URLs, then you use separate Objects to describe each group.
An example, which injects jquery.js & stackContent.js into Stack Exchange sites and jquery.js & exampleContent.js into example.com is:
"content_scripts": [
{
"matches": [
"http*://*.askubuntu.com/*",
"http*://*.mathoverflow.net/*",
"http*://*.serverfault.com/*",
"http*://*.stackapps.com/*",
"http*://*.stackexchange.com/*",
"http*://*.stackoverflow.com/*",
"http*://*.superuser.com/*"
],
"js": ["jquery.js", "stackContent.js"]
},
{
"matches": ["http*://*.example.com/*"],
"js": ["jquery.js", "exampleContent.js"]
}
]
Permissions
Depending on what you are doing, you are going to need permissions to interact with the matching URLs you are interested in and/or use chrome.tabs. From the above examples:
"permissions": [
"tabs",
"http*://*.askubuntu.com/*",
"http*://*.mathoverflow.net/*",
"http*://*.serverfault.com/*",
"http*://*.stackapps.com/*",
"http*://*.stackexchange.com/*",
"http*://*.stackoverflow.com/*",
"http*://*.superuser.com/*",
"http*://*.example.com/*"
]
Use the Tampermonkey / Greasemonkey extensions, with the header like that:
// ==UserScript==
// #name MTV Statistical Data For Tampermonkey & Greasemonkey
// #namespace MTV_statistical_data
// #include /^https?://(www\.)?mytrafficvalue\.com/shareholders\.html(\#marketplace)?$/
// #author facebook.com/igor39
// #version 7.5
// #grant none
// #description Improving the user experience & provide more of statistics on the page www.mytrafficvalue.com/shareholders.html
// ==/UserScript==
Example: http://pastebin.com/Z4zq2h6Q

How Pinterest extension store (temporarily) images from a web page and then access them in an iframe?

I've been trying to create a Pin It button (extension of Pinterest) like chrome extension. What i tried is firing a script when extension is clicked which can iterate through all available images on a webpage, store it in localStorage. Now i need to call an iframe (different domain ofcourse) and access these images. Since one can access localstorage only from same domain, i'm quite confused how Pinterest manages to store all images from a web page (temporarily and not on their server) and then use it in an iframe.
I also saw the code of PinIt Button extension but i can't understand a thing from it as it is too much obfuscated/encrypted or whatever.
I've read about chrome.storage api and i haven't been able to understand it quite well. I'm not even sure if this is the thing i need to do in this case. (This is the first time i'm developing a chrome extension). Can anybody please throw some light on this and guide me the best possible way to achieve this functionality?
P.S. I've completed this extension without using <iframe> however i need to do it with <iframe>.
EDIT 1: I can't write complete code here but here is the flow/structure/my attempts
I start with background.js
chrome.browserAction.onClicked.addListener(function (tab) { /*This fxn fires when extension is clicked*/
chrome.tabs.executeScript(tab.id, {
"file": "bookmarklet.js"/*Calls this file*/
})
});
In bookmarklet.js:
jQuery('body').append('<iframe class="vw_parent" id="image-grabber-container" src="" style="height: 100% !important; width: 100% !important; position: fixed !important; margin: 0% auto !important; background: rgba(17, 17, 17, 0.9) !important; left: 0 !important; right: 0 !important; z-index: 999999999 !important; top: 0% !important;"></iframe>');
jQuery('img').each(function() {
//do some checks to determine what images need to be stored
var allImgs = jQuery(this);
localStorage.setItem('myLocalImgs', allImgs);
})
vwgrid = "https://mydomain/FileToBeInjectedInIframe.php";
jQuery('#image-grabber-container').attr('src', vwgrid);
Now in FileToBeInjectedInIframe.php
var abcde = localStorage.getItem('myLocalImgs');
console.log(abcde);
//This gives NULL value
EDIT 2: As per comments and answer by DelightedD0D, i want to explain How this extension works/should work
1. User is on any webpage and then clicks the extension
2. All the images available on that webpage are displayed in an iFrame
3. User can select multiple images from this iFrame and then post them to my website
4. Reason for iFrame: If user clicks on POST IMAGE button available in iFrame and he is not logged into our website for posting the image, he should see a login popup in the same iFrame
5. If not iFrame, how would i check if the user is logged in my website as i won't be able to read session/cookie of a different domain on a different domain.
For same reasons i believe (i'm not sure though), pinterest also display images in an iFrame
TL;DR, link to an example extension at the bottom ;)
Ok, your question is a bit broad but I'll try to explain the approach I would take if I wanted to do this.
First, I'd drop the iFrame. I can't see any reason to use one here and I just dont like them personally.
I would:
have a content script that is injected into all pages (or specific one if needed, whatever)
the script would have a javascript class that adds a chrome.runtime.onMessage.addListener to the page
this class would listen for messages from the extension
messages would be sent to this class to trigger functions and send back a response
for example a message like {pageAction:"getImages"} would trigger the getImages function in the injected class
getImages would get all the images and send them back to the extension
One note here, I prefer to work with images encoded as base64 strings with stuff like this rather than image urls from a whole bunch of different domains and all the CORRs, link protection,....etc. For that reason, I would have the getImages make an ajax call to encodeImagesToBase64.php a server passing an array of image urls. encodeImagesToBase64.php would return an array of base64 images which are then sent to the extension.
Now that the extension has an array of base64 images, Id save them to chrome.storage.local in the extension's storage area.
Now you can show them in the popup, show them in a new tab for editing, or whatever
If you want to show them on the page in an overlay, just make a function to do that in the javascript listener class we made and send a message to it with the images to display
To get you started, here is a javascript listener class I use to do similar things in my extensions.
(note that this relies on John Resig's Simple JavaScript Inheritance which I highly recommend using when writing Classes )
// IMPORTANT NOTE you must reload your extension via chrome://extensions
// AND reload the browser tab in order for changes to this file to be seen!!
var PageActionListener = Class.extend({
/**
* This class is meant to be injected into a webpage
* once there, it listens for messages from an extension
* messages must pass like {pageAction:"someAction", ... }
* where someAction should map to a function in this class
* responses should take the form
* {success:true, pageAction:"someAction", data:"someData", ... }
*/
attachListener: function() {
/**
* Attaches a chrome message listener to the current page
* this allows the extension to call
* the functions in this class from the context of the page
* and receive responses from those functions via messaging
*/
var _this=this;
// Listen for messages from the popup
chrome.runtime.onMessage.addListener(function (msg, sender, extensionCallback) {
// First, validate the message links to a function
if (msg.pageAction) {
// then make sure its a legit function in this class
if(typeof _this[msg.pageAction] === "function"){
// call that fucntion
_this[msg.pageAction](msg,function(response){
extensionCallback(response);
});
}
else extensionCallback({success:false,msg:msg,error:"Action not found"});
return true;
}
});
},
getImages:function(msg,callback){
/**
* Traverses the DOM looking for images and returning them
* as an array of base64 strings
* #param object msg The message that triggered this action
* #param function callback A function to be called when the action below is complete
* passes to callback the images gathered from the page
*/
var images = [];
var $images= $('img');
$images.each(function(){
var url = this.src;
images.push(url);
});
// convert images to base64
$.ajax({
type: "POST",
url: "https://somedomain.com/shared-resources/php/encodeImagesToBase64.php", // you'll need to update this to your path
data: {urls:images},
success: function(response) {
response.msg=msg;
send the response back to the extension
callback(response);
},
error: function(xhr, status, error) {
callback({success:false,msg:msg,error:error.Message})
}
});
},
// add other functions that need to be called by the extension here
});
And here is the contents of encodeImagesToBase64.php to convert the images to base64:
<?php
if (isset($_POST['urls']) ){
$imageStrings=[];
foreach($_POST['urls'] as $url){
$imageStrings[]=base64_encode(file_get_contents($url));
}
echo json_encode(['success'=>true,'images'=>$imageStrings]);
}else{
echo json_encode(['success'=>false,'error'=>'Error: No images to encode']);;
}
Here is an example extension that kindof does what you want.
It'll need a ways to go to meet your actual needs but, it should be enough for you to understand the concepts and the approach Ive proposed above.
NOTE the example extension uses a copy of encodeImagesToBase64.php that is hosted on my server, you'll need to host your own and update the ajax call with the path to it. Ill leave mine up for now so you can test it out, but dont count on it being around forever :)

Open chrome://newtab from a Chrome extension

I try to write a Google Chrome extension that simply opens a new tab when I click left-right in a short interval. The JavaScript is no problem but I implemented this as a "content_scripts" script.
In some other threads I read that I can't access the chrome.* APIs from content_scripts (except the chrome.extension API).
Even if it's not necessary to access the chrome.tabs API to open a new window (window.open should do the job) it seems I need it though for opening a new tab with the new tab page which obviously isn't possible via window.open.
So I can't really figure out what is the best way to do that. I could use a background page which I could call from the content_script but I think there should be a much more simple way to do that, I just don't get it.
Anyone have an idea?
I think your content script will have to send a message to your background page to invoke chrome.tabs.create - content scripts cannot use the chrome api, nor can they directly communicate with the background page.
Here's a reference about message passing inside Chrome extensions for further detail, but here's the example code ( modified from the example in said reference )
// in background
chrome.extension.onMessage.addListener(
function(request, sender, sendResponse) {
switch ( request.action) {
case 'newTab' : {
//note: passing an empty object opens a new blank tab,
//but an object must be passed
chrome.tabs.create({/*options*/});
// run callback / send response
} break;
}
return true; //required if you want your callback to run, IIRC
});
// in content script:
chrome.extension.sendMessage({action: "newTab"}, function(response) {
//optional callback code here.
});
simple and easy
document.body.onclick = function openNewWindow( ) {
window.location.href = 'javascript:void window.open( "chrome://newtab" )';
}
manifest:
,"permissions":[
"http://*/*"
,"https://*/*"
]
,"manifest_version": 2
,"content_scripts":[{
"matches":[
"http://*/*"
,"https://*/*"
]
,"js":[
"js/openWindow.js"
]
}]
alright i miss understanding the question... modified

How to modify current url location in chrome via extensions

I want to create an extension that redirects the user to another website if he clicks on the extension button. So far I have only seen extensions which create a new tab for each click.
Is it possible to redirect the user to another website using the active tab?
I tried something like this:
chrome.browserAction.onClicked.addListener(function(tab) {
var url = "https://www.mipanga.com/Content/Submit?url="
+ encodeURIComponent(tab.url)
+ "&title=" + encodeURIComponent(tab.title);
document.location.href = url; // <-- this does not work
});
Attention: If you develop cross-browser extensions (I hope you do!), I recommend that you use chrome.tabs.query(). Please see Jean-Marc Amon's answer for more information. This answer still works in both Firefox and Chrome, but query() is more commonly used, has more options, and works in background pages and popup views.
From the chrome.tabs API, you can use getCurrent(), query(), or update().
Right now, I prefer update() as this allows you to update the current tab without needing to do much else.
NB: You cannot use update() from content scripts.
If updating the url from a content script is required then you should look to use query instead. Jean-Marc Amon's answer provides a wonderful example of how to get the active tab in this case (don't forget to upvote him!).
update()
let myNewUrl = `https://www.mipanga.com/Content/Submit?url=${encodeURIComponent(tab.url)}&title=${encodeURIComponent(tab.title)}`;
chrome.tabs.update(undefined, { url: myNewUrl });
Here, we have set the first argument of update to undefined. This is the tab id that you're wanting to update. If it's undefined then Chrome will update the current tab in the current window.
Please see Domino's answer for more information on update and also note that undefined is not needed. Again, please don't forget to upvote their answer as wellif you find it useful.
getCurrent()
getCurrent also cannot be called from a non-tab context (eg a background page or popup view).
Once you have the current tab, simply pass update().
chrome.tabs.getCurrent(function (tab) {
//Your code below...
let myNewUrl = `https://www.mipanga.com/Content/Submit?url=${encodeURIComponent(tab.url)}&title=${encodeURIComponent(tab.title)}`;
//Update the url here.
chrome.tabs.update(tab.id, { url: myNewUrl });
});
NB: In order to use this this functionality, you must ensure that you have the tabs permission enabled in your manifest.json file:
"permissions": [
"tabs"
],
You can use chrome.tabs.query too
chrome.tabs.query({currentWindow: true, active: true}, function (tab) {
chrome.tabs.update(tab.id, {url: your_new_url});
});
The chrome.tabs.update method will automatically run on the current active tab if no tab id is passed.
This has the added advantage of not requiring the tabs permission. Extensions with this permission warn the user that they can read the browsing history, so you should avoid asking for it if you don't need to.
Changing the current tab's URL is as simple as writing this:
chrome.tabs.update(undefined, {url: 'http://example.com'});
Or as mentionned by farwayer in the comments, you don't need to put two arguments at all.
chrome.tabs.update({url: 'http://example.com'});
The answers given here no longer work: the Chrome Tabs API can no longer be used by content scripts, only by service workers and extension pages.
Instead, you can send a message to a service worker to get it to update the location of the current tab: see https://stackoverflow.com/a/62461987.
See this for a simple working example.

Categories