Why can't my content script use already injected script? - javascript

For my Chrome extension, I inject a Javascript file and a CSS file that are needed for the modal to load up by using content.js. Injection is successful and I see those two files in the DOM.
And then I send a message to the background.js to do some stuff and this works too because I receive the message back from there. But my modal fails to load because it seems that it cannot use the injected Javascript file.
Let's say I have a function that inject the files into DOM. Let's call it inject(). I call this function at the start of the content.js file and they are injected successfully. But when I get back message from background.js and try to load the modal, it fails.
If I again try to call inject() function inside chrome.runtime.sendMessage function, the modal successful loads up.
In content.js I have the following code. I send a message to the background script whenever user click a link on the current page.
chrome.runtime.sendMessage(link,function(response){
inject(); //Now the modal loads. But if I remove this, the modal fails to load.
loadModalFunction(response);
});
My question then is if I had already injected modal.js and modal.css as soon as the page has loaded, why do I need to inject the files again to load the modal? My extension loads the modal whenever a user click a link on a page. So my concern is that if I have to inject the two files into the DOM whenever a user clicks something, it would make the page slow.
Update with more code:
In content.js:
function injectStuffs(){
var jquery = chrome.extension.getURL("jquery-1.11.3.min.js");
var script = document.createElement('script');
script.type = 'text/javascript';
script.src = jquery;
$("head").append(script);
var modaljs = chrome.extension.getURL("modal.js");
var script = document.createElement('script');
script.type = 'text/javascript';
script.src = modaljs;
$("head").append(script);
var link = document.createElement("link");
link.href = chrome.extension.getURL("modal.css");
link.type = "text/css";
link.rel = "stylesheet";
$("head").append(link);
}
injectStuffs() //Inject the scripts into ```DOM```
$(document).on('click', '.readmore a', function(event) {
event.preventDefault();
var link = $(this).attr('href');
chrome.runtime.sendMessage(link,function(response){
injectStuffs() //I need to inject here again to load the modal up. Why?
loadModal(response);
});
});

I'm actually surprised that calling injectStuffs() a second time works! I believe the problem is caused by the "isolated world" that your content script is running in:
Content scripts execute in a special environment called an isolated world. They have access to the DOM of the page they are injected into, but not to any JavaScript variables or functions created by the page. It looks to each content script as if there is no other JavaScript executing on the page it is running on. [reference]
When you list JavaScript files in the js property of a content script in your manifest, all those files are considered to be part of the content script, so each of the files can see the functions/variables defined in the others (but not in the page).
In contrast, when you inject JavaScript files by writing <script> elements to the DOM, those files are considered to be part of the page, so your content script can't see the functions/variables defined in them.
So in your situation, content.js is in your content script, but jquery-1.11.3.min.js and modal.js are in the page. That's why content.js can't call loadModal().
You probably want to add jquery-1.11.3.min.js and modal.js to your content script in the manifest. But if you really need to decide at runtime whether to load the JavaScript files, then see the docs for programmatic injection.

Related

Chrome Extension imports/exports

