I recently found out about load.js, but I can't seem to find any indication of whether or not this is possible... (Note: I can't find a 'load.js' tag..)
I've got load.js successfully loading all my JS files, so I know it works. Has anyone got it working for loading CSS files as well?
Update: remyabel's solution worked perfectly for loading the physical files, but it seems there are a few quirks to this process...
For some reason, the order in which the CSS files are loaded and whether they're all done in one load(file1,file2); or in stages with load(file1).then(file2); seems to affect how the style rules are applied to the markup. I'm going to set up a few test cases on my local machine to try work out how or why this happens, but for now at least the files are being loaded.
Final Note:
Following on from the solution posted below, I've decided to use head.appendChild(script); instead of head.insertBefore(script, head.firstChild); to add the CSS elements to the DOM (still uses the original method for JS files).
This doesn't affect the order in which files are fetched and processed, but it makes Load.js insert my CSS links in the same order they were listed and at the end of the header instead of the beginning.
Direct from the source code
function asyncLoadScript(src) {
return function (onload, onerror) {
var script = document.createElement('script');
script.type = 'text/javascript';
script.src = src;
My suggestion is to modify the script (which doesn't seem to contain much) to mirror the function but for a link tag, rather than a script tag.
to reflect OP's comment
The script is built on top of chain.js so it may be more complicated than expected.
Unless you want something else, I'm pretty sure what I wrote above is what you need to change, so it would look like:
function asyncLoadScript(src) {
return function (onload, onerror) {
// Get file extension
var ext = src.split('.').pop();
if (ext == "js")
{
var script = document.createElement('script');
script.type = 'text/javascript';
script.src = src;
} else if (ext == "css")
{
var script = document.createElement('link');
script.type = 'text/css';
script.href = src;
script.rel = "stylesheet";
}
Theoretically that should work. Make another comment if it doesn't work.
Related
This question already has answers here:
The Chrome extension popup is not working, click events are not handled
(2 answers)
Closed 5 years ago.
Main Question
I'm building a Chrome extension that involves appending an iframe to the page. The extension makes an AJAX call in the background page to retrieve the URLs of the external CSS / JS to include and so I need to dynamically add these resources to the iframe. The iframe code is an HTML page that's included in the extension's web_accessible_resources (it has a URL of chrome-extension://...)
This is what I do in the iframe:
window.onload = function() {
chrome.runtime.sendMessage({action: "getResources"}, function(response) {
// get urls of css / js files
let { css, js } = response; // css and js are arrays of urls retrieved
let head = document.getElementsByTagName('head')[0];
for (let i = 0; i < css.length; i++) {
let link = document.createElement('link');
link.href = css[i];
link.type = "text/css";
link.rel = "stylesheet";
head.appendChild(link);
}
for (let i = 0; i < js.length; i++) {
let script = document.createElement('script');
script.src = js[i];
script.type = "text/javascript";
script.onload = function() {
window.alert('loaded script');
}
head.appendChild(script);
}
}
}
After this, I can log the stylesheets and scripts from the head so they are appended successfully. However, the callback in the script.onload is not called and I tried using getComputedStyle like in this answer to check the CSS and that didn't work. So both the stylesheets and scripts are not actually being executed.
I'm wondering if Chrome extensions just don't allow dynamic injections into iframes.
Additional Info
Here are some things I tried:
Adding the URLs to the permissions
Mentioned here. All the scripts I load are from sites outlined in the permissions field of the manifest.json file so CORS shouldn't be a problem.
Trying hacky ways to load the script
I tried script.innerHTML = "console.log('success')" and also tried head.innerHTML = '<scr' + 'ipt type="text/javascript" src="[URL_TO_LOAD]"></scr' + 'ipt>'. Neither worked. I didn't try any solutions with document.write or eval just because I would never use those even if they worked for security reasons.
SetTimeout
I tried setting a timeout after loading the script in case it took a while to execute but even after 10 seconds, global variables that should've been there are still undefined.
HTTPS
Requesting scripts served over HTTPS also did not work.
Any help would be appreciated.
Turns out it's due to Chrome's Content Security Policy. I found the answer here. However the scripts that can be dynamically injected into the iframe must be either local files or served over HTTPS.
I have been attempting to work out why an injected content script in my Chrome Extension (unpackaged) would not load with a charset parameter in the MIME type (tested using Chrome 28.0.1500.95 stable):
var head = document.head || document.getElementsByTagName('head')[0];
var script = document.createElement('script');
script.src = chrome.extension.getURL("myscript.js");
script.type = 'application/ecmascript;charset=UTF-8';
script.async = false;
script.onload = function () { this.parentNode.removeChild(this); };
head.appendChild(script);
When the ;charset=UTF-8 portion of the code is removed, the script loads normally. However, when that section is included (which appears to be a valid MIME type parameter), the script is loaded to the DOM, but never executed. Does anyone know why this would be the case? Note that the included file is encoded using UTF-8 (no BOM, checked using a hex editor).
EDIT: to clarify, I want to know why it seems that charset parameter doesn't work in Chrome.
I don't think that is how you specify charset. I.e. although it may be a valid addition to the type, the browser may only automatically run text/javascript and application/ecmascript exclusively. I don't know, but try this;
script.type = 'application/ecmascript';
script.charset = 'UTF-8';
It turns out that this was based on a misunderstanding of the specification. When the specification asks not to include the charset, it means that you must not include the ;charset=abc bit, not that the HTML charset attribute cannot be specified.
Basically, I want to do this:
<script src=" path + '/jquery.js'"></script>
(Obviously that won't work.)
I have a standalone page loading external scripts with <script>, but the URL needs to be prefixed with the variable path (because the page is run on different servers in different environments). I easily get path from the URL which always ends with "?path=X" where X is the actual path.
If I had jQuery, I know I could use getScript() to dynamically load external .js files, but jQuery is one of the files I need to load! I don't need anything too complicated here, no need to worry about encoding or file-types, it's a fairly simple situation except for the changing servers.
You need to use plain javascript if jquery not loaded. Something like that:
var fileref=document.createElement('script');
fileref.setAttribute("type","text/javascript");
fileref.setAttribute("src", path + '/jquery.js');
document.getElementsByTagName("head")[0].appendChild(fileref);
fileref.onload = function(){alert('loaded')};
You can load JS files by inserting them into the head with javascript, and at the same you can use your variable for the path when setting the source attribute:
<script type="text/javascript">
var script = document.createElement('script');
script.type = 'text/javascript';
script.async = true;
script.src = path + '/jquery.js';
var head = document.getElementsByTagName('head')[0];
head.appendChild(script);
</script>
I have legacy web app situation where I can't load jquery.min.js from a script tag in the HTML markup.. so I have to load it with some js in another existing script file
var head = document.getElementsByTagName('head')[0];
var script = document.createElement('script');
script.src = 'https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js';
script.type = 'text/javascript';
head.appendChild(script);
The problem is.. when the include load is slow.. there are jQuery functions
(also dynamically loaded on the page) that try to run and can't find jQuery
Is there some cross-browser way to do a callback in the above code that calls the jQuery ready function after the jquery.min.js include file finishes downloading from the CDN? Thanks,
EDIT:
Using Mike's code this is working with onload for nearly all browsers except
IE 8 or earlier.. and other browsers which need onreadystatechange I guess
JSFIDDLE HERE:
http://jsfiddle.net/BmyGC/
try
if(script.onreadystatechange)
script.onreadystatechange = function()
{
if(script.readyState == "complete" || script.readyState=="loaded")
{
script.onreadystatechange = false;
console.log("complete");
}
}
else
{
script.onload = function()
{
console.log("complete");
}
}
I would put my jquery-based code in yet another separate javascript file, and load that file in exactly the same way you are loading the jquery.min.js. Just do so immediately after jquery.min.js. That should work.
edit
Okay, since that isn't working, try this:
function jqChecker()
{
if (! jQuery )
{
setTimeout(jqChecker, 500); // adjust as needed.
}
else
{
// insert code to dynamically load your external js file here
}
}
jqChecker();
The following are the first lines of code in a <script> tag just above the closing body tag in my document (it specifies that a locally-served copy of jQuery is run in the event that Google's CDN fails):
if(!window.jQuery){
var script = document.createElement('script');
script.type = 'text/javascript';
script.src = '/js/jquery.js';
var scriptHook = document.getElementsByTagName('script')[0];
scriptHook.parentNode.insertBefore(script, scriptHook);
}
jQuery(document).ready(function($){
// page behaviors
});
It does execute successfully, in the sense that if my computer is not connected to the Internet (this is a locally-served page), the local copy of jQuery is inserted. However, the document.ready() section below does not execute. I'm guessing this is because it is invoked before the fallback copy of jQuery takes effect. What's the proper practice for somehow "delaying" its execution so that either copy of jQuery will work properly?
Consider using an existing script loader such as yepnope. There's an example of exactly what you're trying to do on the home page.
You need to be sure that the script you are appending to the dom has finished loading before calling jQuery. You can do this with the technique described here:
if(!window.jQuery){
var script = document.createElement('script');
script.type = 'text/javascript';
script.src = '/js/jquery.js';
script.onreadystatechange= function () {
if (this.readyState == 'complete') jQueryLoaded();
}
script.onload = jQueryLoaded;
var scriptHook = document.getElementsByTagName('script')[0];
scriptHook.parentNode.insertBefore(script, scriptHook);
}
function jQueryLoaded() { };
You can also fetch the jQuery contents as an Ajax request, create a script tag with those as the body of the script and append it. That would also work.
Try that
<script>window.jQuery || document.write('<script src="js/libs/jquery-1.6.2.min.js"><\/script>')</script>
<script>
jQuery(document).ready(function($){
// page behaviors
});
</script>
This way the script tag will be loaded synchronously.
The question "of how do I cope with my CDN failing and load a file hosted on my server" seems to come up a few times lately.
Question I'd ask is whether adding yet more js is the way to achieve the resilience and what level of resilience do the js approaches really add e.g. if the CDN is down they'll be a quick failure but how well do these approaches if the CDN is slow to respond how well do these solutions cope?
An alternative way to approach this is treat it as an infrastructure problem...
Run a CDN based on a domain/sub-domain you own. Have automated monitoring on it's availability, when it fails switch the DNS over to a backup server (anycast may provide an alternative solution too)
A php solution would be something like this:
$google_jquery = 'https://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js';
$fp = #fsockopen($google_jquery, 'r');
if (!$fp)
{
echo '<script type="text/javascript" src="js/jquery.js"></script>';
}
else
{
echo '<script src="'.$google_jquery.'"></script>' }
}