Question not for solution, Question to understand the system better
Experts! I know whenever you feed javascript code into javascript engine, It will execute by javascript engine immediately. Since, I haven't seen Engine's source code, I have few of questions as follows,
Let us assume I am loading couple of files from remote server namely FILE_1.js and FILE_2.js.
And the code in FILE_2.js is requiring some of the code in FILE_1.js. So I have included files as follows,
<script type="text/javascript" src="FILE_1.js" ></script>
<script type="text/javascript" src="FILE_2.js" ></script>
So hopefully, I have done what Javascript Engine requires. Here unfortunately I have written 5000KB of code in FILE_1.js, and however I have 5KB of code in FILE_2.js. Since server is multi-threaded definitely FILE_2.js will be loaded into my browser before FILE_1.js completed.
How javascript engine handle this?
And If moved the code from FILE_2.js to inline-script tag as follows, what are actions taken by javascript engine to manage this dependency?
<script type="text/javascript" src="FILE_1.js" ></script>
<script type="text/javascript" >
// Dependent code goes here
</script>
Note: I am not expecting single word answer Single Threaded. I just want to know deep who is manage issuing request either browser or javascript engine or common guy? if the request/response is handled by common guy, then how javascript engine aware about this?
When I post an answer about the behavior of code, I always like to go to two places:
The specification
The implementation
The specification:
The DOM API explicitly specifies scripts must be executed in order:
If the element has a src attribute, does not have an async attribute, and does not have the "force-async" flag set
The element must be added to the end of the list of scripts that will execute in order as soon as possible associated with the Document of the script element at the time the prepare a script algorithm started.
From 4.1 Scripting. Please check the list of exceptions to this rule before - having the defer or async attribute. This is specified well in 4.12.1.15.
This makes sense, imagine:
//FILE_1.js
var trololo = "Unicorn";
....
// 1 million lines later
trololo = "unicorn";
var message = "Hello World";
//FILE_2.js
alert(message); // if file 1 doesn't execute first, this throws a reference error.
It is generally better to use a module loader (that will defer script insertion and execution, and will manage dependencies correctly for you).
At the moment, the best approach is to use something like Browserify or RequireJS . In the future, we'll be able to use ECMAScript 6 modules.
The implementation:
Well, you mentioned it and I couldn't resist. So, if we check the Chromium blink source (still similar in WebKit):
bool ScriptLoader::prepareScript(const TextPosition& scriptStartPosition,
LegacyTypeSupport supportLegacyTypes)
{
.....
} else if (client->hasSourceAttribute() && // has src attribute
!client->asyncAttributeValue() &&// and no `async` or `defer`
!m_forceAsync // and it was not otherwise forced
) { // - woah, this is just like the spec
m_willExecuteInOrder = true; // tell it to execute in order
contextDocument->scriptRunner()->queueScriptForExecution(this,
m_resource,
ScriptRunner::IN_ORDER_EXECUTION);
Great, so we can see in the source code that it adds them in order parsed - just like the specification says.
Let's see how a script runner does:
void ScriptRunner::queueScriptForExecution(ScriptLoader* scriptLoader,
ResourcePtr<ScriptResource> resource,
ExecutionType executionType){
.....
// Adds it in the order of execution, as we can see, this just
// adds it to a queue
case IN_ORDER_EXECUTION:
m_scriptsToExecuteInOrder.append(PendingScript(element, resource.get()));
break;
}
And, using a timer, it fires them one by one when ready (or immediately, if nothing is pending):
void ScriptRunner::timerFired(Timer<ScriptRunner>* timer)
{
...
scripts.swap(m_scriptsToExecuteSoon);
for (size_t i = 0; i < size; ++i) {
....
//fire!
toScriptLoaderIfPossible(element.get())->execute(resource);
m_document->decrementLoadEventDelayCount();
}
Related
I'm just a bit confused. I've been going through a presentation slide deck on Content Security Policies. On slide 10, when discussing the 'strict-dynamic' directive, three examples are given: one good, two bad:
// good
<script nonce="r4nd0m">
var s = document.createElement("script");
s.src = "//example.com/bar.js";
document.body.appendChild(s);
</script>
// bad
<script nonce="r4nd0m">
var s = "<script src=//example.com/bar.js></script>";
document.write(s);
// -OR-
document.body.innerHTML = s;
</script>
Google also advises to make similar changes on their CSP page ("Refactor calls to JS APIs incompatible with CSP").
I understand that the good example loads async while the bad one doesn't (unless async defer is added). But...
If I wanted to load an evil script, wouldn't both ways get me there?
Is the issue that the "bad" way doesn't propagate the nonce (automatically)? Or is it about the "bad" way being a bit like a call to eval() in that you can do pretty much anything with it (although, in HTML 5, a script wouldn't execute unless attached to an event handler, apparently)? Is the assumption that the "bad" way likely uses parser-inserted user input and that's why it's bad (that's what I got from the presentation video I found later)?
Why is one call permissible while the other isn't (when, in the example given, they seem to achieve the same thing - loading an external script)?
I am trying to use Modernizr to detect if a browser can use indexedDB.
In both Firefox71 and Chromium79, I'm not getting the expected result true for Modernizr.indexeddb.
test index.html:
<!DOCTYPE html>
<html>
<head>
<title>Main</title>
<script src="modernizr-custom-indexeddb.js"></script>
</head>
<body>
<script>
switch (Modernizr.indexeddb) {
case false: alert('false'); break;
case undefined: alert('undefined'); break;
case true: alert('true'); break;
}
switch (Modernizr.indexeddbblob) {
case false: alert('false'); break;
case undefined: alert('undefined'); break;
case true: alert('true'); break;
}
</script>
</body>
</html>
Command line config file to generate modernizr-custom-indexeddb.js:
{
"minify": true,
"options": [
"setClasses"
],
"feature-detects": [
"test/indexeddb"
]
}
When index.html is loaded, Modernizr is adding the css class indexeddb to the <html> tag. If I remove the switch/alert inline script, this happens almost immediately. The clue to the problem might be in the 'almost' -- the delay is less than one second but discernable.
In Chromium79, Modernizr.indexeddb is always undefined.
In Firefox71, Modernizr.indexeddb is almost always undefined (multiple reloads).
(Modernizr.indexeddbblob is always undefined in each).
In Chromium79, the css class indexeddb-deletedatabase is added about 6 seconds after indexeddb is added.
In Firefox71, the delay between the two is about 40 seconds!
My guess is that Modernizr.indexeddb is not getting set to true in time to be accessed by the switch/alert script. But perhaps I am using Modernizr incorrectly.
In load and execute order of scripts, the accepted answer makes reference to the loading and running of scripts. But the time when a script is run is not the same as the time when a script completes.
There will be a point at which Modernizr returns it's Modernizer.* values(s). Assuming the problem is a race condition, can this moment be detected by another script? Or is the only option a check...wait...check solution, such as suggested in the accepted answer to load jQuery script after JavaScript script has finished?
I have tried using setTimeout() on an anonymous function to call the switch/alert section, which does then yield the 'correct' results. But then the question is, for how long to delay...?
Or am I looking for the wrong solution?
Notes.
A)
Without setTimeout(), both results come back undefined.
When using setTimeout() the results are coming back true (for indexeddb) followed by undefined (for indexeddbblob).
So we can infer that undefined means either
feature not available
OR
result not yet returned
B)
load and execute order of scripts also says: "The relevant part of the HTML5 spec (for newer compliant browsers) is here. There is a lot written in there about async behavior. Obviously, this spec doesn't apply to older browsers (or mal-conforming browsers) whose behavior you would probably have to test to determine."
So it may be that the solution to this sync/async/race-condition issue (assuming that's what it is) cannot be applied to older/mal-conformant browsers. Which defeats the object of discovering if a browser can support something using a javascript-based system like Modernizr.
...so then presumably we need something like Babel... turtles all the way down?
I'm hoping for an answer which begins, "You're making things way too complicated" :)
Why does a linked JavaScript file sometimes not work when it is included at the top of the page and not at the bottom?
<script type="text/javascript" src"..."></script>
For example, if you want to manipulate DOM items, and those are not yet existing, it won't work. If the JavaScript file is included in the head, the body is not existing yet, but if you include it at the end of the body, those items are valid.
If you don't want to rely on this behaviour, you may define a callback, which is run, when the document is ready, i.e. when the whole of the DOM is loaded already.
This is what e.g. jQuery achieves with $(document).ready(function() {}), or more shortly $(function () {});. In vanilla JavaScript (using modern browsers, so IE9+) this can be achieved using
document.addEventListener("DOMContentLoaded", function() {
// code...
});
The best way to know why is it not working is by checking for JS error. Try to find out what errors you are getting when the script has been included at the top. As mentioned in the other response it can be because of DOM items. You can circumvent this issue by adding a "defer" tag to the script.
It can also be because of some JS object you are expecting to be present when this script runs. For example if your script tag is serving a JSONP request then you must have the function that processes the data. Otherwise you will get a "undefined function" error when the script runs.
JS code is executed instruction by instruction from top to bottom.
The code that calls a function needs to be under that functions definition.
This code works:
var func = function()
{
alert('it works');
};
func();
While this doesn't:
func();
var func = function()
{
alert('it works');
};
It throws an undefined error. The reason for this is that JS compiler is not aware of the func definition at the time it tries to call it.
Same goes for the JS files included in your HTML page. You can include them at the bottom as long as there are not dependencies in above sections, or, if they do not try to manipulate HTML code before page load.
I provide a JavaScript widget to several web sites, which they load asynchronously. My widget in turn needs to load a script provided by another party, outside my control.
There are several ways to check whether that script has successfully loaded. However, I also need to run different code if that script load has failed.
The obvious tools that don't work include:
I'm not willing to use JavaScript libraries, such as jQuery. I need a very small script to minimize my impact on the sites that use my widget.
I want to detect the failure as soon as possible, so using a timer to poll it is undesirable. I wouldn't mind using a timer as a last resort on old browsers, though.
I've found the <script> tag's onerror event to be unreliable in some major browsers. (It seemed to depend on which add-ons were installed.)
Anything involving document.write is right out. (Besides that method being intrinsically evil, my code is loaded asynchronously so document.write may do bad things to the page.)
I had a previous solution that involved loading the <script> in a new <iframe>. In that iframe, I set a <body onload=...> event handler that checked whether the <script onload=...> event had already fired. Because the <script> was part of the initial document, not injected asynchronously later, onload only fired after the network layer was done with the <script> tag.
However, now I need the script to load in the parent document; it can't be in an iframe any more. So I need a different way to trigger code as soon as the network layer has given up trying to fetch the script.
I read "Deep dive into the murky waters of script loading" in an attempt to work out what ordering guarantees I can count on across browsers.
If I understand the techniques documented there:
I need to place my failure-handling code in a separate .js file.
Then, on certain browsers I can ensure that my code runs only after the third-party script either has run or has failed. This requires browsers that support either:
Setting the <script async> attribute to false via the DOM,
or using <script onreadystatechange=...> on IE 6+.
Despite looking at the async support table, I can't tell whether I can rely on script ordering in enough browsers for this to be feasible.
So how can I reliably handle failure during loading of a script I don't control?
I believe I've solved the question I asked, though it turns out this doesn't solve the problem I actually had. Oh well. Here's my solution:
We want to run some code after the browser finishes attempting to load a third-party script, so we can check whether it loaded successfully. We accomplish that by constraining the load of a fallback script to happen only after the third-party script has either run or failed. The fallback script can then check whether the third-party script created the globals it was supposed to.
Cross-browser in-order script loading inspired by http://www.html5rocks.com/en/tutorials/speed/script-loading/.
var fallbackLoader = doc.createElement(script),
thirdPartyLoader = doc.createElement(script),
thirdPartySrc = '<URL to third party script>',
firstScript = doc.getElementsByTagName(script)[0];
// Doesn't matter when we fetch the fallback script, as long as
// it doesn't run early, so just set src once.
fallbackLoader.src = '<URL to fallback script>';
// IE starts fetching the fallback script here.
if('async' in firstScript) {
// Browser support for script.async:
// http://caniuse.com/#search=async
//
// By declaring both script tags non-async, we assert
// that they need to run in the order that they're added
// to the DOM.
fallbackLoader.async = thirdPartyLoader.async = false;
thirdPartyLoader.src = thirdPartySrc;
doc.head.appendChild(thirdPartyLoader);
doc.head.appendChild(fallbackLoader);
} else if(firstScript.readyState) {
// Use readyState for IE 6-9. (IE 10+ supports async.)
// This lets us fetch both scripts but refrain from
// running them until we know that the fetch attempt has
// finished for the first one.
thirdPartyLoader.onreadystatechange = function() {
if(thirdPartyLoader.readyState == 'loaded') {
thirdPartyLoader.onreadystatechange = null;
// The script-loading tutorial comments:
// "can't just appendChild, old IE bug
// if element isn't closed"
firstScript.parentNode.insertBefore(thirdPartyLoader, firstScript);
firstScript.parentNode.insertBefore(fallbackLoader, firstScript);
}
};
// Don't set src until we've attached the
// readystatechange handler, or we could miss the event.
thirdPartyLoader.src = thirdPartySrc;
} else {
// If the browser doesn't support async or readyState, we
// just won't worry about the case where script loading
// fails. This is <14% of browsers worldwide according to
// caniuse.com, and hopefully script loading will succeed
// often enough for them that this isn't a problem.
//
// If that isn't good enough, you might try setting an
// onerror listener in this case. That still may not work,
// but might get another small percentage of old browsers.
// See
// http://blog.lexspoon.org/2009/12/detecting-download-failures-with-script.html
thirdPartyLoader.src = thirdPartySrc;
firstScript.parentNode.insertBefore(thirdPartyLoader, firstScript);
}
Have you considered using the window's onerror handler? That will let you detect when most errors occur and you can take appropriate action then. As a fallback for any issues not caught this way you can also protect your own code with try/catch.
You should also check that the third-party script actually loaded:
<script type="text/javascript" onload="loaded=1" src="thirdparty.js"></script>
Then check if it loaded:
window.onload = function myLoadHandler() {
if (loaded == null) {
// The script doesn't exist or couldn't be loaded!
}
}
You can check which script caused the error using the url parameter.
window.onerror = function myErrorHandler(errorMsg, url, lineNumber) {
if (url == third_party_script_url) {
// Do alternate code
} else {
return false; // Do default error handling
}
}
So, as a sort of exercise for myself, I'm writing a little async script loader utility (think require.js, head.js, yepnope.js), and have run across a little bit of a conundrum. First, the basic syntax is like this:
using("Models/SomeModel", function() {
//callback when all dependencies loaded
});
Now, I want to know, when this call is made, what file I'm in. I could do it with an ajax call, so that I can mark a flag after the content loads, but before I eval it to mark that all using calls are going to be for a specific file, then unset the flag immediately after the eval (I know eval is evil, but in this case it's javascript in the first place, not json, so it's not AS evil). I'm pretty sure this would get what I need, however I would prefer to do this with a script tag for a few reasons:
It's semantically more correct
Easier to find scripts for debugging (unique file names are much easier to look through than anonymous script blocks and debugger statements)
Cross-domain requests. I know I could try to use XDomainRequest, but most servers aren't going to be set up for that, and I want the ability to reference external scripts on CDN's.
I tried something that almost got me what I needed. I keep a list of every time using is called. When one of the scripts loads, I take any of those using references and incorporate them into the correct object for the file that just loaded, and clear the global list. This actually seems to work alright in Firefox and Chrome, but fails in IE because the load events seem to go off at weird times (a jQuery reference swallowed a reference to another type and ended up showing it as a dependency). I thought I could latch on to the "interactive" readystate, but it doesn't appear to ever happen.
So now I come asking if anybody here has any thoughts on this. If y'all want, I can post the code, but it's still very messy and probably hard to read.
Edit: Additional usages
//aliasing and multiple dependencies
using.alias("ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js", "jQuery");
using(["jQuery", "Models/SomeModel"], function() {
//should run after both jQuery and SomeModel have been loaded and run
});
//css and conditionals (using some non-existant variables here)
using.css({ src: "IEFix", conditionally: browser === "MSIE" && version < 9 });
//should include the IEFix.css file if the browser is IE8 or below
and to expound more on my response below, consider this to be file A (and consider the jquery alias from before to be there still):
using(["jQuery", "B"], function() {
console.log("This should be last (after both jQuery and B have loaded)");
console.log(typeof($));
});
Then this would be B:
using("C", function() {
console.log("This should be second");
});
And finally, C:
console.log("This should be first");
The output should be:
This should be first
This should be second
This should be last (after both jQuery and B have loaded)
[Object Object]
Commendable that you are taking on such an educational project.
However, you won't be able to pull it off quite the way you want to do it.
The good news is:
No need to know what file you are in
No need to mess with eval.
You actually have everything you need right there: A function reference. A callback, if you will.
A rough P-code for your using function would be:
function using(modules, callback) {
var loadedModules = []
// This will be an ajax call to load things, several different ways to do it..
loadedModules[0] = loadModule(modules[0]);
loadedModules[1] = loadModule(modules[1]);
// Great, now we have all the modules
// null = value for `this`
callback.apply(null, loadedModules);
}