fixes
This commit is contained in:
parent
b300836f00
commit
b1b66a3065
9 changed files with 61 additions and 85 deletions
|
@ -11,12 +11,12 @@ window.open('https://javascript.info/')
|
||||||
|
|
||||||
Popups exist from really ancient times. The initial idea was to show another content without closing the main window. As of now, there are other ways to do that: we can load content dynamically with [fetch](info:fetch) and show it in a dynamically generated `<div>`. So, popups is not something we use everyday.
|
Popups exist from really ancient times. The initial idea was to show another content without closing the main window. As of now, there are other ways to do that: we can load content dynamically with [fetch](info:fetch) and show it in a dynamically generated `<div>`. So, popups is not something we use everyday.
|
||||||
|
|
||||||
Also, popups are tricky on mobile devices.
|
Also, popups are tricky on mobile devices, that don't show multiple windows simultaneously.
|
||||||
|
|
||||||
Still, there are situations when a popup works good, e.g. for OAuth authorization (login with Google/Facebook/...), because:
|
Still, there are tasks where popups are still used, e.g. for OAuth authorization (login with Google/Facebook/...), because:
|
||||||
|
|
||||||
1. A popup is a separate window with its own independent JavaScript environment. So opening a popup with a third-party non-trusted site is safe.
|
1. A popup is a separate window with its own independent JavaScript environment. So opening a popup with a third-party non-trusted site is safe.
|
||||||
2. It's very easy to open a popup, little to no overhead.
|
2. It's very easy to open a popup.
|
||||||
3. A popup can navigate (change URL) and send messages to the opener window.
|
3. A popup can navigate (change URL) and send messages to the opener window.
|
||||||
|
|
||||||
## Popup blocking
|
## Popup blocking
|
||||||
|
@ -149,7 +149,7 @@ newWindow.onload = function() {
|
||||||
Please note: immediately after `window.open`, the new window isn't loaded yet. That's demonstrated by `alert` in line `(*)`. So we wait for `onload` to modify it. We could also use `DOMContentLoaded` handler for `newWin.document`.
|
Please note: immediately after `window.open`, the new window isn't loaded yet. That's demonstrated by `alert` in line `(*)`. So we wait for `onload` to modify it. We could also use `DOMContentLoaded` handler for `newWin.document`.
|
||||||
|
|
||||||
```warn header="Same origin policy"
|
```warn header="Same origin policy"
|
||||||
Windows may only freely modify each other if they come from the same origin (the same protocol://domain:port).
|
Windows may freely access content of each other only if they come from the same origin (the same protocol://domain:port).
|
||||||
|
|
||||||
Otherwise, e.g. if the main window is from `site.com`, and the popup from `gmail.com`, that's impossible for user safety reasons. For the details, see chapter <info:cross-window-communication>.
|
Otherwise, e.g. if the main window is from `site.com`, and the popup from `gmail.com`, that's impossible for user safety reasons. For the details, see chapter <info:cross-window-communication>.
|
||||||
```
|
```
|
||||||
|
@ -158,7 +158,7 @@ Otherwise, e.g. if the main window is from `site.com`, and the popup from `gmail
|
||||||
|
|
||||||
A popup may access the "opener" window as well using `window.opener` reference. It is `null` for all windows except popups.
|
A popup may access the "opener" window as well using `window.opener` reference. It is `null` for all windows except popups.
|
||||||
|
|
||||||
If you run the code below, it replaces the opener window content with "Test":
|
If you run the code below, it replaces the opener (current) window content with "Test":
|
||||||
|
|
||||||
```js run
|
```js run
|
||||||
let newWin = window.open("about:blank", "hello", "width=200,height=200");
|
let newWin = window.open("about:blank", "hello", "width=200,height=200");
|
||||||
|
@ -172,12 +172,13 @@ So the connection between the windows is bidirectional: the main window and the
|
||||||
|
|
||||||
## Closing a popup
|
## Closing a popup
|
||||||
|
|
||||||
- To close a window: `win.close()`.
|
To close a window: `win.close()`.
|
||||||
- To check if a window is closed: `win.close` property.
|
|
||||||
|
To check if a window is closed: `win.closed`.
|
||||||
|
|
||||||
Technically, the `close()` method is available for any `window`, but `window.close()` is ignored by most browsers if `window` is not created with `window.open()`. So it'll only work on a popup.
|
Technically, the `close()` method is available for any `window`, but `window.close()` is ignored by most browsers if `window` is not created with `window.open()`. So it'll only work on a popup.
|
||||||
|
|
||||||
The `win.closed` property is `true` if the window is closed. That's useful to check if the popup (or the main window) is still open or not. A user can close it anytime, and our code should take that possibility into account.
|
The `closed` property is `true` if the window is closed. That's useful to check if the popup (or the main window) is still open or not. A user can close it anytime, and our code should take that possibility into account.
|
||||||
|
|
||||||
This code loads and then closes the window:
|
This code loads and then closes the window:
|
||||||
|
|
||||||
|
@ -259,23 +260,18 @@ For instance:
|
||||||
|
|
||||||
## Summary
|
## Summary
|
||||||
|
|
||||||
Всплывающие окна используются нечасто. Ведь загрузить новую информацию можно динамически, с помощью технологии AJAX, а показать -- в элементе `<div>`, расположенным над страницей (`z-index`). Ещё одна альтернатива -- тег `<iframe>`.
|
Popup windows are used rarely, as there are alternatives: loading and displaying information in-page, or in iframe.
|
||||||
|
|
||||||
Но в некоторых случаях всплывающие окна бывают очень даже полезны. Например, отдельное окно сервиса онлайн-консультаций. Посетитель может ходить по сайту в основном окне, а общаться в чате -- во вспомогательном.
|
If we're going to open a popup, a good practice is to inform the user about it. An "opening window" icon near a link or button would allow the visitor to survive the focus shift and keep both windows in mind.
|
||||||
|
|
||||||
Если вы хотите использовать всплывающее окно, предупредите посетителя об этом, так же и при использовании `target="_blank"` в ссылках или формах. Иконка открывающегося окошка на ссылке поможет посетителю понять, что происходит и не потерять оба окна из поля зрения.
|
|
||||||
|
|
||||||
- A popup can be opened by the `open(url, name, params)` call. It returns the reference to the newly opened window.
|
- A popup can be opened by the `open(url, name, params)` call. It returns the reference to the newly opened window.
|
||||||
- Browsers block `open` calls from the code outside of user actions. Usually a notification appears, so that a user may allow them.
|
- Browsers block `open` calls from the code outside of user actions. Usually a notification appears, so that a user may allow them.
|
||||||
- Browsers open a new tab by default, but if sizes are provided, then it'll be a popup window.
|
- Browsers open a new tab by default, but if sizes are provided, then it'll be a popup window.
|
||||||
- The popup may access the opener window using the `window.opener` property.
|
- The popup may access the opener window using the `window.opener` property.
|
||||||
- The main window and the popup can freely read and modify each other if they havee the same origin. Otherwise, they can change location of each other and [exchange messages](cross-window-communication).
|
- The main window and the popup can freely read and modify each other if they havee the same origin. Otherwise, they can change location of each other and [exchange messages.
|
||||||
|
|
||||||
Methods and properties:
|
To close the popup: use `close()` call. Also the user may close them (just like any other windows). The `window.closed` is `true` after that.
|
||||||
|
|
||||||
- To close the popup: use `close()` call. Also the user may close them (just like any other windows). The `window.closed` is `true` after that.
|
- Methods `focus()` and `blur()` allow to focus/unfocus a window. But they don't work all the time.
|
||||||
- Methods `focus()` and `blur()` allow to focus/unfocus a window. Sometimes.
|
|
||||||
- Events `focus` and `blur` allow to track switching in and out of the window. But please note that a window may still be visible even in the background state, after `blur`.
|
- Events `focus` and `blur` allow to track switching in and out of the window. But please note that a window may still be visible even in the background state, after `blur`.
|
||||||
- ...And a few scrolling and resizing methods.
|
|
||||||
|
|
||||||
If we're going to open a popup, a good practice is to inform the user about it. If there's a link that opens a popup, we could place an icon near it, so that visitor can survive the focus shift and keep both windows in mind.
|
|
||||||
|
|
|
@ -28,12 +28,12 @@ The "Same Origin" policy states that:
|
||||||
|
|
||||||
### In action: iframe
|
### In action: iframe
|
||||||
|
|
||||||
An `<iframe>` tag hosts embbedded window, with its own separate `document` and `window` objects.
|
An `<iframe>` tag hosts a separate embedded window, with its own separate `document` and `window` objects.
|
||||||
|
|
||||||
We can access them using properties:
|
We can access them using properties:
|
||||||
|
|
||||||
- `iframe.contentWindow` to get the window inside the `<iframe>`.
|
- `iframe.contentWindow` to get the window inside the `<iframe>`.
|
||||||
- `iframe.contentDocument` to get the document inside the `<iframe>`.
|
- `iframe.contentDocument` to get the document inside the `<iframe>`, короткий аналог `iframe.contentWindoe.document`.
|
||||||
|
|
||||||
When we access something inside the embedded window, the browser checks if the iframe has the same origin. If that's not so then the access is denied (writing to `location` is an exception, it's still permitted).
|
When we access something inside the embedded window, the browser checks if the iframe has the same origin. If that's not so then the access is denied (writing to `location` is an exception, it's still permitted).
|
||||||
|
|
||||||
|
@ -102,13 +102,13 @@ The `iframe.onload` event (on the `<iframe>` tag) is essentially the same as `if
|
||||||
...But we can't access `iframe.contentWindow.onload` for an iframe from another origin, so using `iframe.onload`.
|
...But we can't access `iframe.contentWindow.onload` for an iframe from another origin, so using `iframe.onload`.
|
||||||
```
|
```
|
||||||
|
|
||||||
## Iframes on subdomains: document.domain
|
## Windows on subdomains: document.domain
|
||||||
|
|
||||||
By definition, two URLs with different domains have different origins.
|
By definition, two URLs with different domains have different origins.
|
||||||
|
|
||||||
But if windows share the same second-level domain, for instance `john.site.com`, `peter.site.com` and `site.com` (so that their common second-level domain is `site.com`), we can make the browser ignore that difference, so that they can be treated as coming from the "same origin" for the purposes of cross-window communication.
|
But if windows share the same second-level domain, for instance `john.site.com`, `peter.site.com` and `site.com` (so that their common second-level domain is `site.com`), we can make the browser ignore that difference, so that they can be treated as coming from the "same origin" for the purposes of cross-window communication.
|
||||||
|
|
||||||
To make it work, each window (including the one from `site.com`) should run the code:
|
To make it work, each such window should run the code:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
document.domain = 'site.com';
|
document.domain = 'site.com';
|
||||||
|
@ -144,22 +144,16 @@ Here, look:
|
||||||
|
|
||||||
We shouldn't work with the document of a not-yet-loaded iframe, because that's the *wrong document*. If we set any event handlers on it, they will be ignored.
|
We shouldn't work with the document of a not-yet-loaded iframe, because that's the *wrong document*. If we set any event handlers on it, they will be ignored.
|
||||||
|
|
||||||
...The right document is definitely there when `iframe.onload` triggers. But it only triggers when the whole iframe with all resources is loaded.
|
How to detect the moment when the document is there?
|
||||||
|
|
||||||
There's also `DOMContentLoaded` event, that triggers sooner than `onload`. As we assume that the iframe comes from the same origin, we can setup the event handler. But we should set it on the right document, so we need to detect when it's there.
|
The right document is definitely at place when `iframe.onload` triggers. But it only triggers when the whole iframe with all resources is loaded.
|
||||||
|
|
||||||
Here's a small recipe for this.
|
We can try to catch the moment earlier using checks in `setInterval`:
|
||||||
|
|
||||||
We can try to catch the moment when a new document appears using checks in `setInterval`, and then setup necessary handlers:
|
|
||||||
|
|
||||||
```html run
|
```html run
|
||||||
<iframe src="/" id="iframe"></iframe>
|
<iframe src="/" id="iframe"></iframe>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
function onDocumentLoaded() {
|
|
||||||
iframe.contentDocument.body.prepend('Hello, world!');
|
|
||||||
}
|
|
||||||
|
|
||||||
let oldDoc = iframe.contentDocument;
|
let oldDoc = iframe.contentDocument;
|
||||||
|
|
||||||
// every 100 ms check if the document is the new one
|
// every 100 ms check if the document is the new one
|
||||||
|
@ -167,14 +161,7 @@ We can try to catch the moment when a new document appears using checks in `setI
|
||||||
let newDoc = iframe.contentDocument;
|
let newDoc = iframe.contentDocument;
|
||||||
if (newDoc == oldDoc) return;
|
if (newDoc == oldDoc) return;
|
||||||
|
|
||||||
// new document
|
alert("New document is here!");
|
||||||
if (newDoc.readyState == 'loading') {
|
|
||||||
// loading yet, wait for the event
|
|
||||||
newDoc.addEventListener('DOMContentLoaded', onDocumentLoaded);
|
|
||||||
} else {
|
|
||||||
// DOM is ready!
|
|
||||||
onDocumentLoaded();
|
|
||||||
}
|
|
||||||
|
|
||||||
clearInterval(timer); // cancel setInterval, don't need it any more
|
clearInterval(timer); // cancel setInterval, don't need it any more
|
||||||
}, 100);
|
}, 100);
|
||||||
|
@ -227,11 +214,11 @@ if (window == top) { // current window == window.top?
|
||||||
|
|
||||||
The `sandbox` attribute allows for the exclusion of certain actions inside an `<iframe>` in order to prevent it executing untrusted code. It "sandboxes" the iframe by treating it as coming from another origin and/or applying other limitations.
|
The `sandbox` attribute allows for the exclusion of certain actions inside an `<iframe>` in order to prevent it executing untrusted code. It "sandboxes" the iframe by treating it as coming from another origin and/or applying other limitations.
|
||||||
|
|
||||||
There's a "default set" of restrictions applied for `<iframe sandbox src="...">`. But it can be relaxed if we provide a space-separated list of keywords for restrictions that should not be applied as a value of the attribute, like this: `<iframe sandbox="allow-forms allow-popups">`.
|
There's a "default set" of restrictions applied for `<iframe sandbox src="...">`. But it can be relaxed if we provide a space-separated list of restrictions that should not be applied as a value of the attribute, like this: `<iframe sandbox="allow-forms allow-popups">`.
|
||||||
|
|
||||||
In other words, an empty `"sandbox"` attribute puts the strictest limitations possible, but we can put a space-delimited list of those that we want to lift.
|
In other words, an empty `"sandbox"` attribute puts the strictest limitations possible, but we can put a space-delimited list of those that we want to lift.
|
||||||
|
|
||||||
Here's a list of limitations. By default, all are applied. We can disable each by specifying the corresponding keyword in the `sandbox` attribute:
|
Here's a list of limitations:
|
||||||
|
|
||||||
`allow-same-origin`
|
`allow-same-origin`
|
||||||
: By default `"sandbox"` forces the "different origin" policy for the iframe. In other words, it makes the browser to treat the `iframe` as coming from another origin, even if its `src` points to the same site. With all implied restrictions for scripts. This option removes that feature.
|
: By default `"sandbox"` forces the "different origin" policy for the iframe. In other words, it makes the browser to treat the `iframe` as coming from another origin, even if its `src` points to the same site. With all implied restrictions for scripts. This option removes that feature.
|
||||||
|
@ -375,7 +362,7 @@ Exceptions are:
|
||||||
- Windows that share the same second-level domain: `a.site.com` and `b.site.com`. Then setting `document.domain='site.com'` in both of them puts them into the "same origin" state.
|
- Windows that share the same second-level domain: `a.site.com` and `b.site.com`. Then setting `document.domain='site.com'` in both of them puts them into the "same origin" state.
|
||||||
- If an iframe has a `sandbox` attribute, it is forcefully put into the "different origin" state, unless the `allow-same-origin` is specified in the attribute value. That can be used to run untrusted code in iframes from the same site.
|
- If an iframe has a `sandbox` attribute, it is forcefully put into the "different origin" state, unless the `allow-same-origin` is specified in the attribute value. That can be used to run untrusted code in iframes from the same site.
|
||||||
|
|
||||||
The `postMessage` interface allows two windows to talk with security checks:
|
The `postMessage` interface allows two windows with any origins to talk:
|
||||||
|
|
||||||
1. The sender calls `targetWin.postMessage(data, targetOrigin)`.
|
1. The sender calls `targetWin.postMessage(data, targetOrigin)`.
|
||||||
2. If `targetOrigin` is not `'*'`, then the browser checks if window `targetWin` has the origin `targetOrigin`.
|
2. If `targetOrigin` is not `'*'`, then the browser checks if window `targetWin` has the origin `targetOrigin`.
|
||||||
|
|
|
@ -10,14 +10,14 @@ First, there's a constructor, similar to `Blob`:
|
||||||
new File(fileParts, fileName, [options])
|
new File(fileParts, fileName, [options])
|
||||||
```
|
```
|
||||||
|
|
||||||
- **`fileParts`** -- is an array of Blob/BufferSource/String value, same as `Blob`.
|
- **`fileParts`** -- is an array of Blob/BufferSource/String values.
|
||||||
- **`fileName`** -- file name string.
|
- **`fileName`** -- file name string.
|
||||||
- **`options`** -- optional object:
|
- **`options`** -- optional object:
|
||||||
- **`lastModified`** -- the timestamp (integer date) of last modification.
|
- **`lastModified`** -- the timestamp (integer date) of last modification.
|
||||||
|
|
||||||
Second, more often we get a file from `<input type="file">` or drag'n'drop or other browser interfaces. Then the file gets these from OS.
|
Second, more often we get a file from `<input type="file">` or drag'n'drop or other browser interfaces. In that case, the file gets this information from OS.
|
||||||
|
|
||||||
As `File` inherits from `Blob`, it has same properties, plus:
|
As `File` inherits from `Blob`, `File` objects have the same properties, plus:
|
||||||
- `name` -- the file name,
|
- `name` -- the file name,
|
||||||
- `lastModified` -- the timestamp of last modification.
|
- `lastModified` -- the timestamp of last modification.
|
||||||
|
|
||||||
|
@ -61,7 +61,7 @@ The main methods:
|
||||||
|
|
||||||
The choice of `read*` method depends on which format we prefer, how we're going to use the data.
|
The choice of `read*` method depends on which format we prefer, how we're going to use the data.
|
||||||
|
|
||||||
- `readAsArrayBuffer` - for binary files, to do low-level binary operations. For high-level operations, like slicing, `File` inherits from `Blob`, so we can calll them directly, without reading.
|
- `readAsArrayBuffer` - for binary files, to do low-level binary operations. For high-level operations, like slicing, `File` inherits from `Blob`, so we can call them directly, without reading.
|
||||||
- `readAsText` - for text files, when we'd like to get a string.
|
- `readAsText` - for text files, when we'd like to get a string.
|
||||||
- `readAsDataURL` -- when we'd like to use this data in `src` for `img` or another tag. There's an alternative to reading a file for that, as discussed in chapter <info:blob>: `URL.createObjectURL(file)`.
|
- `readAsDataURL` -- when we'd like to use this data in `src` for `img` or another tag. There's an alternative to reading a file for that, as discussed in chapter <info:blob>: `URL.createObjectURL(file)`.
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,7 @@ For example, we can:
|
||||||
|
|
||||||
...And all of that without reloading the page!
|
...And all of that without reloading the page!
|
||||||
|
|
||||||
There's an umbrella term "AJAX" (abbreviated <b>A</b>synchronous <b>J</b>avascript <b>A</b>nd <b>X</b>ml) for that. We don't have to use XML though: the term comes from old times, that's why it's here.
|
There's an umbrella term "AJAX" (abbreviated <b>A</b>synchronous <b>J</b>avascript <b>A</b>nd <b>X</b>ml) for that. We don't have to use XML though: the term comes from old times, that's that word is there.
|
||||||
|
|
||||||
There are multiple ways to send a network request and get information from the server.
|
There are multiple ways to send a network request and get information from the server.
|
||||||
|
|
||||||
|
@ -33,7 +33,6 @@ Getting a response is usually a two-stage process.
|
||||||
|
|
||||||
**First, the `promise` resolves with an object of the built-in [Response](https://fetch.spec.whatwg.org/#response-class) class as soon as the server responds with headers.**
|
**First, the `promise` resolves with an object of the built-in [Response](https://fetch.spec.whatwg.org/#response-class) class as soon as the server responds with headers.**
|
||||||
|
|
||||||
|
|
||||||
So we can check HTTP status, to see whether it is successful or not, check headers, but don't have the body yet.
|
So we can check HTTP status, to see whether it is successful or not, check headers, but don't have the body yet.
|
||||||
|
|
||||||
The promise rejects if the `fetch` was unable to make HTTP-request, e.g. network problems, or there's no such site. HTTP-errors, even such as 404 or 500, are considered a normal flow.
|
The promise rejects if the `fetch` was unable to make HTTP-request, e.g. network problems, or there's no such site. HTTP-errors, even such as 404 or 500, are considered a normal flow.
|
||||||
|
@ -67,7 +66,7 @@ if (response.ok) { // if HTTP-status is 200-299
|
||||||
- **`response.arrayBuffer()`** -- return the response as [ArrayBuffer](info:arraybuffer-binary-arrays) (pure binary data),
|
- **`response.arrayBuffer()`** -- return the response as [ArrayBuffer](info:arraybuffer-binary-arrays) (pure binary data),
|
||||||
- additionally, `response.body` is a [ReadableStream](https://streams.spec.whatwg.org/#rs-class) object, it allows to read the body chunk-by-chunk, we'll see an example later.
|
- additionally, `response.body` is a [ReadableStream](https://streams.spec.whatwg.org/#rs-class) object, it allows to read the body chunk-by-chunk, we'll see an example later.
|
||||||
|
|
||||||
For instance, here we get a JSON-object with latest commits from GitHub:
|
For instance, let's get a JSON-object with latest commits from GitHub:
|
||||||
|
|
||||||
```js run async
|
```js run async
|
||||||
let response = await fetch('https://api.github.com/repos/javascript-tutorial/en.javascript.info/commits');
|
let response = await fetch('https://api.github.com/repos/javascript-tutorial/en.javascript.info/commits');
|
||||||
|
@ -79,7 +78,7 @@ let commits = await response.json(); // read response body and parse as JSON
|
||||||
alert(commits[0].author.login);
|
alert(commits[0].author.login);
|
||||||
```
|
```
|
||||||
|
|
||||||
Or, the same using pure promises syntax:
|
Or, the same without `await`, using pure promises syntax:
|
||||||
|
|
||||||
```js run
|
```js run
|
||||||
fetch('https://api.github.com/repos/javascript-tutorial/en.javascript.info/commits')
|
fetch('https://api.github.com/repos/javascript-tutorial/en.javascript.info/commits')
|
||||||
|
@ -191,9 +190,7 @@ To make a `POST` request, or a request with another method, we need to use `fetc
|
||||||
- a string (e.g. JSON),
|
- a string (e.g. JSON),
|
||||||
- `FormData` object, to submit the data as `form/multipart`,
|
- `FormData` object, to submit the data as `form/multipart`,
|
||||||
- `Blob`/`BufferSource` to send binary data,
|
- `Blob`/`BufferSource` to send binary data,
|
||||||
- [URLSearchParams](info:url), to submit the data as `x-www-form-urlencoded`, rarely used.
|
- [URLSearchParams](info:url), to submit the data in `x-www-form-urlencoded` encoding, rarely used.
|
||||||
|
|
||||||
Let's see examples.
|
|
||||||
|
|
||||||
For example, this code submits `user` object as JSON:
|
For example, this code submits `user` object as JSON:
|
||||||
|
|
||||||
|
@ -271,7 +268,7 @@ function submit() {
|
||||||
|
|
||||||
## Summary
|
## Summary
|
||||||
|
|
||||||
A typical fetch request consists of two `awaits`:
|
A typical fetch request consists of two `await` calls:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
let response = await fetch(url, options); // resolves with response headers
|
let response = await fetch(url, options); // resolves with response headers
|
||||||
|
@ -302,4 +299,4 @@ Fetch options so far:
|
||||||
- `headers` -- an object with request headers (not any header is allowed),
|
- `headers` -- an object with request headers (not any header is allowed),
|
||||||
- `body` -- `string`, `FormData`, `BufferSource`, `Blob` or `UrlSearchParams` object to send.
|
- `body` -- `string`, `FormData`, `BufferSource`, `Blob` or `UrlSearchParams` object to send.
|
||||||
|
|
||||||
In the next chapters we'll see more options and use cases.
|
In the next chapters we'll see more options and use cases of `fetch`.
|
||||||
|
|
|
@ -7,7 +7,7 @@ Please note: there's currently no way for `fetch` to track *upload* progress. Fo
|
||||||
|
|
||||||
To track download progress, we can use `response.body` property. It's a "readable stream" -- a special object that provides body chunk-by-chunk, as it comes.
|
To track download progress, we can use `response.body` property. It's a "readable stream" -- a special object that provides body chunk-by-chunk, as it comes.
|
||||||
|
|
||||||
Unlike `response.text()`, `response.json()` and other methods, `response.body` gives full control over the reading process, and we can see how much is consumed at the moment.
|
Unlike `response.text()`, `response.json()` and other methods, `response.body` gives full control over the reading process, and we can count how much is consumed at any moment.
|
||||||
|
|
||||||
Here's the sketch of code that reads the reponse from `response.body`:
|
Here's the sketch of code that reads the reponse from `response.body`:
|
||||||
|
|
||||||
|
@ -29,13 +29,13 @@ while(true) {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
So, we read response chunks in the loop, while `await reader.read()` returns them. When it's done, no more data, so we're done.
|
|
||||||
|
|
||||||
The result of `await reader.read()` call is an object with two properties:
|
The result of `await reader.read()` call is an object with two properties:
|
||||||
- **`done`** -- true when the reading is complete.
|
- **`done`** -- true when the reading is complete.
|
||||||
- **`value`** -- a typed array of bytes: `Uint8Array`.
|
- **`value`** -- a typed array of bytes: `Uint8Array`.
|
||||||
|
|
||||||
To log progress, we just need for every `value` add its length to the counter.
|
We wait for more chunks in the loop, until `done` is `true`.
|
||||||
|
|
||||||
|
To log the progress, we just need for every `value` add its length to the counter.
|
||||||
|
|
||||||
Here's the full code to get response and log the progress, more explanations follow:
|
Here's the full code to get response and log the progress, more explanations follow:
|
||||||
|
|
||||||
|
|
|
@ -48,12 +48,11 @@ One way to communicate with another server was to submit a `<form>` there. Peopl
|
||||||
*/!*
|
*/!*
|
||||||
...
|
...
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
So, it was possible to make a GET/POST request to another site, even without networking methods. But as it's forbidden to access the content of an `<iframe>` from another site, it wasn't possible to read the response.
|
So, it was possible to make a GET/POST request to another site, even without networking methods. But as it's forbidden to access the content of an `<iframe>` from another site, it wasn't possible to read the response.
|
||||||
|
|
||||||
...Okay, in fact there actually were tricks for that (required special scripts at both remote and our page), but let's not delve deeper. Nothing good in those for us now.
|
As we can see, forms allowed to send data anywhere, but not receive the response. To be precise, there wre actually tricks for that (required special scripts at both the iframe and the page), but let these dinosaurs rest in peace.
|
||||||
|
|
||||||
### Using scripts
|
### Using scripts
|
||||||
|
|
||||||
|
|
|
@ -73,7 +73,7 @@ We can provide them in the URL string:
|
||||||
new URL('https://google.com/search?query=JavaScript')
|
new URL('https://google.com/search?query=JavaScript')
|
||||||
```
|
```
|
||||||
|
|
||||||
...But that's not good due to encoding issues. Parameters need to be encoded if they contain spaces, non-latin letterrs, etc (more about that below).
|
...But parameters need to be encoded if they contain spaces, non-latin letters, etc (more about that below).
|
||||||
|
|
||||||
So there's URL property for that: `url.searchParams`, an object of type [URLSearchParams](https://url.spec.whatwg.org/#urlsearchparams).
|
So there's URL property for that: `url.searchParams`, an object of type [URLSearchParams](https://url.spec.whatwg.org/#urlsearchparams).
|
||||||
|
|
||||||
|
@ -96,7 +96,7 @@ url.searchParams.set('q', 'test me!'); // added parameter with a space and !
|
||||||
|
|
||||||
alert(url); // https://google.com/search?query=test+me%21
|
alert(url); // https://google.com/search?query=test+me%21
|
||||||
|
|
||||||
url.searchParams.set('tbs', 'qdr:y'); // add param for date range: past year
|
url.searchParams.set('tbs', 'qdr:y'); // this parameter specifies for date range for Google Search
|
||||||
|
|
||||||
alert(url); // https://google.com/search?q=test+me%21&tbs=qdr%3Ay
|
alert(url); // https://google.com/search?q=test+me%21&tbs=qdr%3Ay
|
||||||
|
|
||||||
|
|
|
@ -220,11 +220,7 @@ We can terminate the request at any time. The call to `xhr.abort()` does that:
|
||||||
xhr.abort(); // terminate the request
|
xhr.abort(); // terminate the request
|
||||||
```
|
```
|
||||||
|
|
||||||
That triggers `abort` event.
|
That triggers `abort` event, and `xhr.status` becomes `0`.
|
||||||
|
|
||||||
That
|
|
||||||
Also, `x and `xhr.status` become `0` in that case.
|
|
||||||
|
|
||||||
|
|
||||||
## Synchronous requests
|
## Synchronous requests
|
||||||
|
|
||||||
|
@ -248,7 +244,7 @@ try {
|
||||||
}
|
}
|
||||||
} catch(err) { // instead of onerror
|
} catch(err) { // instead of onerror
|
||||||
alert("Request failed");
|
alert("Request failed");
|
||||||
};
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
It might look good, but synchronous calls are used rarely, because they block in-page JavaScript till the loading is complete. In some browsers it becomes impossible to scroll. If a synchronous call takes too much time, the browser may suggest to close the "hanging" webpage.
|
It might look good, but synchronous calls are used rarely, because they block in-page JavaScript till the loading is complete. In some browsers it becomes impossible to scroll. If a synchronous call takes too much time, the browser may suggest to close the "hanging" webpage.
|
||||||
|
@ -489,7 +485,7 @@ let xhr = new XMLHttpRequest();
|
||||||
|
|
||||||
xhr.open('GET', '/my/url');
|
xhr.open('GET', '/my/url');
|
||||||
|
|
||||||
xhr.send(); s
|
xhr.send();
|
||||||
|
|
||||||
xhr.onload = function() {
|
xhr.onload = function() {
|
||||||
if (xhr.status != 200) { // HTTP error?
|
if (xhr.status != 200) { // HTTP error?
|
||||||
|
@ -521,9 +517,9 @@ There are actually more events, the [modern specification](http://www.w3.org/TR/
|
||||||
- `timeout` -- the request was canceled due to timeout (only happens if it was set).
|
- `timeout` -- the request was canceled due to timeout (only happens if it was set).
|
||||||
- `loadend` -- triggers after `load`, `error`, `timeout` or `abort`.
|
- `loadend` -- triggers after `load`, `error`, `timeout` or `abort`.
|
||||||
|
|
||||||
The `error`, `abort`, `timeout`, and `load` events are mutually exclusive.
|
The `error`, `abort`, `timeout`, and `load` events are mutually exclusive. Only one of them may happen.
|
||||||
|
|
||||||
The most used events are load completion (`load`), load failure (`error`), or we can use a single `loadend` handler and check event and response to see what happened.
|
The most used events are load completion (`load`), load failure (`error`), or we can use a single `loadend` handler and check the response to see what happened.
|
||||||
|
|
||||||
We've already seen another event: `readystatechange`. Historically, it appeared long ago, before the specification settled. Nowadays, there's no need to use it, we can replace it with newer events, but it can often be found in older scripts.
|
We've already seen another event: `readystatechange`. Historically, it appeared long ago, before the specification settled. Nowadays, there's no need to use it, we can replace it with newer events, but it can often be found in older scripts.
|
||||||
|
|
||||||
|
|
|
@ -96,14 +96,15 @@ let deleteRequest = indexedDB.deleteDatabase(name)
|
||||||
// deleteRequest.onsuccess/onerror tracks the result
|
// deleteRequest.onsuccess/onerror tracks the result
|
||||||
```
|
```
|
||||||
|
|
||||||
### Opening an old version
|
```warn header="Can we open an old version?"
|
||||||
|
Now what if we try to open a database with a lower version than the current one? E.g. the existing DB version is 3, and we try to `open(...2)`.
|
||||||
|
|
||||||
Now what if we try to open a database with a lower version than the current one?
|
That's an error, `openRequest.onerror` triggers.
|
||||||
E.g. the existing DB version is 3, and we try to `open(...2)`. That's simple: `openRequest.onerror` triggers.
|
|
||||||
|
|
||||||
Such thing may happen if the visitor loaded an outdated code, e.g. from a proxy cache. We should check `db.version`, suggest him to reload the page, and also make sure that our caching policy is correct.
|
Such thing may happen if the visitor loaded an outdated code, e.g. from a proxy cache. We should check `db.version`, suggest him to reload the page. And also re-check our caching headers to ensure that the visitor never gets old code.
|
||||||
|
```
|
||||||
|
|
||||||
### Multi-page update problem
|
### Parallel update problem
|
||||||
|
|
||||||
As we're talking about versioning, let's tackle a small related problem.
|
As we're talking about versioning, let's tackle a small related problem.
|
||||||
|
|
||||||
|
@ -113,11 +114,11 @@ Then we rolled out an update, and the same visitor opens our site in another tab
|
||||||
|
|
||||||
The problem is that a database is shared between two tabs, as that's the same site, same origin. And it can't be both version 1 and 2. To perform the update to version 2, all connections to version 1 must be closed.
|
The problem is that a database is shared between two tabs, as that's the same site, same origin. And it can't be both version 1 and 2. To perform the update to version 2, all connections to version 1 must be closed.
|
||||||
|
|
||||||
In order to organize that, there's `versionchange` event on an open database object. We should listen to it, as it lets us know that the version is about to change, so that we should close the database (and probably suggest the visitor to reload the page, to load the updated code).
|
In order to organize that, the `versionchange` event triggers an open database object when a parallel upgrade is attempted. We should listen to it, so that we should close the database (and probably suggest the visitor to reload the page, to load the updated code).
|
||||||
|
|
||||||
If we don't close it, then the second connection will be blocked with `blocked` event instead of `success`.
|
If we don't close it, then the second, new connection will be blocked with `blocked` event instead of `success`.
|
||||||
|
|
||||||
Here's the code to work around it, it has two minor additions:
|
Here's the code to do that:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
let openRequest = indexedDB.open("store", 2);
|
let openRequest = indexedDB.open("store", 2);
|
||||||
|
@ -131,7 +132,7 @@ openRequest.onsuccess = function() {
|
||||||
*!*
|
*!*
|
||||||
db.onversionchange = function() {
|
db.onversionchange = function() {
|
||||||
db.close();
|
db.close();
|
||||||
alert("Your database is outdated, please reload the page.")
|
alert("Database is outdated, please reload the page.")
|
||||||
};
|
};
|
||||||
*/!*
|
*/!*
|
||||||
|
|
||||||
|
@ -146,14 +147,14 @@ openRequest.onblocked = function() {
|
||||||
*/!*
|
*/!*
|
||||||
```
|
```
|
||||||
|
|
||||||
We do two things:
|
Here we do two things:
|
||||||
|
|
||||||
1. Add `db.onversionchange` listener after a successful opening, to close the old database.
|
1. Add `db.onversionchange` listener after a successful opening, to be informed about a parallel update attempt.
|
||||||
2. Add `openRequest.onblocked` listener to handle the case when an old connection wasn't closed. This doesn't happen if we close it in `db.onversionchange`.
|
2. Add `openRequest.onblocked` listener to handle the case when an old connection wasn't closed. This doesn't happen if we close it in `db.onversionchange`.
|
||||||
|
|
||||||
Alternatively, we can take time to close things gracefully in `db.onversionchange`, prompt the visitor to do something. The new connection will be blocked immediatelly after `db.onversionchange` finished without closing, but we can try to reopen it later.
|
There are other variants. For example, we can take time to close things gracefully in `db.onversionchange`, prompt the visitor to save the data before the connection is closed. The new updating connection will be blocked immediatelly after `db.onversionchange` finished without closing, and we can ask the visitor in the new tab to close other tabs for the update.
|
||||||
|
|
||||||
That's up to us, how we handle such version collision, it happens rarely, but we should at least have some handling for it, e.g. `onblocked` handler, so that our script doesn't just die silently.
|
Such update collision happens rarely, but we should at least have some handling for it, e.g. `onblocked` handler, so that our script doesn't surprise the user by dying silently.
|
||||||
|
|
||||||
## Object store
|
## Object store
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue