How to call a function from injected script? - javascript

This is code from my contentScript.js:
function loadScript(script_url)
{
var head= document.getElementsByTagName('head')[0];
var script= document.createElement('script');
script.type= 'text/javascript';
script.src= chrome.extension.getURL('mySuperScript.js');
head.appendChild(script);
someFunctionFromMySuperScript(request.widgetFrame);// ReferenceError: someFunctionFromMySuperScript is not defined
}
but i got an error when calling a function from injected script:
ReferenceError: someFunctionFromMySuperScript is not defined
Is there is a way to call this function without modifying mySuperScript.js?

Your code suffers from multiple problems:
As you've noticed, the functions and variables from the injected script (mySuperScript.js) are not directly visible to the content script (contentScript.js). That is because the two scripts run in different execution environments.
Inserting a <script> element with a script referenced through a src attribute does not immediately cause the script to execute. Therefore, even if the scripts were to run in the same environment, then you can still not access it.
To solve the issue, first consider whether it is really necessary to run mySuperScript.js in the page. If you don't to access any JavaScript objects from the page itself, then you don't need to inject a script. You should try to minimize the amount of code that runs in the page itself to avoid conflicts.
If you don't have to run the code in the page, then run mySuperScript.js before contentScript.js, and then any functions and variables are immediately available (as usual, via the manifest or by programmatic injection).
If for some reason the script really needs to be loaded dynamically, then you could declare it in web_accessible_resources and use fetch or XMLHttpRequest to load the script, and then eval to run it in your content script's context.
For example:
function loadScript(scriptUrl, callback) {
var scriptUrl = chrome.runtime.getURL(scriptUrl);
fetch(scriptUrl).then(function(response) {
return response.text();
}).then(function(responseText) {
// Optional: Set sourceURL so that the debugger can correctly
// map the source code back to the original script URL.
responseText += '\n//# sourceURL=' + scriptUrl;
// eval is normally frowned upon, but we are executing static
// extension scripts, so that is safe.
window.eval(responseText);
callback();
});
}
// Usage:
loadScript('mySuperScript.js', function() {
someFunctionFromMySuperScript();
});
If you really have to call a function in the page from the script (i.e. mySuperScript.js must absolutely run in the context of the page), then you could inject another script (via any of the techniques from Building a Chrome Extension - Inject code in a page using a Content script) and then pass the message back to the content script (e.g. using custom events).
For example:
var script = document.createElement('script');
script.src = chrome.runtime.getURL('mySuperScript.js');
// We have to use .onload to wait until the script has loaded and executed.
script.onload = function() {
this.remove(); // Clean-up previous script tag
var s = document.createElement('script');
s.addEventListener('my-event-todo-rename', function(e) {
// TODO: Do something with e.detail
// (= result of someFunctionFromMySuperScript() in page)
console.log('Potentially untrusted result: ', e.detail);
// ^ Untrusted because anything in the page can spoof the event.
});
s.textContent = `(function() {
var currentScript = document.currentScript;
var result = someFunctionFromMySuperScript();
currentScript.dispatchEvent(new CustomEvent('my-event-todo-rename', {
detail: result,
}));
})()`;
// Inject to run above script in the page.
(document.head || document.documentElement).appendChild(s);
// Because we use .textContent, the script is synchronously executed.
// So now we can safely remove the script (to clean up).
s.remove();
};
(document.head || document.documentElement).appendChild(script);
(in the above example I'm using template literals, which are supported in Chrome 41+)

As long as the someFunctionFromMySuperScript function is global you can call it, however you need to wait for the code actually be loaded.
function loadScript(script_url)
{
var head= document.getElementsByTagName('head')[0];
var script= document.createElement('script');
script.type= 'text/javascript';
script.src= chrome.extension.getURL('mySuperScript.js');
script.onload = function () {
someFunctionFromMySuperScript(request.widgetFrame);
}
head.appendChild(script);
}
You can also use jQuery's getScript method.

This doesn't work, because your content script and the injected script live in different contexts: what you inject into the page is in the page context instead.
If you just want to load code dynamically into the content script context, you can't do it from the content script - you need to ask a background page to do executeScript on your behalf.
// Content script
chrome.runtime.sendMessage({injectScript: "mySuperScript.js"}, function(response) {
// You can use someFunctionFromMySuperScript here
});
// Background script
chrome.runtime.onMessage.addListener(function(message, sender, sendResponse) {
if (message.injectScript) {
chrome.tabs.executeScript(
sender.tab.id,
{
frameId: sender.frameId},
file: message.injectScript
},
function() { sendResponse(true); }
);
return true; // Since sendResponse is called asynchronously
}
});
If you need to inject code in the page context, then your method is correct but you can't call it directly. Use other methods to communicate with it, such as custom DOM events.

Related

<script> onload fires when ...?

