updates
This commit is contained in:
parent
94c83e9e50
commit
cc5213b09e
79 changed files with 1341 additions and 357 deletions
|
@ -7,12 +7,20 @@ Basically, you just run:
|
|||
window.open('https://javascript.info/')
|
||||
```
|
||||
|
||||
... And it will open a new window with given URL. Most modern browsers are configured to open new tabs instead of separate windows.
|
||||
...And it will open a new window with given URL. Most modern browsers are configured to open new tabs instead of separate windows.
|
||||
|
||||
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.
|
||||
|
||||
Still, there are situations when a popup works good, 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.
|
||||
2. It's very easy to open a popup, little to no overhead.
|
||||
3. A popup can navigate (change URL) and send messages to the opener window.
|
||||
|
||||
## Popup blocking
|
||||
|
||||
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: JavaScript is able to send requests for server, so popups are rarely used. But sometimes they are still handy.
|
||||
|
||||
In the past, evil sites abused popups a lot. A bad page could open tons of popup windows with ads. So now most browsers try to block popups and protect the user.
|
||||
|
||||
**Most browsers block popups if they are called outside of user-triggered event handlers like `onclick`.**
|
||||
|
@ -50,14 +58,6 @@ setTimeout(() => window.open('http://google.com'), 1000);
|
|||
|
||||
The difference is that Firefox treats a timeout of 2000ms or less are acceptable, but after it -- removes the "trust", assuming that now it's "outside of the user action". So the first one is blocked, and the second one is not.
|
||||
|
||||
## Modern usage
|
||||
|
||||
As of now, we have many methods to load and show data on-page with JavaScript. But there are still situations when a popup works good, 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.
|
||||
2. It's very easy to open a popup, little to no overhead.
|
||||
3. A popup may persist even if the user left the page. In also can navigate (change URL) in the opener window.
|
||||
|
||||
## window.open
|
||||
|
||||
The syntax to open a popup is: `window.open(url, name, params)`:
|
||||
|
@ -118,16 +118,26 @@ Rules for omitted settings:
|
|||
- If there is no `left/top` in params, then the browser tries to open a new window near the last opened window.
|
||||
- If there is no `width/height`, then the new window will be the same size as the last opened.
|
||||
|
||||
## Accessing a popup
|
||||
## Accessing popup from window
|
||||
|
||||
The `open` call returns a reference to the new window. It can be used to manipulate it's properties, change location and even more.
|
||||
|
||||
In the example below, the contents of the new window is modified after loading.
|
||||
In this example, we generate popup content from JavaScript:
|
||||
|
||||
```js
|
||||
let newWin = window.open("about:blank", "hello", "width=200,height=200");
|
||||
|
||||
newWin.document.write("Hello, world!");
|
||||
```
|
||||
|
||||
And here we modify the contents after loading:
|
||||
|
||||
```js run
|
||||
let newWindow = open('/', 'example', 'width=300,height=300')
|
||||
newWindow.focus();
|
||||
|
||||
alert(newWin.location.href); // (*) about:blank, loading hasn't started yet
|
||||
|
||||
newWindow.onload = function() {
|
||||
let html = `<div style="font-size:30px">Welcome!</div>`;
|
||||
*!*
|
||||
|
@ -136,35 +146,95 @@ newWindow.onload = function() {
|
|||
};
|
||||
```
|
||||
|
||||
Please note that external `document` content is only accessible for windows from the same origin (the same protocol://domain:port).
|
||||
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`.
|
||||
|
||||
For windows with URLs from another sites, we are able to change the location by assigning `newWindow.location=...`, but we can't read the location or access the content. That's for user safety, so that an evil page can't open a popup with `http://gmail.com` and read the data. We'll talk more about it later.
|
||||
```warn header="Same origin policy"
|
||||
Windows may only freely modify each other if they come from the same origin (the same protocol://domain:port).
|
||||
|
||||
## Accessing the opener window
|
||||
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>.
|
||||
```
|
||||
|
||||
A popup may access the "opener" window as well. A JavaScript in it may use `window.opener` to access the window that opened it. It is `null` for all windows except popups.
|
||||
## Accessing window from popup
|
||||
|
||||
So both the main window and the popup have a reference to each other. They may modify each other freely assuming that they come from the same origin. If that's not so, then there are still means to communicate, to be covered in the next chapter <info:cross-window-communication>.
|
||||
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":
|
||||
|
||||
```js run
|
||||
let newWin = window.open("about:blank", "hello", "width=200,height=200");
|
||||
|
||||
newWin.document.write(
|
||||
"<script>window.opener.document.body.innerHTML = 'Test'<\/script>"
|
||||
);
|
||||
```
|
||||
|
||||
So the connection between the windows is bidirectional: the main window and the popup have a reference to each other.
|
||||
|
||||
## Closing a popup
|
||||
|
||||
If we don't need a popup any more, we can call `newWindow.close()` on it.
|
||||
- To close a window: `win.close()`.
|
||||
- To check if a window is closed: `win.close` property.
|
||||
|
||||
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()`.
|
||||
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 `newWindow.closed` 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 could close it, and our code should take that possibility into account.
|
||||
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.
|
||||
|
||||
This code loads and then closes the window:
|
||||
|
||||
```js run
|
||||
let newWindow = open('/', 'example', 'width=300,height=300')
|
||||
let newWindow = open('/', 'example', 'width=300,height=300');
|
||||
|
||||
newWindow.onload = function() {
|
||||
newWindow.close();
|
||||
alert(newWindow.closed); // true
|
||||
};
|
||||
```
|
||||
|
||||
## Focus/blur on a popup
|
||||
|
||||
## Scrolling and resizing
|
||||
|
||||
There are methods to move/resize a window:
|
||||
|
||||
`win.moveBy(x,y)`
|
||||
: Move the window relative to current position `x` pixels to the right and `y` pixels down. Negative values are allowed (to move left/up).
|
||||
|
||||
`win.moveTo(x,y)`
|
||||
: Move the window to coordinates `(x,y)` on the screen.
|
||||
|
||||
`win.resizeBy(width,height)`
|
||||
: Resize the window by given `width/height` relative to the current size. Negative values are allowed.
|
||||
|
||||
`win.resizeTo(width,height)`
|
||||
: Resize the window to the given size.
|
||||
|
||||
There's also `window.onresize` event.
|
||||
|
||||
```warn header="Only popups"
|
||||
To prevent abuse, the browser usually blocks these methods. They only work reliably on popups that we opened, that have no additional tabs.
|
||||
```
|
||||
|
||||
```warn header="No minification/maximization"
|
||||
JavaScript has no way to minify or maximize a window. These OS-level functions are hidden from Frontend-developers.
|
||||
|
||||
Move/resize methods do not work for maximized/minimized windows.
|
||||
```
|
||||
|
||||
## Scrolling a window
|
||||
|
||||
We already talked about scrolling a window in the chapter <info:size-and-scroll-window>.
|
||||
|
||||
`win.scrollBy(x,y)`
|
||||
: Scroll the window `x` pixels right and `y` down relative the current scroll. Negative values are allowed.
|
||||
|
||||
`win.scrollTo(x,y)`
|
||||
: Scroll the window to the given coordinates `(x,y)`.
|
||||
|
||||
`elem.scrollIntoView(top = true)`
|
||||
: Scroll the window to make `elem` show up at the top (the default) or at the bottom for `elem.scrollIntoView(false)`.
|
||||
|
||||
There's also `window.onscroll` event.
|
||||
|
||||
## Focus/blur on a window
|
||||
|
||||
Theoretically, there are `window.focus()` and `window.blur()` methods to focus/unfocus on a window. Also there are `focus/blur` events that allow to focus a window and catch the moment when the visitor switches elsewhere.
|
||||
|
||||
|
@ -189,12 +259,23 @@ For instance:
|
|||
|
||||
## Summary
|
||||
|
||||
Всплывающие окна используются нечасто. Ведь загрузить новую информацию можно динамически, с помощью технологии AJAX, а показать -- в элементе `<div>`, расположенным над страницей (`z-index`). Ещё одна альтернатива -- тег `<iframe>`.
|
||||
|
||||
Но в некоторых случаях всплывающие окна бывают очень даже полезны. Например, отдельное окно сервиса онлайн-консультаций. Посетитель может ходить по сайту в основном окне, а общаться в чате -- во вспомогательном.
|
||||
|
||||
Если вы хотите использовать всплывающее окно, предупредите посетителя об этом, так же и при использовании `target="_blank"` в ссылках или формах. Иконка открывающегося окошка на ссылке поможет посетителю понять, что происходит и не потерять оба окна из поля зрения.
|
||||
|
||||
- A popup can be opened by the `open(url, name, params)` call. It returns the reference to the newly opened window.
|
||||
- By default, browsers block `open` calls from the code outside of user actions. Usually a notification appears, so that a user may allow them.
|
||||
- The popup may access the opener window using the `window.opener` property, so the two are connected.
|
||||
- If the main window and the popup come from the same origin, they can freely read and modify each other. Otherwise, they can change location of each other and communicate using messages (to be covered).
|
||||
- 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.
|
||||
- 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).
|
||||
|
||||
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.
|
||||
- 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`.
|
||||
- ...And a few scrolling and resizing methods.
|
||||
|
||||
Also if we open a popup, a good practice is to notify the user about it. An icon with the opening window can help the visitor to survive the focus shift and keep both windows in mind.
|
||||
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.
|
||||
|
|
|
@ -26,39 +26,18 @@ The "Same Origin" policy states that:
|
|||
- if we have a reference to another window, e.g. a popup created by `window.open` or a window inside `<iframe>`, and that window comes from the same origin, then we have full access to that window.
|
||||
- otherwise, if it comes from another origin, then we can't access the content of that window: variables, document, anything. The only exception is `location`: we can change it (thus redirecting the user). But we cannot *read* location (so we can't see where the user is now, no information leak).
|
||||
|
||||
Now let's see some examples. First, we'll look at pages that come from the same origin and thus allow direct access, and afterwards we'll cover cross-window messaging that allows to work around the "Same Origin" policy.
|
||||
### In action: iframe
|
||||
|
||||
An `<iframe>` tag hosts embbedded window, with its own separate `document` and `window` objects.
|
||||
|
||||
````warn header="Windows on different subdomains of the same domain"
|
||||
By definition, two URLs with different domains have different origins.
|
||||
We can access them using properties:
|
||||
|
||||
Still, there's a small exclusion here.
|
||||
- `iframe.contentWindow` to get the window inside the `<iframe>`.
|
||||
- `iframe.contentDocument` to get the document inside the `<iframe>`.
|
||||
|
||||
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.
|
||||
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).
|
||||
|
||||
To make it work, each window (including the one from `site.com`) should run the code:
|
||||
|
||||
```js
|
||||
document.domain = 'site.com';
|
||||
```
|
||||
|
||||
That's all. Now they can interact without limitations. Again, that's only possible for pages with the same second-level domain.
|
||||
````
|
||||
|
||||
## Accessing an iframe contents
|
||||
|
||||
Our first example covers iframes. An `<iframe>` is a two-faced beast. From one side it's a tag, just like `<script>` or `<img>`. From the other side it's a window-in-window.
|
||||
|
||||
The embedded window in the iframe has a separate `document` and `window` objects.
|
||||
|
||||
We can access them like using the properties:
|
||||
|
||||
- `iframe.contentWindow` is a reference to the window inside the `<iframe>`.
|
||||
- `iframe.contentDocument` is a reference to the document inside the `<iframe>`.
|
||||
|
||||
When we access an 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).
|
||||
|
||||
For instance, here's an `<iframe>` from another origin:
|
||||
For instance, let's try reading and writing to `<iframe>` from another origin:
|
||||
|
||||
```html run
|
||||
<iframe src="https://example.com" id="iframe"></iframe>
|
||||
|
@ -66,44 +45,47 @@ For instance, here's an `<iframe>` from another origin:
|
|||
<script>
|
||||
iframe.onload = function() {
|
||||
// we can get the reference to the inner window
|
||||
let iframeWindow = iframe.contentWindow;
|
||||
|
||||
*!*
|
||||
let iframeWindow = iframe.contentWindow; // OK
|
||||
*/!*
|
||||
try {
|
||||
// ...but not to the document inside it
|
||||
let doc = iframe.contentDocument;
|
||||
*!*
|
||||
let doc = iframe.contentDocument; // ERROR
|
||||
*/!*
|
||||
} catch(e) {
|
||||
alert(e); // Security Error (another origin)
|
||||
}
|
||||
|
||||
// also we can't read the URL of the page in it
|
||||
// also we can't READ the URL of the page in iframe
|
||||
try {
|
||||
alert(iframe.contentWindow.location);
|
||||
// Can't read URL from the Location object
|
||||
*!*
|
||||
let href = iframe.contentWindow.location.href; // ERROR
|
||||
*/!*
|
||||
} catch(e) {
|
||||
alert(e); // Security Error
|
||||
}
|
||||
|
||||
// ...but we can change it (and thus load something else into the iframe)!
|
||||
iframe.contentWindow.location = '/'; // works
|
||||
// ...we can WRITE into location (and thus load something else into the iframe)!
|
||||
*!*
|
||||
iframe.contentWindow.location = '/'; // OK
|
||||
*/!*
|
||||
|
||||
iframe.onload = null; // clear the handler, to run this code only once
|
||||
iframe.onload = null; // clear the handler, not to run it after the location change
|
||||
};
|
||||
</script>
|
||||
```
|
||||
|
||||
The code above shows errors for any operations except:
|
||||
|
||||
- Getting the reference to the inner window `iframe.contentWindow`
|
||||
- Changing its `location`.
|
||||
- Getting the reference to the inner window `iframe.contentWindow` - that's allowed.
|
||||
- Writing to `location`.
|
||||
|
||||
```smart header="`iframe.onload` vs `iframe.contentWindow.onload`"
|
||||
The `iframe.onload` event is essentially the same as `iframe.contentWindow.onload`. It triggers when the embedded window fully loads with all resources.
|
||||
|
||||
...But `iframe.onload` is always available from outside the iframe, while accessing `iframe.contentWindow.onload` is only permitted from the window with the same origin.
|
||||
```
|
||||
|
||||
And now an example with the same origin. We can do anything with the embedded window:
|
||||
Contrary to that, if the `<iframe>` has the same origin, we can do anything with it:
|
||||
|
||||
```html run
|
||||
<!-- iframe from the same site -->
|
||||
<iframe src="/" id="iframe"></iframe>
|
||||
|
||||
<script>
|
||||
|
@ -114,9 +96,33 @@ And now an example with the same origin. We can do anything with the embedded wi
|
|||
</script>
|
||||
```
|
||||
|
||||
### Please wait until the iframe loads
|
||||
```smart header="`iframe.onload` vs `iframe.contentWindow.onload`"
|
||||
The `iframe.onload` event (on the `<iframe>` tag) is essentially the same as `iframe.contentWindow.onload` (on the embedded window object). It triggers when the embedded window fully loads with all resources.
|
||||
|
||||
When an iframe is created, it immediately has a document. But that document is different from the one that finally loads into it!
|
||||
...But we can't access `iframe.contentWindow.onload` for an iframe from another origin, so using `iframe.onload`.
|
||||
```
|
||||
|
||||
## Iframes on subdomains: document.domain
|
||||
|
||||
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.
|
||||
|
||||
To make it work, each window (including the one from `site.com`) should run the code:
|
||||
|
||||
```js
|
||||
document.domain = 'site.com';
|
||||
```
|
||||
|
||||
That's all. Now they can interact without limitations. Again, that's only possible for pages with the same second-level domain.
|
||||
|
||||
## Iframe: wrong document pitfall
|
||||
|
||||
When an iframe comes from the same origin, and we may access its `document`, there's a pitfall. It's not related to cross-domain things, but important to know.
|
||||
|
||||
Upon its creation an iframe immediately has a document. But that document is different from the one that loads into it!
|
||||
|
||||
So if we do something with the document immediately, that will probably be lost.
|
||||
|
||||
Here, look:
|
||||
|
||||
|
@ -136,13 +142,15 @@ Here, look:
|
|||
</script>
|
||||
```
|
||||
|
||||
That's a well-known pitfall. 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.
|
||||
|
||||
...We definitely can access the right document when the `onload` event triggers. But it only triggers when the whole iframe with all resources is loaded. What if we want to act sooner, on `DOMContentLoaded` of the embedded document?
|
||||
...The right document is definitely there when `iframe.onload` triggers. But it only triggers when the whole iframe with all resources is loaded.
|
||||
|
||||
If the iframe comes from another origin, we can't access its document, so it's impossible.
|
||||
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.
|
||||
|
||||
But for the same origin we can setup the event handler. We just need to set it on the right document. For instance, we can try to catch the moment when a new document appears using checks in `setInterval`, and then setup necessary handlers, like this:
|
||||
Here's a small recipe for this.
|
||||
|
||||
We can try to catch the moment when a new document appears using checks in `setInterval`, and then setup necessary handlers:
|
||||
|
||||
```html run
|
||||
<iframe src="/" id="iframe"></iframe>
|
||||
|
@ -173,7 +181,7 @@ But for the same origin we can setup the event handler. We just need to set it o
|
|||
</script>
|
||||
```
|
||||
|
||||
## window.frames
|
||||
## Collection: window.frames
|
||||
|
||||
An alternative way to get a window object for `<iframe>` -- is to get it from the named collection `window.frames`:
|
||||
|
||||
|
@ -215,7 +223,7 @@ if (window == top) { // current window == window.top?
|
|||
}
|
||||
```
|
||||
|
||||
## The sandbox attribute
|
||||
## The "sandbox" iframe attribute
|
||||
|
||||
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.
|
||||
|
||||
|
@ -331,6 +339,8 @@ window.addEventListener("message", function(event) {
|
|||
}
|
||||
|
||||
alert( "received: " + event.data );
|
||||
|
||||
// can message back using event.source.postMessage(...)
|
||||
});
|
||||
```
|
||||
|
||||
|
@ -358,11 +368,10 @@ For iframes, we can access parent/children windows using:
|
|||
If windows share the same origin (host, port, protocol), then windows can do whatever they want with each other.
|
||||
|
||||
Otherwise, only possible actions are:
|
||||
- Change the location of another window (write-only access).
|
||||
- Change the `location` of another window (write-only access).
|
||||
- Post a message to it.
|
||||
|
||||
|
||||
Exclusions are:
|
||||
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.
|
||||
- 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.
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue