components

This commit is contained in:
Ilya Kantor 2019-04-02 14:01:44 +03:00
parent 304d578b54
commit 6fb4aabcba
344 changed files with 669 additions and 406 deletions

View file

@ -0,0 +1,285 @@
# Page: DOMContentLoaded, load, beforeunload, unload
The lifecycle of an HTML page has three important events:
- `DOMContentLoaded` -- the browser fully loaded HTML, and the DOM tree is built, but external resources like pictures `<img>` and stylesheets may be not yet loaded.
- `load` -- the browser loaded all resources (images, styles etc).
- `beforeunload/unload` -- when the user is leaving the page.
Each event may be useful:
- `DOMContentLoaded` event -- DOM is ready, so the handler can lookup DOM nodes, initialize the interface.
- `load` event -- additional resources are loaded, we can get image sizes (if not specified in HTML/CSS) etc.
- `beforeunload` event -- the user is leaving: we can check if the user saved the changes and ask them whether they really want to leave.
- `unload` -- the user almost left, but we still can initiate some operations, such as sending out statistics.
Let's explore the details of these events.
## DOMContentLoaded
The `DOMContentLoaded` event happens on the `document` object.
We must use `addEventListener` to catch it:
```js
document.addEventListener("DOMContentLoaded", ready);
// not "document.onDOMContentLoaded = ..."
```
For instance:
```html run height=200 refresh
<script>
function ready() {
alert('DOM is ready');
// image is not yet loaded (unless was cached), so the size is 0x0
alert(`Image size: ${img.offsetWidth}x${img.offsetHeight}`);
}
*!*
document.addEventListener("DOMContentLoaded", ready);
*/!*
</script>
<img id="img" src="https://en.js.cx/clipart/train.gif?speed=1&cache=0">
```
In the example the `DOMContentLoaded` handler runs when the document is loaded, so it can see all the elements, including `<img>` below.
But it doesn't wait for the image to load. So `alert` shows zero sizes.
At the first sight `DOMContentLoaded` event is very simple. The DOM tree is ready -- here's the event. There are few peculiarities though.
### DOMContentLoaded and scripts
When the browser processes an HTML-document and comes across a `<script>` tag, it needs to execute before continuing building the DOM. That's a precaution, as scripts may want to modify DOM, and even `document.write` into it, so `DOMContentLoaded` has to wait.
So DOMContentLoaded definitely happens after such scripts:
```html run
<script>
document.addEventListener("DOMContentLoaded", () => {
console.log("DOM ready!");
});
</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.3.0/lodash.js"></script>
<script>
alert("Library loaded, inline script executed");
</script>
```
In the example above, we first see "Library loaded...", and then "DOM ready!" (all scripts are executed).
```warn header="Scripts with `async`, `defer` or `type=\"module\"` don't block DOMContentLoaded"
Script attributes `async` and `defer`, that we'll cover [a bit later](info:script-async-defer), don't block DOMContentLoaded. [Javascript modules](info:modules) behave like `defer`, they don't block it too.
So here we're talking about "regular" scripts, like `<script>...</script>`, or `<script src="..."></script>`.
```
### DOMContentLoaded and styles
External style sheets don't affect DOM, so `DOMContentLoaded` does not wait for them.
But there's a pitfall. Isf we have a script after the style, then that script must wait until the stylesheet loads:
```html
<link type="text/css" rel="stylesheet" href="style.css">
<script>
// the script doesn't not execute until the stylesheet is loaded
alert(getComputedStyle(document.body).marginTop);
</script>
```
The reason is that the script may want to get coordinates and other style-dependent properties of elements, like in the example above. Naturally, it has to wait for styles to load.
As `DOMContentLoaded` waits for scripts, it now waits for styles before them as well.
### Built-in browser autofill
Firefox, Chrome and Opera autofill forms on `DOMContentLoaded`.
For instance, if the page has a form with login and password, and the browser remembered the values, then on `DOMContentLoaded` it may try to autofill them (if approved by the user).
So if `DOMContentLoaded` is postponed by long-loading scripts, then autofill also awaits. You probably saw that on some sites (if you use browser autofill) -- the login/password fields don't get autofilled immediately, but there's a delay till the page fully loads. That's actually the delay until the `DOMContentLoaded` event.
## window.onload [#window-onload]
The `load` event on the `window` object triggers when the whole page is loaded including styles, images and other resources.
The example below correctly shows image sizes, because `window.onload` waits for all images:
```html run height=200 refresh
<script>
window.onload = function() {
alert('Page loaded');
// image is loaded at this time
alert(`Image size: ${img.offsetWidth}x${img.offsetHeight}`);
};
</script>
<img id="img" src="https://en.js.cx/clipart/train.gif?speed=1&cache=0">
```
## window.onunload
When a visitor leaves the page, the `unload` event triggers on `window`. We can do something there that doesn't involve a delay, like closing related popup windows.
The notable exception is sending analytics.
Let's say we gather data about how the page is used: mouse clicks, scrolls, viewed page areas, and so on.
Naturally, `unload` event is when the user leaves us, and we'd like to save the data on our server.
There exists a special `navigator.sendBeacon(url, data)` method for such needs, described in the specification <https://w3c.github.io/beacon/>.
It sends the data in background. The transition to another page is not delayed: the browser leaves the page, but still performs `sendBeacon`.
Here's how to use it:
```js
let analyticsData = { /* object with gathered data */ };
window.addEventListener("unload", function() {
navigator.sendBeacon("/analytics", JSON.stringify(analyticsData));
};
```
- The request is sent as POST.
- We can send not only a string, but also forms and other formats, as described in the chapter <info:fetch-basics>, but usually it's a stringified object.
- The data is limited by 64kb.
When the `sendBeacon` request is finished, the browser probably has already left the document, so there's no way to get server response (which is usually empty for analytics).
There's also a `keepalive` flag for doing such "after-page-left" requests in [fetch](info:fetch-basics) method for generic network requests. You can find more information in the chapter <info:fetch-api>.
If we want to cancel the transition to another page, we can't do it here. But we can use another event -- `onbeforeunload`.
## window.onbeforeunload [#window.onbeforeunload]
If a visitor initiated navigation away from the page or tries to close the window, the `beforeunload` handler asks for additional confirmation.
If we cancel the event, the browser may ask the visitor if they are sure.
You can try it by running this code and then reloading the page:
```js run
window.onbeforeunload = function() {
return false;
};
```
For historical reasons, returning a non-empty string also counts as canceling the event. Some time ago browsers used show it as a message, but as the [modern specification](https://html.spec.whatwg.org/#unloading-documents) says, they shouldn't.
Here's an example:
```js run
window.onbeforeunload = function() {
return "There are unsaved changes. Leave now?";
};
```
The behavior was changed, because some webmasters abused this event handler by showing misleading and annoying messages. So right now old browsers still may show it as a message, but aside of that -- there's no way to customize the message shown to the user.
## readyState
What happens if we set the `DOMContentLoaded` handler after the document is loaded?
Naturally, it never runs.
There are cases when we are not sure whether the document is ready or not. We'd like our function to execute when the DOM is loaded, be it now or later.
The `document.readyState` property tells us about the current loading state.
There are 3 possible values:
- `"loading"` -- the document is loading.
- `"interactive"` -- the document was fully read.
- `"complete"` -- the document was fully read and all resources (like images) are loaded too.
So we can check `document.readyState` and setup a handler or execute the code immediately if it's ready.
Like this:
```js
function work() { /*...*/ }
if (document.readyState == 'loading') {
// loading yet, wait for the event
document.addEventListener('DOMContentLoaded', work);
} else {
// DOM is ready!
work();
}
```
There's also `readystatechange` event that triggers when the state changes, so we can print all these states like this:
```js run
// current state
console.log(document.readyState);
// print state changes
document.addEventListener('readystatechange', () => console.log(document.readyState));
```
The `readystatechange` event is an alternative mechanics of tracking the document loading state, it appeared long ago. Nowadays, it is rarely used.
Let's see the full events flow for the completeness.
Here's a document with `<iframe>`, `<img>` and handlers that log events:
```html
<script>
log('initial readyState:' + document.readyState);
document.addEventListener('readystatechange', () => log('readyState:' + document.readyState));
document.addEventListener('DOMContentLoaded', () => log('DOMContentLoaded'));
window.onload = () => log('window onload');
</script>
<iframe src="iframe.html" onload="log('iframe onload')"></iframe>
<img src="http://en.js.cx/clipart/train.gif" id="img">
<script>
img.onload = () => log('img onload');
</script>
```
The working example is [in the sandbox](sandbox:readystate).
The typical output:
1. [1] initial readyState:loading
2. [2] readyState:interactive
3. [2] DOMContentLoaded
4. [3] iframe onload
5. [4] img onload
6. [4] readyState:complete
7. [4] window onload
The numbers in square brackets denote the approximate time of when it happens. Events labeled with the same digit happen approximately at the same time (+- a few ms).
- `document.readyState` becomes `interactive` right before `DOMContentLoaded`. These two things actually mean the same.
- `document.readyState` becomes `complete` when all resources (`iframe` and `img`) are loaded. Here we can see that it happens in about the same time as `img.onload` (`img` is the last resource) and `window.onload`. Switching to `complete` state means the same as `window.onload`. The difference is that `window.onload` always works after all other `load` handlers.
## Summary
Page load events:
- `DOMContentLoaded` event triggers on `document` when DOM is ready. We can apply JavaScript to elements at this stage.
- Script such as `<script>...</script>` or `<script src="..."></script>` block DOMContentLoaded, the browser waits for them to execute.
- Images and other resources may also still continue loading.
- `load` event on `window` triggers when the page and all resources are loaded. We rarely use it, because there's usually no need to wait for so long.
- `beforeunload` event on `window` triggers when the user wants to leave the page. If we cancel the event, browser asks whether the user really wants to leave (e.g we have unsaved changes).
- `unload` event on `window` triggers when the user is finally leaving, in the handler we can only do simple things that do not involve delays or asking a user. Because of that limitation, it's rarely used. We can send out a network request with `navigator.sendBeacon`.
- `document.readyState` is the current state of the document, changes can be tracked in the `readystatechange` event:
- `loading` -- the document is loading.
- `interactive` -- the document is parsed, happens at about the same time as `DOMContentLoaded`, but before it.
- `complete` -- the document and resources are loaded, happens at about the same time as `window.onload`, but before it.

View file

@ -0,0 +1,9 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
</head>
<body>
test iframe
</body>
</html>

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,19 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<script>
function setHandler() {
window.onbeforeunload = function() {
return "There are unsaved changes. Leave now?";
};
}
</script>
<button onclick="setHandler()">Set window.onbeforeunload</button>
<a href="http://example.com">Leave for EXAMPLE.COM</a>
</body>
</html>

View file

@ -0,0 +1,197 @@
# Scripts: async, defer
In modern websites, scripts are often "heavier" than HTML: their download size is larger, and processing time is also longer.
When the browser loads HTML and comes across a `<script>...</script>` tag, it can't continue building DOM. It must execute the script right now. The same happens for external scripts `<script src="..."></script>`: the browser must wait until the script downloads, execute it, and only after process the rest of the page.
That leads to two important issues:
1. Scripts can't see DOM elements below them, so can't add handlers etc.
2. If there's a bulky script at the top of the page, it "blocks the page". Users can't see the page content till it downloads and runs:
```html run height=100
<p>...content before script...</p>
<script src="https://javascript.info/article/script-async-defer/long.js?speed=1"></script>
<!-- This isn't visible until the script loads -->
<p>...content after script...</p>
```
There are some workarounds to that. For instance, we can put a script at the bottom of the page. Then it can see elements above it, and it doesn't block the page content from showing:
```html
<body>
...all content is above the script...
<script src="https://javascript.info/article/script-async-defer/long.js?speed=1"></script>
</body>
```
But this solution is far from perfect. For example, the browser actually notices the script (and can start downloading it) only after it downloaded the full HTML document. For long HTML documents, that may be a noticeable delay.
Such things are invisible for people using very fast connections, but many people in the world still have slower internet speeds and far-from-perfect mobile connectivity.
Luckily, there are two `<script>` attributes that solve the problem for us: `defer` and `async`
## defer
The `defer` attribute tells the browser that it should go on working with the page, and load the script "in background", then run the script when it loads.
Here's the same example as above, but with `defer`:
```html run height=100
<p>...content before script...</p>
<script defer src="https://javascript.info/article/script-async-defer/long.js?speed=1"></script>
<!-- visible immediately -->
<p>...content after script...</p>
```
- Scripts with `defer` never block the page.
- Scripts with `defer` always execute when the DOM is ready, but before `DOMContentLoaded` event.
The following example demonstrates that:
```html run height=100
<p>...content before scripts...</p>
<script>
document.addEventListener('DOMContentLoaded', () => alert("DOM ready after defer!")); // (2)
</script>
<script defer src="https://javascript.info/article/script-async-defer/long.js?speed=1"></script>
<p>...content after scripts...</p>
```
1. The page content shows up immediately.
2. `DOMContentLoaded` waits for the deferred script. It only triggers when the script `(2)` is downloaded is executed.
Deferred scripts keep their relative order, just like regular scripts.
So, if we have a long script first, and then a smaller one, then the latter one waits.
```html
<script async src="https://javascript.info/article/script-async-defer/long.js"></script>
<script async src="https://javascript.info/article/script-async-defer/small.js"></script>
```
```smart header="The small script downloads first, runs second"
Browsers scan the page for scripts and download them in parallel, to improve performance. So in the example above both scripts download in parallel. The `small.js` probably makes it first.
But the specification requres scripts to execute in the document order, so it waits for `long.js` to execute.
```
```smart header="The `defer` attribute is only for external scripts"
The `defer` attribute is ignored if the script has no `src`.
```
## async
The `async` attribute means that a script is completely independant:
- The page doesn't wait for async scripts, the contents is processed and displayed.
- `DOMContentLoaded` and async scripts don't wait each other:
- `DOMContentLoaded` may happen both before an async script (if an async script finishes loading after the page is complete)
- ...or after an async script (if an async script is short or was in HTTP-cache)
- Other scripts don't wait for `async` scripts, and `async` scripts don't wait for them.
So, if we have several `async` scripts, they may execute in any order. Whatever loads first -- runs first:
```html run height=100
<p>...content before scripts...</p>
<script>
document.addEventListener('DOMContentLoaded', () => alert("DOM ready!"));
</script>
<script async src="https://javascript.info/article/script-async-defer/long.js"></script>
<script async src="https://javascript.info/article/script-async-defer/small.js"></script>
<p>...content after scripts...</p>
```
1. The page content shows up immediately: `async` doesn't block it.
2. `DOMContentLoaded` may happen both before and after `async`, no guarantees here.
3. Async scripts don't wait for each other. A smaller script `small.js` goes second, but probably loads before `long.js`, so runs first. That's called a "load-first" order.
Async scripts are great when we integrate an independant third-party script into the page: counters, ads and so on.
```html
<script async src="https://google-analytics.com/analytics.js"></script>
```
## Dynamic scripts
We can also create a script dynamically using Javascript:
```js run
let script = document.createElement('script');
script.src = "/article/script-async-defer/long.js";
document.body.append(script); // (*)
```
The script starts loading as soon as it's appended to the document `(*)`.
**Dynamic scripts behave as "async" by default.**
That is:
- They don't wait for anything, nothing waits for them.
- The script that loads first -- runs first ("load-first" order).
We can change the load-first order into the document order by explicitly setting `async` to `false`:
```js run
let script = document.createElement('script');
script.src = "/article/script-async-defer/long.js";
*!*
script.async = false;
*/!*
document.body.append(script);
```
For example, here we add two scripts. Without `script.async=false` they would execute in load-first order (the `small.js` probably first). But with that flag the order is "as in the document":
```js run
function loadScript(src) {
let script = document.createElement('script');
script.src = src;
script.async = false;
document.body.append(script);
}
// long.js runs first because of async=false
loadScript("/article/script-async-defer/long.js");
loadScript("/article/script-async-defer/small.js");
```
## Summary
Both `async` and `defer` have one common thing: they don't block page rendering. So the user can read page content and get acquanted with the page immediately.
But there are also essential differences between them:
| | Order | `DOMContentLoaded` |
|---------|---------|---------|
| `async` | *Load-first order*. Their document order doesn't matter -- which loads first | Irrelevant. May load and execute while the document has not yet been fully downloaded. That happens if scripts are small or cached, and the document is long enough. |
| `defer` | *Document order* (as they go in the document). | Execute after the document is loaded and parsed (they wait if needed), right before `DOMContentLoaded`. |
```warn header="Page without scripts should be usable"
Please note that if you're using `defer`, then the page is visible before the script loads and enables all the graphical components.
So, buttons should be disabled by CSS or by other means, to let the user
In practice, `defer` is used for scripts that need DOM and/or their relative execution order is important.
So `async` is used for independent scripts, like counters or ads, that don't need to access page content. And their relative execution order does not matter.

View file

@ -0,0 +1,32 @@
// ...long js... ...long js... ...long js... ...long js... ...long js... ...long js...
// ...long js... ...long js... ...long js... ...long js... ...long js... ...long js...
// ...long js... ...long js... ...long js... ...long js... ...long js... ...long js...
// ...long js... ...long js... ...long js... ...long js... ...long js... ...long js...
// ...long js... ...long js... ...long js... ...long js... ...long js... ...long js...
// ...long js... ...long js... ...long js... ...long js... ...long js... ...long js...
// ...long js... ...long js... ...long js... ...long js... ...long js... ...long js...
// ...long js... ...long js... ...long js... ...long js... ...long js... ...long js...
// ...long js... ...long js... ...long js... ...long js... ...long js... ...long js...
// ...long js... ...long js... ...long js... ...long js... ...long js... ...long js...
// ...long js... ...long js... ...long js... ...long js... ...long js... ...long js...
// ...long js... ...long js... ...long js... ...long js... ...long js... ...long js...
// ...long js... ...long js... ...long js... ...long js... ...long js... ...long js...
// ...long js... ...long js... ...long js... ...long js... ...long js... ...long js...
// ...long js... ...long js... ...long js... ...long js... ...long js... ...long js...
// ...long js... ...long js... ...long js... ...long js... ...long js... ...long js...
// ...long js... ...long js... ...long js... ...long js... ...long js... ...long js...
// ...long js... ...long js... ...long js... ...long js... ...long js... ...long js...
// ...long js... ...long js... ...long js... ...long js... ...long js... ...long js...
// ...long js... ...long js... ...long js... ...long js... ...long js... ...long js...
// ...long js... ...long js... ...long js... ...long js... ...long js... ...long js...
// ...long js... ...long js... ...long js... ...long js... ...long js... ...long js...
// ...long js... ...long js... ...long js... ...long js... ...long js... ...long js...
// ...long js... ...long js... ...long js... ...long js... ...long js... ...long js...
// ...long js... ...long js... ...long js... ...long js... ...long js... ...long js...
// ...long js... ...long js... ...long js... ...long js... ...long js... ...long js...
// ...long js... ...long js... ...long js... ...long js... ...long js... ...long js...
// ...long js... ...long js... ...long js... ...long js... ...long js... ...long js...
// ...long js... ...long js... ...long js... ...long js... ...long js... ...long js...
// ...long js... ...long js... ...long js... ...long js... ...long js... ...long js...
alert("Long script loaded");

View file

@ -0,0 +1,3 @@
// ...small js...
alert("Small script loaded");

View file

@ -0,0 +1,19 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<script>
function setHandler() {
window.onbeforeunload = function() {
return "There are unsaved changes. Leave now?";
};
}
</script>
<button onclick="setHandler()">Set window.onbeforeunload</button>
<a href="http://example.com">Leave for EXAMPLE.COM</a>
</body>
</html>

View file

@ -0,0 +1,6 @@
The algorithm:
1. Make `img` for every source.
2. Add `onload/onerror` for every image.
3. Increase the counter when either `onload` or `onerror` triggers.
4. When the counter value equals to the sources count -- we're done: `callback()`.

View file

@ -0,0 +1,54 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<script>
function preloadImages(sources, callback) {
let counter = 0;
function onLoad() {
counter++;
if (counter == sources.length) callback();
}
for(let source of sources) {
let img = document.createElement('img');
img.onload = img.onerror = onLoad;
img.src = source;
}
}
// ---------- The test ----------
let sources = [
"https://en.js.cx/images-load/1.jpg",
"https://en.js.cx/images-load/2.jpg",
"https://en.js.cx/images-load/3.jpg"
];
// add random characters to prevent browser caching
for (let i = 0; i < sources.length; i++) {
sources[i] += '?' + Math.random();
}
// for each image,
// let's create another img with the same src and check that we have its width immediately
function testLoaded() {
let widthSum = 0;
for (let i = 0; i < sources.length; i++) {
let img = document.createElement('img');
img.src = sources[i];
widthSum += img.width;
}
alert(widthSum);
}
// should output 300
preloadImages(sources, testLoaded);
</script>
</body>
</html>

View file

@ -0,0 +1,43 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<script>
function preloadImages(sources, callback) {
/* your code */
}
// ---------- The test ----------
let sources = [
"https://en.js.cx/images-load/1.jpg",
"https://en.js.cx/images-load/2.jpg",
"https://en.js.cx/images-load/3.jpg"
];
// add random characters to prevent browser caching
for (let i = 0; i < sources.length; i++) {
sources[i] += '?' + Math.random();
}
// for each image,
// let's create another img with the same src and check that we have its width immediately
function testLoaded() {
let widthSum = 0;
for (let i = 0; i < sources.length; i++) {
let img = document.createElement('img');
img.src = sources[i];
widthSum += img.width;
}
alert(widthSum);
}
// every image is 100x100, the total width should be 300
preloadImages(sources, testLoaded);
</script>
</body>
</html>

View file

@ -0,0 +1,36 @@
importance: 4
---
# Load images with a callback
Normally, images are loaded when they are created. So when we add `<img>` to the page, the user does not see the picture immediately. The browser needs to load it first.
To show an image immediately, we can create it "in advance", like this:
```js
let img = document.createElement('img');
img.src = 'my.jpg';
```
The browser starts loading the image and remembers it in the cache. Later, when the same image appears in the document (no matter how), it shows up immediately.
**Create a function `preloadImages(sources, callback)` that loads all images from the array `sources` and, when ready, runs `callback`.**
For instance, this will show an `alert` after the images are loaded:
```js
function loaded() {
alert("Images loaded")
}
preloadImages(["1.jpg", "2.jpg", "3.jpg"], loaded);
```
In case of an error, the function should still assume the picture "loaded".
In other words, the `callback` is executed when all images are either loaded or errored out.
The function is useful, for instance, when we plan to show a gallery with many scrollable images, and want to be sure that all images are loaded.
In the source document you can find links to test images, and also the code to check whether they are loaded or not. It should output `300`.

View file

@ -0,0 +1,206 @@
# Resource loading: onload and onerror
The browser allows to track the loading of external resources -- scripts, iframes, pictures and so on.
There are two events for it:
- `onload` -- successful load,
- `onerror` -- an error occurred.
## Loading a script
Let's say we need to load a third-party script and call a function that resides there.
We can load it dynamically, like this:
```js
let script = document.createElement('script');
script.src = "my.js";
document.head.append(script);
```
...But how to run the function that is declared inside that script? We need to wait until the script loads, and only then we can call it.
```smart
For our own scripts we could use [Javascript modules](info:modules) here, but they are not widely adopted by third-party libraries.
```
### script.onload
The main helper is the `load` event. It triggers after the script was loaded and executed.
For instance:
```js run untrusted
let script = document.createElement('script');
// can load any script, from any domain
script.src = "https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.3.0/lodash.js"
document.head.append(script);
*!*
script.onload = function() {
// the script creates a helper function "_"
alert(_); // the function is available
};
*/!*
```
So in `onload` we can use script variables, run functions etc.
...And what if the loading failed? For instance, there's no such script (error 404) or the server or the server is down (unavailable).
### script.onerror
Errors that occur during the loading of the script can be tracked on `error` event.
For instance, let's request a script that doesn't exist:
```js run
let script = document.createElement('script');
script.src = "https://example.com/404.js"; // no such script
document.head.append(script);
*!*
script.onerror = function() {
alert("Error loading " + this.src); // Error loading https://example.com/404.js
};
*/!*
```
Please note that we can't get HTTP error details here. We don't know was it error 404 or 500 or something else. Just that the loading failed.
```warn
Events `onload`/`onerror` track only the loading itself.
Errors during script processing and execution are out of the scope of these events. To track script errors, one can use `window.onerror` global handler.
```
## Other resources
The `load` and `error` events also work for other resources, basically for any resource that has an external `src`.
For example:
```js run
let img = document.createElement('img');
img.src = "https://js.cx/clipart/train.gif"; // (*)
img.onload = function() {
alert(`Image loaded, size ${img.width}x${img.height}`);
};
img.onerror = function() {
alert("Error occured while loading image");
};
```
There are some notes though:
- Most resources start loading when they are added to the document. But `<img>` is an exception. It starts loading when it gets an src `(*)`.
- For `<iframe>`, the `iframe.onload` event triggers when the iframe loading finished, both for successful load and in case of an error.
That's for historical reasons.
## Crossorigin policy
There's a rule: scripts from one site can't access contents of the other site. So, e.g. a script at `https://facebook.com` can't read the user's mailbox at `https://gmail.com`.
Or, to be more precise, one origin (domain/port/protocol triplet) can't access the content from another one. So even if we have a subdomain, or just another port, these are different origins, no access to each other.
This rule also affects resources from other domains.
If we're using a script from another domain, and there's an error in it, we can't get error details.
For example, let's take a script with a single (bad) function call:
```js
// 📁 error.js
noSuchFunction();
```
Now load it from our domain:
```html run height=0
<script>
window.onerror = function(message, url, line, col, errorObj) {
alert(`${message}\n${url}, ${line}:${col}`);
};
</script>
<script src="/article/onload-onerror/crossorigin/error.js"></script>
```
We can see a good error report, like this:
```
Uncaught ReferenceError: noSuchFunction is not defined
https://javascript.info/article/onload-onerror/crossorigin/error.js, 1:1
```
Now let's load the same script from another domain:
```html run height=0
<script>
window.onerror = function(message, url, line, col, errorObj) {
alert(`${message}\n${url}, ${line}:${col}`);
};
</script>
<script src="https://cors.javascript.info/article/onload-onerror/crossorigin/error.js"></script>
```
The report is different, like this:
```
Script error.
, 0:0
```
Details may vary depeding on the browser, but the idea is same: any information about the internals of a script is hidden. Exactly because it's from another domain.
Why do we need the details?
There are many services (and we can build our own) that listen to `window.onerror`, save errors at the server and provide an interface to access and analyze them. That's great, as we can see real errors, triggered by our users. But we can't see any error information for scripts from other domains.
Similar cross-origin policy (CORS) is enforced for other types of resources as well.
**To allow cross-origin access, we need `crossorigin` attribute, plus the remote server must provide special headers.**
There are three levels of cross-origin access:
1. **No `crossorigin` attribute** -- access prohibited.
2. **`crossorigin="anonymous"`** -- access allowed if the server responds with the header `Access-Control-Allow-Origin` with `*` or our origin. Browser does not send authorization information and cookies to remote server.
3. **`crossorigin="use-credentials"`** -- access allowed if the server sends back the header `Access-Control-Allow-Origin` with our origin and `Access-Control-Allow-Credentials: true`. Browser sends authorization information and cookies to remote server.
```smart
You can read more about cross-origin access in the chapter <info:fetch-crossorigin>. It describes `fetch` method for network requests, but the policy is exactly the same.
Such thing as "cookies" is out of our current scope, but you can read about them in the chapter <info:cookie>.
```
In our case, we didn't have any crossorigin attribute. So the cross-origin access was prohibited. Let's add it.
We can choose between `"anonymous"` (no cookies sent, one server-side header needed) and `"use-credentials"` (sends cookies too, two server-side headers needed).
If we don't care about cookies, then `"anonymous"` is a way to go:
```html run height=0
<script>
window.onerror = function(message, url, line, col, errorObj) {
alert(`${message}\n${url}, ${line}:${col}`);
};
</script>
<script *!*crossorigin="anonymous"*/!* src="https://cors.javascript.info/article/onload-onerror/crossorigin/error.js"></script>
```
Now, assuming that the server provides `Access-Control-Allow-Origin` header, everything's fine. We have the full error report.
## Summary
Images `<img>`, external styles, scripts and other resources provide `load` and `error` events to track their loading:
- `load` triggers on a successful load,
- `error` triggers on a failed load.
The only exception is `<iframe>`: for historical reasons it always triggers `load`, for any load completion, even if the page is not found.
The `readystatechange` event also works for resources, but is rarely used, because `load/error` events are simpler.

View file

@ -0,0 +1 @@
noSuchFunction();

2
2-ui/5-loading/index.md Normal file
View file

@ -0,0 +1,2 @@
# Document and resource loading