I'm failing hard in trying to import/export functions from one file to another with a Chrome Extension. My problem is the following:
I have one script that's loaded as a content script from the manifest.js file. That script's name is script.js. In that script, I have a code like this to detect the URL of the webpage I've opened (in order to start my extension):
chrome.runtime.sendMessage({ command: 'currentTab' }, (tab) => { console.log('We're in.') });
Also, in the same script I have a function to attach two other scripts that I will use them as modules since they have export/import functions. These are: core.js and utils.js. The function is:
injectScript('extension.../modules/core.js');
injectScript('extension.../modules/utils.js');
function injectScript(scriptURL) {
const script = document.createElement('script');
script.setAttribute('type', 'module');
script.setAttribute('src', scriptURL);
const head = document.head || document.getElementsByTagName('head')[0] || document.documentElement;
head.insertBefore(script, head.lastChild);
}
So, this first script.js sends a message to the background.js script in order to check for the tab URL and if everything's correct, I'll insert as modules my two other scripts.
Now, when I detect the URL of the website as OK, I would like to execute a function start() (which is in core.js) from this main script.js in order to execute everything from core.js that uses imported functions from utils.js.
I've also detected that if I inject my utils.js with the script.js it also injects it through the manifest.json. I'm really stuck in here guys. Could you give me a hand with this spaghetti mess?
Thank you!

JS function works on chrome console but not when load from plugin

So I have a webpage with a function applaud. When I call it from the console, I get the normal return:
applaud(3004,1935);
undefined
However, if I use CTG plugins (simple plugin to run a js script), with that code
applaud(3004,1935);
I get the following error in console:
3VM5444:1 Uncaught ReferenceError: applaud is not defined
at <anonymous>:1:1
(anonymous) # VM5444:1
and function isn't working.
Do you know how I can use it?
Thanks.
I know this is a bit outdated, but I can answer this. (I made the extension in question.)
Chrome Extensions by default insert scripts into a webpage in a different context than the rest of the page. This is for security reasons. If you'd like to run code in the context of the webpage, you'd need to use a little workaround.
In the script that the Chrome Extension injects, have that inject a <script> tag into the body of the page. Then that script will be loaded and be able to execute the functions like you can in the console.
Here's a demo of code that can do what I'm talking about:
//Create a new script element.
var script = document.createElement("script");
//Get the function you want to inject as a string and add it to the script.
script.innerHTML = injection.toString();
//Add a call to that injection function so it'll automatically execute once it's injected.
script.innerHTML += "injection();";
//Inject that newly created script into the body of the page.
document.body.appendChild(script);
//The contents of this script will be run inside the same context as the webpage.
function injection(){
applaud(3004, 1935);
}

Uncaught TypeError: Cannot read property 'colors' of undefined

I am trying to create a chrome extension that allows a user to add a new color to Desmos, a graphing calculator, when the extension button is clicked.
The two basic files:
chrome.browserAction.onClicked.addListener(function (tab) {
chrome.tabs.executeScript(tab.ib, {
file: "add_color.js"
});
});
and
(function() {
if (window.location.href === "https://www.desmos.com/calculator") {
var name = prompt("What would you like the name of the new color to be?");
var hex = prompt("What should the hex code of the new color be?");
window.Calc.colors[name] = hex;
};
})();
But when I try running it, I get Uncaught TypeError: Cannot read property 'colors' of undefined. If I run it using the DevTools console it runs perfectly. Could someone explain why?
You can't access a webpage's evaluated Javascript variables from a content script. Your extension script, content script and THE page's script are running in different contexts. Your content script has access to the page's DOM and not the execution context. Thus, you need your content script to directly inject code into the page's DOM via add script tags.
Summary.
Extension script has access to the chrome tabs.
Content script has access to the page DOM.
Page script is executing in a different context than the other scripts.
Content script must inject code via adding script tags.
Injected code can access page's evaluated javascript variables.
Injected code can communicate back to content script using window.postMessage
Then content script can communicate back to extension script via chrome API.
It's not fun...

Chrome extension code vs Content scripts vs Injected scripts

I am trying to get my Chrome Extension to run the function init() whenever a new page is loaded, but I am having trouble trying to understand how to do this. From what I understand, I need to do the following in background.html:
Use chrome.tabs.onUpdated.addListener() to check when the page is
changed
Use chrome.tabs.executeScript to run a script.
This is the code I have:
//background.html
chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab) {
chrome.tabs.executeScript(null, {code:"init();"});
});
//script.js
function init() {
alert("It works!");
}
I am also wondering if the init() function will have access to my other functions located in other JS files?
JavaScript code in Chrome extensions can be divided in the following groups:
Extension code - Full access to all permitted chrome.* APIs.
This includes the background page, and all pages which have direct access to it via chrome.extension.getBackgroundPage(), such as the browser pop-ups.
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 (not to any of the window objects, including frames).
Content scripts run in a scope between the extension and the page. The global window object of a Content script is distinct from the page/extension's global namespace.
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.
Injected scripts behave as if they were included by the page itself, and are not connected to the extension in any way. See this post to learn more information on the various injection methods.
To send a message from the injected script to the content script, events have to be used. See this answer for an example. Note: Message transported within an extension from one context to another are automatically (JSON)-serialised and parsed.
In your case, the code in the background page (chrome.tabs.onUpdated) is likely called before the content script script.js is evaluated. So, you'll get a ReferenceError, because init is not .
Also, when you use chrome.tabs.onUpdated, make sure that you test whether the page is fully loaded, because the event fires twice: Before load, and on finish:
//background.html
chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab) {
if (changeInfo.status == 'complete') {
// Execute some script when the page is fully (DOM) ready
chrome.tabs.executeScript(null, {code:"init();"});
}
});

Open new page in the same broswer window without affecting the browser history

The question is for IE7 only, because location.replace(strURL) seems to work file in all other major browsers.
I try to execute some analytics js and then redirect the users to the location of a resource (usually doc or pdf) they want to download.
The user opens a page containing the js code.
After the download is tracked the broswer should load the resurouce url using the following code by REPLACING the current page entry in the history with the resource url:
if (IE) {
window.open(strURL,"_self", true); //doesn't work
//window.open(strURL,"_self",undefined, true); //doesn't work
return;
}
This code creates entry in the history for the redirecting page.
I have tried using iframe on the same page but IE will pop up a security warning if the file is a *.doc
Any ideas?
I'm not sure if there is a way to avoid this in IE7 or not. Perhaps a different approach is possible. Inject the analytics js into the current page before redirecting, thereby avoiding the need to load another page altogether. In this approach, you would create a new script tag and inject it into the head element. The script will execute when it loads, do your analytics and then trigger the redirect for download as the last step (for instance).
function redirectWithAnalytics() {
var s = document.createElement('script');
s.src = 'path/to/analytics.js';
s.type = 'text/javascript';
document.getElementsByTagName('HEAD')[0].appendChild(s);
}

Categories