Skip to content

Commit d44422c

Browse files
authored
Merge pull request javascript-tutorial#364 from odsantos/update-en-cross-window-communication
Update "Cross-window communication" article
2 parents 260710e + 54592b3 commit d44422c

File tree

1 file changed

+57
-68
lines changed
  • 3-frames-and-windows/03-cross-window-communication

1 file changed

+57
-68
lines changed

3-frames-and-windows/03-cross-window-communication/article.md

+57-68
Original file line numberDiff line numberDiff line change
@@ -26,82 +26,66 @@ The "Same Origin" policy states that:
2626
- 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.
2727
- 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).
2828

29-
Now let's see some examples. First, we'll look at pages that come from the same origin and do not conflict with the "Same Origin" policy, and afterwards we'll cover cross-window messaging that allows to work around the "Same Origin" policy.
29+
### In action: iframe
3030

31+
An `<iframe>` tag hosts a separate embedded window, with its own separate `document` and `window` objects.
3132

32-
````warn header="Subdomains may be same-origin"
33-
There's a small exclusion in the "Same Origin" policy.
33+
We can access them using properties:
3434

35-
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`), they can be treated as coming from the "same origin".
35+
- `iframe.contentWindow` to get the window inside the `<iframe>`.
36+
- `iframe.contentDocument` to get the document inside the `<iframe>`, shorthand for `iframe.contentWindow.document`.
3637

37-
To make it work, all such pages (including the one from `site.com`) should run the code:
38+
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).
3839

39-
```js
40-
document.domain = 'site.com';
41-
```
42-
43-
That's all. Now they can interact without limitations. Again, that's only possible for pages with the same second-level domain.
44-
````
45-
46-
## Accessing an iframe contents
47-
48-
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.
49-
50-
The embedded window has a separate `document` and `window` objects.
51-
52-
We can access them like using the properties:
53-
54-
- `iframe.contentWindow` is a reference to the window inside the `<iframe>`.
55-
- `iframe.contentDocument` is a reference to the document inside the `<iframe>`.
56-
57-
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 (with exclusions noted above).
58-
59-
For instance, here's an `<iframe>` from another origin:
40+
For instance, let's try reading and writing to `<iframe>` from another origin:
6041

6142
```html run
6243
<iframe src="https://example.com" id="iframe"></iframe>
6344

6445
<script>
6546
iframe.onload = function() {
6647
// we can get the reference to the inner window
67-
let iframeWindow = iframe.contentWindow;
68-
48+
*!*
49+
let iframeWindow = iframe.contentWindow; // OK
50+
*/!*
6951
try {
7052
// ...but not to the document inside it
71-
let doc = iframe.contentDocument;
53+
*!*
54+
let doc = iframe.contentDocument; // ERROR
55+
*/!*
7256
} catch(e) {
7357
alert(e); // Security Error (another origin)
7458
}
7559
76-
// also we can't read the URL of the page in it
60+
// also we can't READ the URL of the page in iframe
7761
try {
78-
alert(iframe.contentWindow.location);
62+
// Can't read URL from the Location object
63+
*!*
64+
let href = iframe.contentWindow.location.href; // ERROR
65+
*/!*
7966
} catch(e) {
8067
alert(e); // Security Error
8168
}
8269
83-
// ...but we can change it (and thus load something else into the iframe)!
84-
iframe.contentWindow.location = '/'; // works
70+
// ...we can WRITE into location (and thus load something else into the iframe)!
71+
*!*
72+
iframe.contentWindow.location = '/'; // OK
73+
*/!*
8574
86-
iframe.onload = null; // clear the handler, to run this code only once
75+
iframe.onload = null; // clear the handler, not to run it after the location change
8776
};
8877
</script>
8978
```
9079

9180
The code above shows errors for any operations except:
9281

93-
- Getting the reference to the inner window `iframe.contentWindow`
94-
- Changing its `location`.
82+
- Getting the reference to the inner window `iframe.contentWindow` - that's allowed.
83+
- Writing to `location`.
9584

96-
```smart header="`iframe.onload` vs `iframe.contentWindow.onload`"
97-
The `iframe.onload` event is actually the same as `iframe.contentWindow.onload`. It triggers when the embedded window fully loads with all resources.
98-
99-
...But `iframe.onload` is always available, while `iframe.contentWindow.onload` needs the same origin.
100-
```
101-
102-
And now an example with the same origin. We can do anything with the embedded window:
85+
Contrary to that, if the `<iframe>` has the same origin, we can do anything with it:
10386

10487
```html run
88+
<!-- iframe from the same site -->
10589
<iframe src="/" id="iframe"></iframe>
10690

10791
<script>
@@ -112,7 +96,13 @@ And now an example with the same origin. We can do anything with the embedded wi
11296
</script>
11397
```
11498

115-
### Please wait until the iframe loads
99+
```smart header="`iframe.onload` vs `iframe.contentWindow.onload`"
100+
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.
101+
102+
...But we can't access `iframe.contentWindow.onload` for an iframe from another origin, so using `iframe.onload`.
103+
```
104+
105+
## Windows on subdomains: document.domain
116106
117107
By definition, two URLs with different domains have different origins.
118108
@@ -159,11 +149,13 @@ Here, look:
159149
</script>
160150
```
161151

162-
That's actually a well-known pitfall for developers. We shouldn't work with the document immediately, because that's the *wrong document*. If we set any event handlers on it, they will be ignored.
152+
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.
153+
154+
How to detect the moment when the document is there?
163155

