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";
}
}
Related
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.
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.
Let's say normally my users access our web page via https://www.mycompany.com/go/mybusinessname
Inside this web page, we have a iframe which actually comes from https://www.mycompany.com/myapp
Everything is working fine, except that if for some reason, the users come to know about this url https://www.mycompany.com/myapp. They can start accessing it directly by typing into the address bar.
This is what I want to prevent them from doing. Is there any best practice to achieve this?
==== Update to provide more background ====
The parent page which is https://www.mycompany.com is the company's page and it's maintained by some other team. So they have all the generic header and footer, etc. so each application is rendered as an iframe inside it. (This also means we cannot change the parent page's code)
If users access https://www.mycompany.com/myapp directly, they won't be able to see the header and footer. Yes, it's not a big deal, but I just want to maintain the consistency.
Another of my concern is that, in our dev environment (aka when running the page locally) we don't have the parent-iframe thing. We access our page directly from http://localhost:port. Hence I want to find a solution that can allow us access it normally when running locally as well.
If such solution simple does not exist, please let me know as well :)
On your iframe's source, you can check the parent's window by using window.top.location and see if it's set to 'https://www.mycompany.com/go/mybusinessname'. If not, redirect the page.
var myUrl = 'https://www.mycompany.com/go/mybusinessname';
if(window.top.location.href !== myUrl) {
window.top.location.href = myUrl;
}
I realized we already had a function to determine whether the page in running under https://www.mycompany.com. So now I only need to do the below to perform the redirecting when our page is not iframe
var expectedPathname = "/go/mybusinessname";
var getLocation = function (href) {
var l = document.createElement("a");
l.href = href;
return l;
};
if (window == window.top) { // if not iframe
var link = getLocation(window.top.location.href);
if (link.pathname !== expectedPathname) {
link.pathname = expectedPathname;
window.top.location.replace(link.href);
}
}
You can use HTTP referer header on server-side. If the page is opened in IFRAME - the referer contains parent page address. Otherwise, it is empty or contains different page.
I'm attempting to perform programmatic injection of my content script into open tabs after my Chrome extension is reloaded or updated.
My script may call the following method for an arbitrary tab:
var manifest = chrome.app.getDetails();
var scripts = manifest.content_scripts[0].js;
chrome.tabs.executeScript(nTabID, {
file: scripts[0]
});
This works, except when I try to load it into a page that was not supposed to have a content script running according to the matches clause in the manifest.json. I get the following exception:
Cannot access contents of url "actual-url-here". Extension manifest
must request permission to access this host.
So my question. Is there a way to parse the page URL and see if it matches matches clause from manifest.json and prevent calling chrome.tabs.executeScript for unnecessary URL?
PS. I understand that one "hacky" solution is to catch-and-ignore exceptions. So I'm not asking for it.
When you use chrome.tabs.query for a list of tabs, use the url attribute to filter by a match patterns. As of Chrome 39, this key also supports an array of match patterns. If you need to support Chrome 38 or earlier, or if you got the tabs without chrome.tabs.query, use the parse_match_pattern function from this answer to filter tabs. To use it, copy that function and include it within your (background) page (e.g. by pasting it before the following snippet).
var content_scripts = chrome.runtime.getManifest().content_scripts;
// Exclude CSS files - CSS is automatically inserted.
content_scripts = content_scripts.filter(function(content_script) {
return content_script.js && content_script.js.length > 0;
});
content_scripts.forEach(function(content_script) {
try {
// NOTE: an array of patterns is only supported in Chrome 39+
chrome.tabs.query({
url: content_script.matches
}, injectScripts);
} catch (e) {
// NOTE: This requires the "tabs" permission!
chrome.tabs.query({
}, function(tabs) {
var parsed = content_script.matches.map(parse_match_pattern);
var pattern = new RegExp(parsed.join('|'));
tabs = tabs.filter(function(tab) {
return pattern.test(tab.url);
});
injectScripts(tabs);
});
}
function injectScripts(tabs) {
tabs.forEach(function(tab) {
content_script.js.forEach(function(js) {
chrome.tabs.executeScript(tab.id, {
file: js
});
});
});
}
});
The previous snippet inserts a content script in all tabs. It is your responsibility to make sure that inserting the script does not conflict with an earlier/later instance of your script.
Mimicking the all_frames and match_about_blank functionality is slightly more complex, because the chrome.tabs.executeScript API cannot be used to target specific frames (crbug.com/63979). If you want to inject in frames as well, then you have to insert in every tab (because there might be a frame under the non-matching top-level frame that matches the URL) and check the page's URL within the content script.
Finally, note that your content script must also deal with the fact that it may run at a point different from "run_at". In particular, content scripts that rely on "run_at":"document_start" might fail to work because calling chrome.tabs.executeScript will cause a script to be injected far past the document_start phase.
I'd like to write an extension that redirects all web traffic to a specific domain, let's say wikipedia.org, to an intermediate page that says something like, "Whoa, bub. You're about to go to Wikipedia. Are you sure about this?" followed by Yes and No buttons.
How do I respond to a specific URL request and replace the desired page with a custom one when it meets the condition of being a page with "wikipedia.org" in the domain?
You can do this using webRequest feature, a background page, and custom page with yes and no buttons. For example, write something similar in the background page:
var URLStorage;
function interceptRequest(request)
{
if(request && request.url)
{
if(request.type == "main_frame") // new page/site is loading in main window
{
if(request.url.indexOf("wikipedia.org") > -1)
{
URLStorage = request.url;
return {redirectUrl: chrome.extension.getURL("confirmation.html")};
}
}
}
}
chrome.webRequest.onBeforeRequest.addListener(interceptRequest, {urls: ["*://*/*"]}, ['blocking']);
This example does not strictly check if wikipedia is mentioned in domain, but I did this for clarity. In my real code a special class 'URL' is used which parses passed url and provides properties for every part of it, so they can be checked selectively.
In the confirmation.html just place 2 buttons, and bind them to an appropriate code, for example redirecting to requested site, if a user answered "yes".
$('#okbutton').click(function()
{
document.location.href = chrome.extension.getBackgroundPage().URLStorage;
});
Don't forget to mention "webRequest" and "webRequestBlocking" in permissions section of your manifest.
You can create a content script that injects javascript code into each page that the user visits. In your content script you could have the js check the current url against invalid url's and redirect them accordingly.
I think content scripts load after the page has loaded so there may be a brief period where the user sees the page they were looking for and then gets redirected to your landing page. Check out the content script docs here:
http://developer.chrome.com/extensions/content_scripts.html
{
"name": "My extension",
...
"content_scripts": [
{
"matches": ["http://www.google.com/*"],
"css": ["mystyles.css"],
"js": ["jquery.js", "myscript.js"]
}
],
...
}
"matches" you should make the array of something similar to
"matches": ["http://www.*.com/*", "http://*.com/*, "https://www.*.com/*", "https://*.*.com/*]
and "js" would be the name of your javascript file that you want to use to write the injection into the page.
something like:
if(window.location == "http://wikipedia.com"){
window.location.href = "http://mysplashpage.com";
}
Of course, that js won't work in all instances, for instance, if the user is trying to get to a directory of the target website. You will probably need to some regex checks or some other functions like protocol and host as defined here : http://css-tricks.com/snippets/javascript/get-url-and-url-parts-in-javascript/