if (typeof $ == "undefined"){
console.log("$ is undefined. Adding javascript element to the document");
jQs = document.createElement("script");
jQs.type = 'text/javascript';
jQs.src = 'http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js';
var s = document.getElementsByTagName('script')[0];
s.parentNode.insertBefore(jQs, s);
// Run script once jQuery is loaded
console.log(" startScript() Start");
jQs.onload = startScript;
}
I have got the script above, which adds a script tag that points to jquery in the case if the $ is undefined. I try to insert the tag before the document begins and execute the startScript function. But what I think happens is that the body.onload doesnt trigger the startScript fast enough.. and that prevents its execution. Is there an event that would trigger teh startScript as soon as the jquery script tag / document is ready?
I think the answer lies in executing the startscript whenever the jquery tag is ready and the document is ready together..but i dont know how to achieve the effect..
Note that putting the code in $(document).ready() is an irrelevant solution because it would $ is undefined if there is no jquery file included in the script
note also that i dont want to execute the onload function on the jquery tag onload event,, cause it may mean that the function is being triggered several times
function init() {
if (typeof $ == "undefined"){
console.log("$ is undefined. Adding javascript element to the document");
jQs = document.createElement("script");
jQs.type = 'text/javascript';
jQs.src = 'http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js';
var s = document.getElementsByTagName('script')[0];
s.parentNode.insertBefore(jQs, s);
// Run script once jQuery is loaded
console.log(" startScript() Start");
jQs.onload = startScript;
}
}
window.onload = init;
Related
createElement script ,async=falseļ¼don't work
<script>
let script = document.createElement('script');
script.src = 'a.js';
script.async = false;
document.head.appendChild(script);
console.log(a); // undefined
</script>
a.js is just var a = 123;createElement script is async default,so i use async=false,but it look like don't work
Yes, <script> elements that are not "parser inserted" have their force async flag set to true. Setting the async attribute will set that flag to false, but then, it will be either async per the attribute, or it will only make it appended in the list of scripts that will execute in order as soon as possible instead of in the set of scripts that will execute as soon as possible. This will ensure that if you do load another script that way, they'll load in the order you defined, but they will still be loaded in parallel.
You can however add a load event handler on your <script> to wait until it's properly loaded.
const el = document.createElement("script");
el.src = "https://app.requestly.io/delay/2000/https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js";
el.async = false;
el.onload = (evt) => console.log(typeof jQuery);
document.body.append(el);
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.
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.
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 );
I have a unique issue -
I am designing a web application that creates widgets that a user can then embed in their own page (blog posts, mostly). I want them to just have to embed one line, so I just had that line be an include statement, to pull a Javascript off my server.
The problem is, I am building the widget code using jQuery, and I need to load the jQuery plugin, since I obviously don't know whether or not my users will have it available. I thought 'this should be pretty simple'....
function includeJavaScript(jsFile) {
var script = document.createElement('script');
script.src = jsFile;
script.type = 'text/javascript';
document.getElementsByTagName('head')[0].appendChild(script);
}
includeJavaScript('http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js');
jQuery();
So, I am appending the jQuery file to the head, then afterwards, trying to run a jQuery function. Trouble is, this doesn't work! Everytime I run it, I get the error that variable jQuery is not defined. I have tried a few things. I tried putting the jQuery functions in an onLoad trigger, so that the whole page (including, presumably, the jQuery file) would load before it called my script. I tried putting the jQuery function in a seperate file, and loading it after loading the jQuery lib file. But I get the idea I'm missing something simple - I'm new to jQuery, so if I'm missing something obvious, I apologize...
EDIT
OK,I tried the suggestion offered by digitalFresh, as follows (using Safari 5, if that helps), but I still get the same error?
function test() {
jQuery()
}
var script = document.createElement("script");
script.type = "text/javascript";
script.src = 'http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js';
script.onload = test(); //execute
document.body.appendChild(script);
EDIT
OK, I FINALLY got it to work, in an offhand suggestion from Brendan, by putting the call ITSELF in an 'onload' handler, like so:
function addLoadEvent(func) {
var oldonload = window.onload;
if (typeof window.onload != 'function') {
window.onload = func;
} else {
window.onload = function() {
if (oldonload) {
oldonload();
}
func();
}
}
}
addLoadEvent( function() {
var script = document.createElement("script");
script.type = "text/javascript";
script.src = 'http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js';
document.body.appendChild(script);
jQuery();
});
At this point, as you can see, I don't even have to put it in an 'onload' - it just works. Though I have to admit, I still don't understand WHY it works, which bothers me...
The solution you end up using works but slow to start, and not 100% fail proof. If someone rewrites window.onload your code will not run. Also window.onload happens when all the content on the page is loaded (including images) which is not exactly what you want. You don't want your script to wait that long.
Fortunately <script> elements have their own onload (ready) event, which can be used to couple other scripts with them.
function include(file, callback) {
var head = document.getElementsByTagName('head')[0];
var script = document.createElement('script');
script.type = 'text/javascript';
script.src = file;
script.onload = script.onreadystatechange = function() {
// execute dependent code
if (callback) callback();
// prevent memory leak in IE
head.removeChild(script);
script.onload = null;
};
head.appendChild(script);
}
include('http://ajax.googleapis.com/.../jquery.min.js', myFunction);
In this case the function will be called exactly when jquery is available. Your code is fail proof and starts quickly.
The script is not loaded or executed when you call the jQuery function. Thats why you get the error that jQuery is not defined.
I answered this problem on Loading Scripts Dynamically. The script is simple for all browsers except IE. Just add an onload event listener
You should first check to make sure they do not already use jquery so something like:
if (jQuery) {
// jQuery is loaded
} else {
// jQuery is not loaded
}
Secondly, you should make sure you use jQuery in no conflict mode and do not use the $ operator as it may be claimed by another script library.
Then to embed, declare this function:
function load_script (url)
{
var xmlhttp;
try {
// Mozilla / Safari / IE7
xmlhttp = new XMLHttpRequest();
} catch (e) {
//Other IE
xmlhttp = new ActiveXObject("Msxml2.XMLHTTP");
}
xmlhttp.open('GET', url, false); x.send('');
eval(xmlhttp.responseText);
var s = xmlhttp.responseText.split(/\n/);
var r = /^function\s*([a-z_]+)/i;
for (var i = 0; i < s.length; i++)
{
var m = r.exec(s[i]);
if (m != null)
window[m[1]] = eval(m[1]);
}
}
Then call it:
load_script('http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js');
Hope this helps.
Use LABjs. Embedding <script> tags work because the browser loads and executes whatever script in it or referenced by it upon seeing any of them, but that also means the browser will block until it is done with the script.
If you're using jQuery, you can use getScript(). Facebook has a good example on how to employ this. This is a more manageable solution to the original one I posted below.
Alternatively:
Building off of galambalazs's answer you can wrap his include() function in an event like the jQuery $(document).ready() to create deferred scripts with a dependency line without having to rely on an extra JS plugin. I also added a callbackOnError, which is handy if you're fetching from Google CDN.
/* galambalazs's include() */
function include(file, callback, callbackOnError) {
var head = document.getElementsByTagName('head')[0];
var script = document.createElement('script');
script.type = 'text/javascript';
script.src = file;
script.onload = script.onreadystatechange = function() {
// execute dependent code
if (callback) callback();
// prevent memory leak in IE
head.removeChild(script);
script.onload = null;
script.onerror = null;
};
script.onerror = function() {
if(callbackOnError) callbackOnError();
// prevent memory leak in IE
head.removeChild(script);
script.onload = null;
script.onerror = null;
};
head.appendChild(script);
}
/* If you need a deferred inline script, this will work */
function init() {
// Insert Code
}
/* Example with callback */
$(document).ready(function() {
include('something.js', init);
});
/* Maybe I want something on window.load() */
$(window).load(function() {
// Without a callback
include('whatever.js');
});
/* Or use an event to load and initialize script when it's needed */
$(".nearAButton").hover(include('button.js',function() {initButtonJS()}));
function initButtonJS() {
$(".nearAButton").off("mouseenter mouseleave");
}
/* Attempt to load Google Code, on fail load local */
include(
'//ajax.googleapis.com/ajax/libs/jqueryui/1.10.3/jquery-ui.min.js',
function () {
// Inline Callback
},
function () {
include('/local/jquery-ui-1.10.3.custom.min.js', function () {
// Inline Callback
});
}
);