In the function below from Mozilla, JS is used to add a tag into the document. I'm confused when the onload event fires. Does onload fire when the script starts to download or has already downloaded?
function prefixScript(url, onloadFunction) {
var newScript = document.createElement("script");
newScript.onerror = loadError;
if (onloadFunction) { newScript.onload = onloadFunction; }
document.currentScript.parentNode.insertBefore(newScript, document.currentScript);
newScript.src = url;
}
https://developer.mozilla.org/en-US/docs/Web/API/HTMLScriptElement
Thank you
onload's callback is called when these have been completed:
The HTTP request to fetch the script file has been completed successfully
The script content has been parsed
The script has been executed, thus possibly exposing new global variables and/or triggering side effects like DOM manipulation or XHR
Here's a demo where a script for jQuery library is added to <head>, thus exposing the global variable $ created by the execution of the imported script:
const script = document.createElement('script');
script.onload = () => {
try {
$;
console.log('onload - $ is defined');
} catch(e) {
console.log('onload - $ is not defined yet');
}
}
script.src = 'https://code.jquery.com/jquery-3.3.1.js';
try {
$;
console.log('main - $ is defined');
} catch(e) {
console.log('main - $ is not defined yet');
}
document.head.appendChild(script);
Last precision - int this case, the load is triggered by appending the script to the DOM, not by script.src = ...! Try commenting out the last line - the script never loads.
There can also be other cases where the load is triggered by script.src = ..., for example when the script is appened to the DOM, the the .src is set.
From the MDN GlobalEventHandlers.onload docs:
The load event fires when a given resource has loaded.
There is also an event for onloadstart called when loading for that resource is started.

Function not available after document.body.appendChild(scriptElement)

Here is the code I am using for dynamically including script tag with src in my HTML page. There is a function in that newly imported javascript file that I want to use:
var scriptFile= document.createElement("script");
scriptFile.src = "something.js";
var something=document.body.appendChild(scriptFile);
something.js contains a function called doSomething(). Now when I call this function immediately after the appendChild above, it say doSomething is not defined. However, when I fire it from the Chrome console, it executes successfully. I am not sure why is this happening.
When you append the script tag with the source, first the source will be parsed and compiled which is an asyn process ( non blocking ).
If you try to invoke the method immediately this would throw an error as the source has not been compiled yet.
Bind a load event which gets triggered when the script is available. This will make sure you are running the contents of the script tag only after it has completely loaded.
var scriptFile= document.createElement("script");
scriptFile.addEventListener('load', function() {
console.log('Script is ready to execute');
// invoke your function here
});
scriptFile.src = "something.js";
var something=document.body.appendChild(scriptFile);
Listen for onload
var scriptFile= document.createElement("script")
scriptFile.src = "something.js"
document.body.appendChild(scriptFile)
scriptFile.onload = () => {
// call something.js functions here
}

Object inside window is present but undefined when invoked

I dynamically added StripeCheckout script in the DOM like:
var script = document.createElement(`script`);
script.src = `https://checkout.stripe.com/checkout.js`;
document.getElementsByTagName('head')[0].appendChild(script);
When I do console.log(window) I get :
But when I do console.log(window.StripeCheckout) I freaking get an undefined.
Why?
P.S. window.StripeCheckout is present when I accessed it directly from the dev console.
Why?
Because it takes time to load and execute the script. When you log window, by the time you expand the object, the script has loaded and executed. Same for when you run window.StripeCheckout in the developer console.
When you hover over that little [i] in the console it will also tell you that the "content" of the object was just evaluated when you expanded that line.
Additionally, because JavaScript runs to completion, it is guaranteed that console.log(window.StripeCheckout) is executed before the script is evaluated. So the property cannot exist at that moment, even if the script was available immediately.
If you want to know when the script loaded have a look at
Dynamically load external javascript file, and wait for it to load - without using JQuery
Dynamically load a JavaScript file
Try this:
function loadScript(url, callback){
var script = document.createElement("script")
script.type = "text/javascript";
if (script.readyState){ //IE
script.onreadystatechange = function(){
if (script.readyState == "loaded" ||
script.readyState == "complete"){
script.onreadystatechange = null;
callback();
}
};
} else { //Others
script.onload = function(){
callback();
};
}
script.src = url;
document.getElementsByTagName("head")[0].appendChild(script);
}
loadScript("https://checkout.stripe.com/checkout.js", function(){
console.log(window.StripeCheckout)
});

Injecting Javascript in a page from a Chrome extension : concurrency issue?

