Skip to content

Commit 78176b6

Browse files
committed
up
1 parent 733b0c6 commit 78176b6

File tree

1 file changed

+71
-50
lines changed
  • 4-frames-and-windows/03-cross-window-communication

1 file changed

+71
-50
lines changed

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

Lines changed: 71 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -10,28 +10,30 @@ The idea is that if we have two windows open: one from `john-smith.com`, and ano
1010

1111
Two URLs are said to have the "same origin" if they have the same protocol, domain and port.
1212

13-
These URLs have the same origin:
13+
These URLs all share the same origin:
1414

1515
- `http://site.com`
1616
- `http://site.com/`
1717
- `http://site.com/my/page.html`
1818

19-
These ones are not:
19+
These ones do not:
2020

21-
- `http://www.site.com` (another domain: `www.` matters)
22-
- `http://site.org` (another domain: `.org` matters)
23-
- `https://site.com` (another protocol: `https`)
24-
- `http://site.com:8080` (another port: `8080`)
21+
- <code>http://<b>www.</b>site.com</code> (another domain: `www.` matters)
22+
- <code>http://<b>site.org</b></code> (another domain: `.org` matters)
23+
- <code><b>https://</b>site.com</code> (another protocol: `https`)
24+
- <code>http://site.com:<b>8080</b></code> (another port: `8080`)
2525

2626
If we have a reference to another window (a popup or iframe), and that window comes from the same origin, then we can do everything with it.
2727

28-
Otherwise, we can only change its location. Please note: not *read*, but modify it, redirect it to another place. That's possible, because such action does not reveal any data. Also such windows windows may exchange messages. Soon about that later.
28+
If it comes from another origin, then we can only change its location. Please note: not *read* the location, but *modify* it, redirect it to another place. That's safe, because the URL may contain sensitive parameters, so reading it from another origin is prohibited, but changing is not.
29+
30+
Also such windows windows may exchange messages. Soon about that later.
2931

3032
````warn header="Exclusion: subdomains may be same-origin"
3133
3234
There's an important exclusion in the same-origin policy.
3335
34-
If windows share the same second-level domain, for instance `john.site.com`, `peter.site.com` and `site.com`, and assign to `document.domain` their common second-level domain `site.com`, then limitations are removed.
36+
If windows share the same second-level domain, for instance `john.site.com`, `peter.site.com` and `site.com`, we can use JavaScript to assign to `document.domain` their common second-level domain `site.com`. Then these windows are treated as having the same origin.
3537
3638
In other words, all such documents (including the one from `site.com`) should have the code:
3739
@@ -40,49 +42,51 @@ document.domain = 'site.com';
4042
```
4143
4244
Then they can interact without limitations.
45+
46+
That's only possible for pages with the same second-level domain.
4347
````
4448

45-
## Managing iframes
49+
## Accessing an iframe contents
4650

47-
An `<iframe>` is a two-faced beast. From one side it's a tag, just like `<script>` or `<img>`. From another side it's a window-in-window.
51+
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.
4852

49-
The embedded window has a separate `window` and `document` objects, scripts and so on.
53+
The embedded window has a separate `document` and `window` objects.
5054

51-
We can access them:
55+
We can access them like using the properties:
5256

5357
- `iframe.contentWindow` is a reference to the window inside the `<iframe>`.
54-
- `iframe.contentDocument` is a reference to the document inside it.
58+
- `iframe.contentDocument` is a reference to the document inside the `<iframe>`.
5559

56-
When we access an embedded window, the browser checks if the iframe has the same origin. If that's not so then the access to almost everything is denied.
60+
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).
5761

58-
For instance:
62+
For instance, here's an `<iframe>` from another origin:
5963

6064
```html run
6165
<iframe src="https://example.com" id="iframe"></iframe>
6266