164-
...But the `onload` event triggers when the whole iframe with all resources is loaded. What if we want to act sooner, on `DOMContentLoaded` of the embedded document?
156+
The right document is definitely at place when `iframe.onload` triggers. But it only triggers when the whole iframe with all resources is loaded.
165157

166-
That's not possible if the iframe comes from another origin. But for the same origin we can try to catch the moment when a new document appears, and then setup necessary handlers, like this:
158+
We can try to catch the moment earlier using checks in `setInterval`:
167159

168160
```html run
169161
<iframe src="/" id="iframe"></iframe>
@@ -173,21 +165,17 @@ That's not possible if the iframe comes from another origin. But for the same or
173165
174166
// every 100 ms check if the document is the new one
175167
let timer = setInterval(() => {
176-
if (iframe.contentDocument == oldDoc) return;
168+
let newDoc = iframe.contentDocument;
169+
if (newDoc == oldDoc) return;
177170
178-
// new document, let's set handlers
179-
iframe.contentDocument.addEventListener('DOMContentLoaded', () => {
180-
iframe.contentDocument.body.prepend('Hello, world!');
181-
});
171+
alert("New document is here!");
182172
183173
clearInterval(timer); // cancel setInterval, don't need it any more
184174
}, 100);
185175
</script>
186176
```
187177

188-
Let me know in comments if you know a better solution here.
189-
190-
## window.frames
178+
## Collection: window.frames
191179

192180
An alternative way to get a window object for `<iframe>` -- is to get it from the named collection `window.frames`:
193181

@@ -229,11 +217,11 @@ if (window == top) { // current window == window.top?
229217
}
230218
```
231219

232-
## The sandbox attribute
220+
## The "sandbox" iframe attribute
233221

234222
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.
235223

236-
By default, for `<iframe sandbox src="...">` the "default set" of restrictions is applied to the iframe. But we can provide a space-separated list of "excluded" limitations as a value of the attribute, like this: `<iframe sandbox="allow-forms allow-popups">`. The listed limitations are not applied.
224+
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">`.
237225

238226
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.
239227

@@ -287,9 +275,9 @@ Arguments:
287275
`targetOrigin`
288276
: Specifies the origin for the target window, so that only a window from the given origin will get the message.
289277

290-
The `targetOrigin` is a safety measure. Remember, if the target window comes from another origin, we can't read it's `location`. So we can't be sure which site is open in the intended window right now: the user could navigate away.
278+
The `targetOrigin` is a safety measure. Remember, if the target window comes from another origin, we can't read its `location` in the sender window. So we can't be sure which site is open in the intended window right now: the user could navigate away, and the sender window has no idea about it.
291279

292-
Specifying `targetOrigin` ensures that the window only receives the data if it's still at that site. Good when the data is sensitive.
280+
Specifying `targetOrigin` ensures that the window only receives the data if it's still at the right site. Important when the data is sensitive.
293281

294282
For instance, here `win` will only receive the message if it has a document from the origin `http://example.com`:
295283

@@ -331,7 +319,7 @@ The event object has special properties:
331319
: The origin of the sender, for instance `http://javascript.info`.
332320

333321
`source`
334-
: The reference to the sender window. We can immediately `postMessage` back if we want.
322+
: The reference to the sender window. We can immediately `source.postMessage(...)` back if we want.
335323

336324
To assign that handler, we should use `addEventListener`, a short syntax `window.onmessage` does not work.
337325

@@ -345,6 +333,8 @@ window.addEventListener("message", function(event) {
345333
}
346334

347335
alert( "received: " + event.data );
336+
337+
// can message back using event.source.postMessage(...)
348338
});
349339
```
350340

@@ -356,9 +346,9 @@ The full example:
356346

357347
To call methods and access the content of another window, we should first have a reference to it.
358348

359-
For popups we have two properties:
360-
- `window.open` -- opens a new window and returns a reference to it,
361-
- `window.opener` -- a reference to the opener window from a popup
349+
For popups we have these references:
350+
- From the opener window: `window.open` -- opens a new window and returns a reference to it,
351+
- From the popup: `window.opener` -- is a reference to the opener window from a popup.
362352

363353
For iframes, we can access parent/children windows using:
364354
- `window.frames` -- a collection of nested window objects,
@@ -368,18 +358,17 @@ For iframes, we can access parent/children windows using:
368358
If windows share the same origin (host, port, protocol), then windows can do whatever they want with each other.
369359

370360
Otherwise, only possible actions are:
371-
- Change the location of another window (write-only access).
361+
- Change the `location` of another window (write-only access).
372362
- Post a message to it.
373363

374-
375-
Exclusions are:
364+
Exceptions are:
376365
- 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.
377366
- 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.
378367

379-
The `postMessage` interface allows two windows to talk with security checks:
368+
The `postMessage` interface allows two windows with any origins to talk:
380369

381370
1. The sender calls `targetWin.postMessage(data, targetOrigin)`.
382-
2. If `targetOrigin` is not `'*'`, then the browser checks if window `targetWin` has the URL from `targetWin` site.
371+
2. If `targetOrigin` is not `'*'`, then the browser checks if window `targetWin` has the origin `targetOrigin`.
383372
3. If it is so, then `targetWin` triggers the `message` event with special properties:
384373
- `origin` -- the origin of the sender window (like `http://my.site.com`)
385374
- `source` -- the reference to the sender window.

0 commit comments

Comments
 (0)