I am taking part in the development of a Chrome extension.
At some point, we need to run some static non-generated code which has to run in the context of the page and not of the extension.
For simple scripts, there is no problem, using either $.getScript(chrome.extension.getURL(....)) either script = document.createElement('script'); ... document.body.appendChild(script);
For more complex scripts, we'd need sometimes to incluque jquery itself, or some other script definition (because of dependencies).
In this latter case, though, despite the fact that Javascript is supposedly single threaded, it seems that JQuery is not parsed entirely when the dependend script are run, leading to the following
Uncaught ReferenceError: $ is not defined
Am I wrong when assuming that JScript is single-threaded?
What is the correct way to inject scripts in a page when there are dependencies between those scripts? (e.g. script X uses a function defined in script Y)
You could use the onload event for the script....
function addScript(scriptURL, onload) {
var script = document.createElement('script');
script.setAttribute("type", "application/javascript");
script.setAttribute("src", scriptURL);
if (onload) script.onload = onload;
document.documentElement.appendChild(script);
}
function addSecondScript(){
addScript(chrome.extension.getURL("second.js"));
}
addScript(chrome.extension.getURL("jquery-1.7.1.min.js"), addSecondScript);
Appending a script element with src attribute in the DOM doesn't mean that the script is actually loaded and ready to be used. You will need to monitor when the script has actually loaded, after which you can start using jQuery as normal.
You could do something like this:
var script = doc.createElement("script");
script.src = url;
/* if you are just targeting Chrome, the below isn't necessary
script.onload = (function(script, func){
var intervalFunc;
if (script.onload === undefined) {
// IE lack of support for script onload
if( script.onreadystatechange !== undefined ) {
intervalFunc = function() {
if (script.readyState !== "loaded" && script.readyState !== "complete") {
window.setTimeout( intervalFunc, 250 );
} else {
// it is loaded
func();
}
};
window.setTimeout( intervalFunc, 250 );
}
} else {
return func;
}
})(script, function(){
// your onload event, whatever jQuery etc.
});
*/
script.onload = onreadyFunction; // your onload event, whatever jQuery etc.
document.body.appendChild( script );

Execute javascript after external javascript document has loaded

I want to include a remote js file and then invoke a function once that has finished executing. I thought I could do something like this:
var sc = document.createElement('script');
sc.setAttribute('type', 'text/javascript');
sc.setAttribute('src', src);
sc.innerHTML = "alert('testing');"
parentNode.appendChild(sc);
Turns out, the alert('testing') gets wiped out be whatever is in the file. Is there anyway to do this?
This function will load library from scriptPath and execute passed handler function once script is loaded:
loadExternalScript : function(scriptPath, handler) {
var head = document.getElementsByTagName('head')[0];
var script = document.createElement('script');
script.type = 'text/javascript';
script.src = scriptPath;
script.charset = 'utf-8';
script.onload = handler;
head.appendChild(script);
}
First thing is, forget about using src and inner contents on the same script tag. It doesn't work in any general way, although John Resig gave it some thought in this blog post.
Second thing is, decide whether you want to load the script synchronously or asynchronously. If the script is large or long-running, you'll either want to do it asynchronously, or do it synchronously at the bottom of the page so as not to block rendering.
Your approach (dynamically appending script tags) will load and run it asynchronously, which means the code that should run after it's finished needs to go in a callback that fires when the script is finished. Setting this up isn't very straightforward, so I'd suggest either using jQuery and its ajax.getScript function, or just copy the getScript functionality right out of the jQuery source (lines 3473-3505 of jQuery 1.3.2).
If you want to avoid all of that, just load it synchronously. This is done by using document.write. Your provided example would look like:
document.write("<scr" + "ipt src='" + src + "' type='text/javascript'></script>");
// The script is guaranteed to have executed at this point
alert('testing');
Be sure to keep "script" split up like that, I'm not sure why but it's a quirk of JavaScript.
Have you tried just creating a second script element containing the code you want to run and adding that after the you've added the one that needs downloading?
Adding another
<script></script>
section after the first one should work. AFAIK you can't mix external and inline JS in one tag.
However I'm not sure whether putting code into "innerHTML" will work as expected. I'm interested to see whether it does.
You might be able to use the sc load event to figure out when that script has loaded then do some action.
example http://iamnoah.blogspot.com/2008/01/ie-script-load-event.html
I created this script for myself yesterday. It uses jQuery to load JavaScript files via AJAX and adds them in a script tag to the header, and then calls a callback function I pass it.
Has been working fine for me.
/**
* Fetches and executes JavaScript files from the server.
* #param files A list of files to load, or a single filename as a string.
* #param callback The function to call when the process is done. Passes a boolean success value as the only parameter.
* #param thisObject The calling object for the callback.
*/
window.include = function(files, callback, thisObject) {
var current_location = null;
var recursive = false;
if(!(thisObject instanceof Object)) {
thisObject = window;
}
if(files instanceof Array || files instanceof Object) {
if(files.length > 0) {
current_location = files.shift();
recursive = true;
}
else {
callback.apply(thisObject, [true]);
return;
}
}
else if(typeof files == 'string') {
current_location = files;
}
else {
callback.apply(thisObject, [false]);
return;
}
if((current_location instanceof String || typeof current_location == 'string') && current_location != '')
{
$.ajax({
type : 'GET',
url : current_location,
timeout : 5000,
success : function(data) {
var scriptTag = $(document.createElement('script'));
scriptTag.attr('type', 'text/javascript');
scriptTag.html(data);
$('head').append(scriptTag);
if(recursive) {
window.adlib.include(files, callback, thisObject);
}
else {
callback.apply(thisObject, [true]);
}
},
error : function() {
callback.apply(thisObject, [false]);
}
});
}
}

Categories