6367
<script>
6468
iframe.onload = function() {
65-
// can get the reference to the inner window
69+
// we can get the reference to the inner window
6670
let iframeWindow = iframe.contentWindow;
6771
6872
try {
6973
// ...but not to the document inside it
7074
let doc = iframe.contentDocument;
7175
} catch(e) {
72-
alert(e); // Security Error
76+
alert(e); // Security Error (another origin)
7377
}
7478
75-
// also can't read iframe.contentWindow.location:
79+
// also we can't read the URL of the page in it
7680
try {
7781
alert(iframe.contentWindow.location);
7882
} catch(e) {
7983
alert(e); // Security Error
8084
}
8185
82-
// ...but can modify it!
86+
// ...but we can change it (and thus load something else into the iframe)!
8387
iframe.contentWindow.location = '/'; // works
8488
85-
iframe.onload = null; // run this code only once
89+
iframe.onload = null; // clear the handler, to run this code only once
8690
};
8791
</script>
8892
```
@@ -98,7 +102,7 @@ The `iframe.onload` event is actually the same as `iframe.contentWindow.onload`.
98102
...But `iframe.onload` is always available, while `iframe.contentWindow.onload` needs the same origin.
99103
```
100104
101-
And here's an example with the same origin:
105+
And now an example with the same origin. We can do anything with the embedded window:
102106
103107
```html run
104108
<iframe src="/" id="iframe"></iframe>
@@ -111,7 +115,7 @@ And here's an example with the same origin:
111115
</script>
112116
```
113117

114-
### Wait until the iframe loads
118+
### Please wait until the iframe loads
115119

116120
When an iframe is created, it immediately has a document. But that document is different from the one that finally loads into it!
117121

@@ -126,6 +130,7 @@ Here, look:
126130
iframe.onload = function() {
127131
let newDoc = iframe.contentDocument;
128132
*!*
133+
// the loaded document is not the same as initial!
129134
alert(oldDoc == newDoc); // false
130135
*/!*
131136
};
@@ -134,9 +139,9 @@ Here, look:
134139

135140
That's actually a well-known pitfall for novice 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.
136141

137-
...But the `onload` event triggers when the whole iframe with all resources is loaded. What if we want to act sooner, on `DOMContentLoaded`?
142+
...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?
138143

139-
We can try to catch the moment when a new document appears, and then setup necessary handlers, like this:
144+
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:
140145

141146
```html run
142147
<iframe src="/" id="iframe"></iframe>
@@ -148,19 +153,19 @@ We can try to catch the moment when a new document appears, and then setup neces
148153
let timer = setInterval(() => {
149154
if (iframe.contentDocument == oldDoc) return;
150155
151-
// yeah, now set handlers and do whatever we want
152-
clearInterval(timer);
153-
156+
// new document, let's set handlers
154157
iframe.contentDocument.addEventListener('DOMContentLoaded', () => {
155158
iframe.contentDocument.body.prepend('Hello, world!');
156159
});
160+
161+
clearInterval(timer); // cancel setInterval, don't need it any more
157162
}, 100);
158163
</script>
159164
```
160165

161166
Let me know in comments if you know a better solution here.
162167

163-
### window.frames
168+
## window.frames
164169

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

@@ -202,16 +207,18 @@ if (window == top) { // current window == window.top?
202207
}
203208
```
204209

205-
### The sandbox attribute
210+
## The sandbox attribute
211+
212+
The `sandbox` attribute allows to forbid certain actions inside an `<iframe>`, to run an untrusted code. It "sandboxes" the iframe by treating it as coming from another origin and/or applying other limitations.
206213

207-
The `sandbox` attribute allows to run untrusted content inside an `<iframe>`. It "sandboxes" the iframe by treating it as coming from another origin and forbidding certain actions.
214+
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.
208215

209-
There are many restrictions. By default, for `<iframe sandbox src="...">` all of them are applied. But if we specify them in a value of the attribute, like this: `<iframe sandbox="allow-forms allow-popups">`, then they are lifted.
216+
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.
210217

211-
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:
218+
Here's a list of limitations:
212219

213220
`allow-same-origin`
214-
: By default `"sandbox"` forces the browser to treat the `iframe` as coming from another origin, even if it's `src` points to the same site. This option removes that feature.
221+
: 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.
215222

216223
`allow-top-navigation`
217224
: Allows the `iframe` to change `parent.location`.
@@ -227,13 +234,15 @@ In other words, an empty `"sandbox"` attribute puts the strictest limitations po
227234

228235
See [the manual](mdn:/HTML/Element/iframe) for more.
229236

230-
The example below demonstrates a sandboxed iframe with some JavaScript and a form. Neither one works:
237+
The example below demonstrates a sandboxed iframe with the default set of restrictions: `<iframe sandbox src="...">`. It has some JavaScript and a form.
238+
239+
Please note that nothing works. So the default set is really harsh:
231240

232241
[codetabs src="sandbox" height=140]
233242

234243

235244
```smart
236-
The purpose of the `"sandbox"` attribute is to add restrictions. It cannot remove them. In particular, it can't relax same-origin restrictions if the iframe comes from another origin.
245+
The purpose of the `"sandbox"` attribute is only to *add more* restrictions. It cannot remove them. In particular, it can't relax same-origin restrictions if the iframe comes from another origin.
237246
```
238247

239248
## Cross-window messaging
@@ -249,18 +258,16 @@ The window that wants to send a message calls [postMessage](mdn:api/Window.postM
249258
Arguments:
250259

251260
`data`
252-
: The data to send. Can be any object, the data is cloned using the "structured cloning algorithm". IE supports only strings, so we can `JSON.stringify` complex objects.
261+
: The data to send. Can be any object, the data is cloned using the "structured cloning algorithm". IE supports only strings, so we should `JSON.stringify` complex objects to support that browser.
253262

254263
`targetOrigin`
255-
: Allow only a window from the given origin to get the message.
256-
257-
The `targetOrigin` is a safety measure. If the target window comes from another origin, we can't read it's `location`. We can't be sure which site is open in it right now, the user could navigate away.
264+
: Specifies the origin for the target window, so that only a window from the given origin will get the message.
258265

259-
Specifying `targetOrigin` ensures that the window only receives the data if it's still at that site. Good when the data is secure.
266+
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.
260267

261-
If we don't want that check, we can set `targetOrigin` to `*`.
268+
Specifying `targetOrigin` ensures that the window only receives the data if it's still at that site. Good when the data is sensitive.
262269

263-
For instance:
270+
For instance, here `win` will only receive the message if it has a document from the origin `http://example.com`:
264271

265272
```html no-beautify
266273
<iframe src="http://example.com" name="example">
@@ -269,18 +276,32 @@ For instance:
269276
let win = window.frames.example;
270277
271278
win.postMessage("message", "http://example.com");
272-
// win.postMessage("message", "*");
273279
</script>
274280
```
275281

282+
If we don't want that check, we can set `targetOrigin` to `*`.
283+
284+
```html no-beautify
285+
<iframe src="http://example.com" name="example">
286+
287+
<script>
288+
let win = window.frames.example;
289+
290+
*!*
291+
win.postMessage("message", "*");
292+
*/!*
293+
</script>
294+
```
295+
296+
276297
### onmessage
277298

278-
To receive a message, the window should have a handler on the `message` event.
299+
To receive a message, the target window should have a handler on the `message` event. It triggers when `postMessage` is called (and `targetOrigin` check is successful).
279300

280301
The event object has special properties:
281302

282303
`data`
283-
: The data from `postMessage`
304+
: The data from `postMessage`.
284305

285306
`origin`
286307
: The origin of the sender, for instance `http://javascript.info`.
@@ -315,23 +336,23 @@ There's totally no delay between `postMessage` and the `message` event. That hap
315336

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

318-
For popups:
339+
For popups we have two properties:
319340
- `window.open` -- opens a new window and returns a reference to it,
320341
- `window.opener` -- a reference to the opener window from a popup
321342

322-
For iframes:
323-
- `window.frames` is a collection of nested window objects,
343+
For iframes, we can access parent/children windows using:
344+
- `window.frames` -- a collection of nested window objects,
324345
- `window.parent`, `window.top` are the references to parent and top windows,
325346
- `iframe.contentWindow` is the window inside an `<iframe>` tag.
326347

327-
Then if they come from the same origin (host, port, protocol), then windows can do whatever they want with each other.
348+
If windows share the same origin (host, port, protocol), then windows can do whatever they want with each other.
328349

329350
Otherwise, only possible actions are:
330351
- Change the location of another window (write-only access).
331352
- Post a message to it.
332353

333354
Exclusions are:
334-
- Windows share the same main domain: `a.site.com` and `b.site.com`. Then setting `document.domain='site.com'` in both of them puts them into "same origin".
355+
- 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.
335356
- 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.
336357

337358
The `postMessage` interface allows two windows to talk with security checks:

0 commit comments

Comments
 (0)