resize-iframe 0.1.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +37 -0
- package/README.md +43 -13
- package/child-source.js +2 -0
- package/inject.d.ts +11 -0
- package/inject.js +47 -0
- package/package.json +20 -2
- package/resize-iframe-child.js +26 -4
- package/resize-iframe.d.ts +6 -0
- package/resize-iframe.js +13 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,42 @@
|
|
|
1
1
|
# resize-iframe
|
|
2
2
|
|
|
3
|
+
## 0.3.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- e0996b5: `srcdoc` support, for inline HTML the host owns.
|
|
8
|
+
|
|
9
|
+
`withResizeChild(html)` from `resize-iframe/inject` inlines the child script into
|
|
10
|
+
HTML you generate — report embeds, quizzes, model output — so the document you hand
|
|
11
|
+
to `srcdoc` stays self-contained: no CDN, no relative path back to the package. It
|
|
12
|
+
is idempotent, and wraps a fragment in a minimal document with a measurable box, so
|
|
13
|
+
anything from a bare string of text upwards sizes correctly. `childScriptTag()` and
|
|
14
|
+
`childScriptSource` are exported too, for hosts that place the script themselves.
|
|
15
|
+
|
|
16
|
+
The `<resize-iframe>` element now accepts `srcdoc` alongside `src`, applies
|
|
17
|
+
`sandbox` and `allow` before either so the first load carries them, and takes a
|
|
18
|
+
`warning-timeout` attribute (`0` silences the missing-child warning).
|
|
19
|
+
|
|
20
|
+
## 0.2.0
|
|
21
|
+
|
|
22
|
+
### Minor Changes
|
|
23
|
+
|
|
24
|
+
- f2e12a7: Give the framed page a way to receive what the parent sends.
|
|
25
|
+
|
|
26
|
+
`parentIframe.onMessage = (message) => …` delivers whatever the embedder posted with `sendMessage`. Until now the channel was send-only: the parent could post into the frame, but the child script never listened, so every embedded page had to hand-roll a `message` listener and work out for itself which messages came from the embedder.
|
|
27
|
+
|
|
28
|
+
Both directions now travel in the same `resize-iframe-message` envelope, and the child checks `event.source === parent` the same way the parent checks the frame's own window. Unrelated postMessage traffic aimed at your frame no longer reaches the callback.
|
|
29
|
+
|
|
30
|
+
**Breaking for hand-rolled child listeners.** A child page reading `event.data` directly now sees `{ 'resize-iframe-message': … }` rather than the bare payload. Use `parentIframe.onMessage` instead.
|
|
31
|
+
|
|
32
|
+
### Patch Changes
|
|
33
|
+
|
|
34
|
+
- f2e12a7: Document the third-party embedding flow and cover the library with a Playwright suite.
|
|
35
|
+
|
|
36
|
+
- README: Storage Access API setup for cross-site embeds, including the `allow="storage-access"` and `allow-storage-access-by-user-activation` requirements, and why the request has to come from a click inside the frame.
|
|
37
|
+
- README: note that `sendMessage` is dropped if the frame is still on `about:blank`, so send it after `ready`.
|
|
38
|
+
- 33 specs across Chromium, Firefox and WebKit, served from two origins so they run against real cross-site restrictions rather than same-document shortcuts.
|
|
39
|
+
|
|
3
40
|
## 0.1.1
|
|
4
41
|
|
|
5
42
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -7,8 +7,11 @@ each page, no build step, no dependencies, MIT.
|
|
|
7
7
|
- ↔️ Vertical, horizontal, or both
|
|
8
8
|
- 🔒 Messages matched against the frame's own window, which cannot be forged
|
|
9
9
|
- 🍪 Storage Access API support for third-party embeds
|
|
10
|
-
- 🧩 Use `iframeResize()
|
|
11
|
-
- 📦 ~
|
|
10
|
+
- 🧩 Use `iframeResize()`, `<resize-iframe>`, or `withResizeChild` for srcdoc
|
|
11
|
+
- 📦 ~6KB unminified, across the files you ship
|
|
12
|
+
|
|
13
|
+
**[Live demo →](https://jagreehal.github.io/resize-iframe/)** — sandboxed, opaque-origin frames
|
|
14
|
+
resizing, messaging, and refusing to be read from the parent.
|
|
12
15
|
|
|
13
16
|
## Installation
|
|
14
17
|
|
|
@@ -66,6 +69,35 @@ That alone auto-resizes the frame. A cross-origin parent cannot measure your
|
|
|
66
69
|
content, so without this script nothing happens — the parent logs a warning after
|
|
67
70
|
five seconds saying exactly that.
|
|
68
71
|
|
|
72
|
+
### Inline HTML (`srcdoc`) the host owns
|
|
73
|
+
|
|
74
|
+
When you build the framed HTML yourself — a report embed, a quiz, skill output —
|
|
75
|
+
put the child script inside it with `withResizeChild` and pass the result as
|
|
76
|
+
`srcdoc`. No CDN, no relative path, the document stays self-contained:
|
|
77
|
+
|
|
78
|
+
```html
|
|
79
|
+
<script type="module">
|
|
80
|
+
import 'resize-iframe';
|
|
81
|
+
import { withResizeChild } from 'resize-iframe/inject';
|
|
82
|
+
|
|
83
|
+
const frame = document.createElement('resize-iframe');
|
|
84
|
+
frame.setAttribute('sandbox', 'allow-scripts'); // opaque origin; keep it
|
|
85
|
+
frame.setAttribute('min-h', '200px');
|
|
86
|
+
frame.setAttribute('srcdoc', withResizeChild(generatedHtml));
|
|
87
|
+
document.body.append(frame);
|
|
88
|
+
</script>
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
`withResizeChild` is idempotent, inserts the script before `</body>`, and wraps
|
|
92
|
+
fragments in a minimal document — margin zeroed, content in a box — so even a bare
|
|
93
|
+
string of text has something measurable around it. The same module exports
|
|
94
|
+
`childScriptTag()` and `childScriptSource` if you would rather place the script
|
|
95
|
+
yourself. Remote `src` pages are unchanged: only that page's author can include the
|
|
96
|
+
child script.
|
|
97
|
+
|
|
98
|
+
Working with an AI agent? `.claude/skills/resize-iframe/` teaches it which of these
|
|
99
|
+
shapes to reach for.
|
|
100
|
+
|
|
69
101
|
The script tag takes two optional attributes:
|
|
70
102
|
|
|
71
103
|
| Attribute | Default | Description |
|
|
@@ -162,11 +194,13 @@ turned off for.
|
|
|
162
194
|
| Attribute | Default | Description |
|
|
163
195
|
| ------------- | ------------------ | ---------------------------------------- |
|
|
164
196
|
| `src` | — | URL to embed |
|
|
197
|
+
| `srcdoc` | — | Inline HTML (prefer `withResizeChild`) |
|
|
165
198
|
| `title` | `Embedded content` | Accessible name for the iframe |
|
|
166
199
|
| `direction` | `vertical` | As above |
|
|
167
200
|
| `offset-size` | `0` | As above |
|
|
168
201
|
| `min-h` | — | Minimum height constraint |
|
|
169
202
|
| `max-h` | — | Maximum height constraint |
|
|
203
|
+
| `warning-timeout` | `5000` | Ms before missing-child warning; `0` off |
|
|
170
204
|
| `allow` | — | Passed through, e.g. `storage-access` |
|
|
171
205
|
| `sandbox` | — | Passed through |
|
|
172
206
|
|
|
@@ -181,9 +215,15 @@ frame.sendMessage({ hello: 'world' }, 'https://anotherdomain.com');
|
|
|
181
215
|
frame.disconnect(); // call before removing the iframe from the page
|
|
182
216
|
```
|
|
183
217
|
|
|
218
|
+
Both directions travel in the same envelope, so `parentIframe.onMessage` only ever
|
|
219
|
+
sees what the embedder sent — not the postMessage traffic every other script on the
|
|
220
|
+
page also aims at your frame. A plain `addEventListener('message', …)` in the child
|
|
221
|
+
would have to sort that out itself.
|
|
222
|
+
|
|
184
223
|
Child, on `window.parentIframe`:
|
|
185
224
|
|
|
186
225
|
```javascript
|
|
226
|
+
parentIframe.onMessage = (message) => { … }; // ← parent's sendMessage
|
|
187
227
|
parentIframe.sendMessage('ping'); // → parent's onMessage / 'frame-message' event
|
|
188
228
|
parentIframe.autoResize(false); // pause resizing; returns the current state
|
|
189
229
|
parentIframe.resize(); // nudge, for a change neither observer sees
|
|
@@ -227,21 +267,11 @@ pnpm exec playwright install
|
|
|
227
267
|
pnpm test
|
|
228
268
|
```
|
|
229
269
|
|
|
230
|
-
|
|
270
|
+
42 Playwright specs across Chromium, Firefox and WebKit. The suite serves the
|
|
231
271
|
parent page and the child pages from two different origins — `localhost` and
|
|
232
272
|
`127.0.0.1`, which are cross-*site*, not merely cross-origin — so every test runs
|
|
233
273
|
against the same partitioning and `contentDocument` restrictions as production.
|
|
234
274
|
|
|
235
|
-
## Testing
|
|
236
|
-
|
|
237
|
-
```bash
|
|
238
|
-
pnpm install
|
|
239
|
-
pnpm exec playwright install
|
|
240
|
-
pnpm test
|
|
241
|
-
```
|
|
242
|
-
|
|
243
|
-
21 Playwright specs, run across Chromium, Firefox and WebKit.
|
|
244
|
-
|
|
245
275
|
## Browser Support
|
|
246
276
|
|
|
247
277
|
- Chrome/Edge 80+
|
package/child-source.js
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
// Generated by scripts/embed-child.mjs — do not edit.
|
|
2
|
+
export const childScriptSource = "// Load this inside the framed page as a classic script, e.g.\n// <script src=\".../resize-iframe-child.js\"><\\/script>\n// (Written with an escaped closer so the file can also be inlined into a\n// parent <script> without the HTML parser cutting the tag short.)\n// Optional attributes on that script tag:\n// data-parent-origin=\"https://parent.example\" report size only to that embedder\n// data-size-selector=\".content\" measure these elements instead\n// Or mark elements in the page with data-iframe-size to the same effect.\nconst config = document.currentScript?.dataset ?? {};\nconst targetOrigin = config.parentOrigin || '*';\nconst sizeSelector = config.sizeSelector || '[data-iframe-size]';\n\nconst start = () => {\n let last = { height: 0, width: 0 };\n let queued = false;\n let auto = true;\n let inbound = null;\n\n // Measure the bottom and right edges of the marked elements, or of body's\n // children. Never body itself: it stretches to fill the frame in quirks mode\n // or under `body { height: 100% }`, which ratchets the frame larger and never\n // lets it shrink back.\n // Body's own bottom/right padding and margin fall outside the measurement —\n // zero them if that gap matters, or mark a wrapper with data-iframe-size.\n const measure = () => {\n const marked = document.querySelectorAll(sizeSelector);\n const elements = marked.length ? marked : document.body.children;\n if (!elements.length) {\n const body = document.body.getBoundingClientRect();\n return { height: Math.ceil(body.bottom), width: Math.ceil(body.right) };\n }\n let height = 0;\n let width = 0;\n for (const element of elements) {\n const box = element.getBoundingClientRect();\n height = Math.max(height, box.bottom);\n width = Math.max(width, box.right);\n }\n return { height: Math.ceil(height), width: Math.ceil(width) };\n };\n\n const post = () => {\n queued = false;\n const size = measure();\n if (size.height === last.height && size.width === last.width) return;\n last = size;\n parent.postMessage({ 'resize-iframe': size }, targetOrigin);\n };\n\n // Batch to one measurement per frame: mutations arrive in bursts and every\n // measure() forces a layout.\n const schedule = () => {\n if (queued || !auto) return;\n queued = true;\n requestAnimationFrame(post);\n };\n\n // ResizeObserver catches reflow (images, fonts, viewport). MutationObserver\n // catches DOM and style changes, which the observer misses entirely whenever\n // body is stretched to the frame and so never changes size itself.\n new ResizeObserver(schedule).observe(document.body);\n new MutationObserver(schedule).observe(document.body, {\n subtree: true,\n childList: true,\n attributes: true,\n characterData: true,\n });\n addEventListener('load', schedule); // images and fonts landing late\n\n // Messages from the embedder. event.source is set by the browser and cannot be\n // forged, so this is the same guard the parent uses in the other direction, and\n // the envelope keeps unrelated postMessage traffic (analytics, wallets, other\n // embeds) out of the callback.\n addEventListener('message', (event) => {\n if (event.source !== parent) return;\n const data = event.data;\n if (data && typeof data === 'object' && 'resize-iframe-message' in data) {\n inbound?.(data['resize-iframe-message']);\n }\n });\n\n // Third-party cookies: an embedded page gets partitioned storage until the user\n // grants access. Probe the API rather than sniffing the browser — Safari and\n // Chrome both partition, and which browsers do is not a stable list.\n const hasStorageAccess = () =>\n document.hasStorageAccess?.().catch(() => false) ?? Promise.resolve(true);\n\n const reportStorageAccess = async () => {\n const access = await hasStorageAccess();\n parent.postMessage({ 'resize-iframe-storage': { hasAccess: access } }, targetOrigin);\n return access;\n };\n\n window.parentIframe = {\n // Assign a function to receive what the parent sends with sendMessage().\n get onMessage() {\n return inbound;\n },\n set onMessage(fn) {\n inbound = fn;\n },\n autoResize(state) {\n if (state !== undefined) auto = state;\n if (auto) schedule();\n return auto;\n },\n resize() {\n post(); // nudge, for the rare change neither observer sees\n },\n sendMessage(message, origin = targetOrigin) {\n parent.postMessage({ 'resize-iframe-message': message }, origin);\n },\n hasStorageAccess,\n // MUST be called from a click or tap handler in this page: the browser denies\n // the request without a user gesture here, and the parent cannot supply one.\n // Reload afterwards if your page reads cookies during startup.\n async requestStorageAccess() {\n if (!document.requestStorageAccess) return true;\n try {\n await document.requestStorageAccess();\n } catch {\n // Denied, or the user has never visited this site first-party.\n }\n return reportStorageAccess();\n },\n };\n\n reportStorageAccess();\n post();\n};\n\nif (parent !== window) {\n if (document.body) start();\n else addEventListener('DOMContentLoaded', start);\n}\n";
|
package/inject.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** Source of resize-iframe-child.js, for hosts that want to inject it themselves. */
|
|
2
|
+
export const childScriptSource: string;
|
|
3
|
+
|
|
4
|
+
/** The inline `<script data-resize-iframe-child>…</script>` tag. */
|
|
5
|
+
export function childScriptTag(): string;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Returns HTML that includes the child script exactly once, ready for srcdoc.
|
|
9
|
+
* Fragments are wrapped in a minimal document with a measurable box to sit in.
|
|
10
|
+
*/
|
|
11
|
+
export function withResizeChild(html: string): string;
|
package/inject.js
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Prepare HTML so a parent using resize-iframe can size a srcdoc frame.
|
|
3
|
+
*
|
|
4
|
+
* A cross-origin (or opaque-origin sandboxed) parent cannot measure the framed
|
|
5
|
+
* page. The child script has to run inside it. For HTML the host owns — skill
|
|
6
|
+
* output, quizzes, inlined report fragments — call this before setting srcdoc
|
|
7
|
+
* and the script is inlined, so the report stays self-contained: no CDN, no
|
|
8
|
+
* relative path to the package.
|
|
9
|
+
*
|
|
10
|
+
* Remote `src` pages are not covered: only that page's author can include
|
|
11
|
+
* resize-iframe-child.js.
|
|
12
|
+
*/
|
|
13
|
+
import { childScriptSource } from "./child-source.js";
|
|
14
|
+
|
|
15
|
+
export { childScriptSource };
|
|
16
|
+
|
|
17
|
+
const MARKER = "data-resize-iframe-child";
|
|
18
|
+
|
|
19
|
+
/** The inline script tag `withResizeChild` inserts. */
|
|
20
|
+
export function childScriptTag() {
|
|
21
|
+
// A literal </script> anywhere in the source — even inside a JS comment —
|
|
22
|
+
// closes an HTML <script> element. Break the sequence for the parser; JS
|
|
23
|
+
// still sees <\/script> as </script>.
|
|
24
|
+
const safe = childScriptSource.replace(/<\/(script)/gi, "<\\/$1");
|
|
25
|
+
return `<script ${MARKER}>${safe}</script>`;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Returns HTML that includes the child script exactly once.
|
|
30
|
+
*
|
|
31
|
+
* - Full documents: script inserted before `</body>` (or `</html>`).
|
|
32
|
+
* - Fragments: wrapped in a minimal document so `document.body` exists for
|
|
33
|
+
* the child (it waits on body before measuring).
|
|
34
|
+
* - Idempotent: HTML that already carries the marker is returned unchanged.
|
|
35
|
+
*/
|
|
36
|
+
export function withResizeChild(html) {
|
|
37
|
+
const source = typeof html === "string" ? html : String(html ?? "");
|
|
38
|
+
if (source.includes(MARKER)) return source;
|
|
39
|
+
|
|
40
|
+
const tag = childScriptTag();
|
|
41
|
+
if (/<\/body>/i.test(source)) return source.replace(/<\/body>/i, `${tag}</body>`);
|
|
42
|
+
if (/<\/html>/i.test(source)) return source.replace(/<\/html>/i, `${tag}</html>`);
|
|
43
|
+
// The child measures body's element children, so bare text needs a box around
|
|
44
|
+
// it, and the default body margin would land inside the measured top edge
|
|
45
|
+
// while its counterpart fell outside the bottom one.
|
|
46
|
+
return `<!doctype html><html><head><style>body{margin:0}</style></head><body><div>${source}</div>${tag}</body></html>`;
|
|
47
|
+
}
|
package/package.json
CHANGED
|
@@ -1,15 +1,31 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "resize-iframe",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Iframes that size themselves to their content, cross-origin included. No build step, no dependencies.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "resize-iframe.js",
|
|
7
7
|
"module": "resize-iframe.js",
|
|
8
8
|
"types": "resize-iframe.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./resize-iframe.d.ts",
|
|
12
|
+
"import": "./resize-iframe.js",
|
|
13
|
+
"default": "./resize-iframe.js"
|
|
14
|
+
},
|
|
15
|
+
"./inject": {
|
|
16
|
+
"types": "./inject.d.ts",
|
|
17
|
+
"import": "./inject.js",
|
|
18
|
+
"default": "./inject.js"
|
|
19
|
+
},
|
|
20
|
+
"./resize-iframe-child.js": "./resize-iframe-child.js"
|
|
21
|
+
},
|
|
9
22
|
"files": [
|
|
10
23
|
"resize-iframe.js",
|
|
11
24
|
"resize-iframe-child.js",
|
|
12
25
|
"resize-iframe.d.ts",
|
|
26
|
+
"inject.js",
|
|
27
|
+
"inject.d.ts",
|
|
28
|
+
"child-source.js",
|
|
13
29
|
"CHANGELOG.md"
|
|
14
30
|
],
|
|
15
31
|
"publishConfig": {
|
|
@@ -41,9 +57,11 @@
|
|
|
41
57
|
"typescript": "6.0.3"
|
|
42
58
|
},
|
|
43
59
|
"scripts": {
|
|
60
|
+
"embed-child": "node scripts/embed-child.mjs",
|
|
61
|
+
"pretest": "pnpm embed-child",
|
|
44
62
|
"test": "playwright test",
|
|
45
63
|
"test:ui": "playwright test --ui",
|
|
46
|
-
"typecheck": "tsc --noEmit --strict --lib es2022,dom resize-iframe.d.ts",
|
|
64
|
+
"typecheck": "tsc --noEmit --strict --lib es2022,dom resize-iframe.d.ts inject.d.ts",
|
|
47
65
|
"changeset": "changeset",
|
|
48
66
|
"version-packages": "changeset version",
|
|
49
67
|
"release": "changeset publish"
|
package/resize-iframe-child.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
// Load this inside the framed page
|
|
2
|
-
// <script src=".../resize-iframe-child.js"
|
|
1
|
+
// Load this inside the framed page as a classic script, e.g.
|
|
2
|
+
// <script src=".../resize-iframe-child.js"><\/script>
|
|
3
|
+
// (Written with an escaped closer so the file can also be inlined into a
|
|
4
|
+
// parent <script> without the HTML parser cutting the tag short.)
|
|
3
5
|
// Optional attributes on that script tag:
|
|
4
6
|
// data-parent-origin="https://parent.example" report size only to that embedder
|
|
5
7
|
// data-size-selector=".content" measure these elements instead
|
|
@@ -12,13 +14,14 @@ const start = () => {
|
|
|
12
14
|
let last = { height: 0, width: 0 };
|
|
13
15
|
let queued = false;
|
|
14
16
|
let auto = true;
|
|
17
|
+
let inbound = null;
|
|
15
18
|
|
|
16
19
|
// Measure the bottom and right edges of the marked elements, or of body's
|
|
17
20
|
// children. Never body itself: it stretches to fill the frame in quirks mode
|
|
18
21
|
// or under `body { height: 100% }`, which ratchets the frame larger and never
|
|
19
22
|
// lets it shrink back.
|
|
20
|
-
//
|
|
21
|
-
// if that gap matters, or
|
|
23
|
+
// Body's own bottom/right padding and margin fall outside the measurement —
|
|
24
|
+
// zero them if that gap matters, or mark a wrapper with data-iframe-size.
|
|
22
25
|
const measure = () => {
|
|
23
26
|
const marked = document.querySelectorAll(sizeSelector);
|
|
24
27
|
const elements = marked.length ? marked : document.body.children;
|
|
@@ -64,6 +67,18 @@ const start = () => {
|
|
|
64
67
|
});
|
|
65
68
|
addEventListener('load', schedule); // images and fonts landing late
|
|
66
69
|
|
|
70
|
+
// Messages from the embedder. event.source is set by the browser and cannot be
|
|
71
|
+
// forged, so this is the same guard the parent uses in the other direction, and
|
|
72
|
+
// the envelope keeps unrelated postMessage traffic (analytics, wallets, other
|
|
73
|
+
// embeds) out of the callback.
|
|
74
|
+
addEventListener('message', (event) => {
|
|
75
|
+
if (event.source !== parent) return;
|
|
76
|
+
const data = event.data;
|
|
77
|
+
if (data && typeof data === 'object' && 'resize-iframe-message' in data) {
|
|
78
|
+
inbound?.(data['resize-iframe-message']);
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
|
|
67
82
|
// Third-party cookies: an embedded page gets partitioned storage until the user
|
|
68
83
|
// grants access. Probe the API rather than sniffing the browser — Safari and
|
|
69
84
|
// Chrome both partition, and which browsers do is not a stable list.
|
|
@@ -77,6 +92,13 @@ const start = () => {
|
|
|
77
92
|
};
|
|
78
93
|
|
|
79
94
|
window.parentIframe = {
|
|
95
|
+
// Assign a function to receive what the parent sends with sendMessage().
|
|
96
|
+
get onMessage() {
|
|
97
|
+
return inbound;
|
|
98
|
+
},
|
|
99
|
+
set onMessage(fn) {
|
|
100
|
+
inbound = fn;
|
|
101
|
+
},
|
|
80
102
|
autoResize(state) {
|
|
81
103
|
if (state !== undefined) auto = state;
|
|
82
104
|
if (auto) schedule();
|
package/resize-iframe.d.ts
CHANGED
|
@@ -32,6 +32,8 @@ export function iframeResize(
|
|
|
32
32
|
|
|
33
33
|
/** Available inside the framed page once resize-iframe-child.js has loaded. */
|
|
34
34
|
export interface ParentIframe {
|
|
35
|
+
/** Called with whatever the parent sends via `sendMessage`. Assign to subscribe. */
|
|
36
|
+
onMessage: ((message: unknown) => void) | null;
|
|
35
37
|
autoResize(state?: boolean): boolean;
|
|
36
38
|
resize(): void;
|
|
37
39
|
sendMessage(message: unknown, targetOrigin?: string): void;
|
|
@@ -43,11 +45,15 @@ export interface ParentIframe {
|
|
|
43
45
|
/** Attributes of the <resize-iframe> element. */
|
|
44
46
|
export interface ResizeIframeAttributes {
|
|
45
47
|
src?: string;
|
|
48
|
+
/** Inline HTML. Prefer `withResizeChild(html)` from `resize-iframe/inject` so the frame can report its size. */
|
|
49
|
+
srcdoc?: string;
|
|
46
50
|
title?: string;
|
|
47
51
|
direction?: 'vertical' | 'horizontal' | 'both' | 'none';
|
|
48
52
|
'offset-size'?: number | string;
|
|
49
53
|
'min-h'?: string;
|
|
50
54
|
'max-h'?: string;
|
|
55
|
+
/** Ms to wait before warning that the child script is missing. `0` silences. */
|
|
56
|
+
'warning-timeout'?: number | string;
|
|
51
57
|
allow?: string;
|
|
52
58
|
sandbox?: string;
|
|
53
59
|
/** Whatever else the host framework puts on an element: class, ref, key. */
|
package/resize-iframe.js
CHANGED
|
@@ -88,8 +88,10 @@ function connect(iframe, settings) {
|
|
|
88
88
|
clearTimeout(warning);
|
|
89
89
|
delete iframe.iframeResizer;
|
|
90
90
|
},
|
|
91
|
+
// Same envelope the child sends back in, so the framed page can tell an
|
|
92
|
+
// embedder message from every other script posting at it.
|
|
91
93
|
sendMessage(message, targetOrigin = '*') {
|
|
92
|
-
iframe.contentWindow?.postMessage(message, targetOrigin);
|
|
94
|
+
iframe.contentWindow?.postMessage({ 'resize-iframe-message': message }, targetOrigin);
|
|
93
95
|
},
|
|
94
96
|
};
|
|
95
97
|
return iframe.iframeResizer;
|
|
@@ -97,7 +99,9 @@ function connect(iframe, settings) {
|
|
|
97
99
|
|
|
98
100
|
class ResizeIframe extends HTMLElement {
|
|
99
101
|
static get observedAttributes() {
|
|
100
|
-
|
|
102
|
+
// src / srcdoc last: setting either starts the navigation, and sandbox/allow
|
|
103
|
+
// have to be on the element before that or the first load runs without them.
|
|
104
|
+
return ['min-h', 'max-h', 'allow', 'sandbox', 'warning-timeout', 'srcdoc', 'src'];
|
|
101
105
|
}
|
|
102
106
|
|
|
103
107
|
connectedCallback() {
|
|
@@ -115,10 +119,14 @@ class ResizeIframe extends HTMLElement {
|
|
|
115
119
|
this.attributeChangedCallback(name, null, this.getAttribute(name));
|
|
116
120
|
}
|
|
117
121
|
}
|
|
122
|
+
const warningTimeoutAttr = this.getAttribute('warning-timeout');
|
|
118
123
|
iframeResize(
|
|
119
124
|
{
|
|
120
125
|
direction: this.getAttribute('direction') || DEFAULTS.direction,
|
|
121
126
|
offsetSize: Number(this.getAttribute('offset-size')) || 0,
|
|
127
|
+
...(warningTimeoutAttr !== null
|
|
128
|
+
? { warningTimeout: Number(warningTimeoutAttr) }
|
|
129
|
+
: {}),
|
|
122
130
|
},
|
|
123
131
|
this.iframe
|
|
124
132
|
);
|
|
@@ -130,6 +138,8 @@ class ResizeIframe extends HTMLElement {
|
|
|
130
138
|
|
|
131
139
|
attributeChangedCallback(name, oldValue, newValue) {
|
|
132
140
|
if (!this.iframe || newValue === null) return;
|
|
141
|
+
// srcdoc wins over src when both are set (HTML behaviour). Prefer one.
|
|
142
|
+
if (name === 'srcdoc') this.iframe.srcdoc = newValue;
|
|
133
143
|
if (name === 'src') this.iframe.src = newValue;
|
|
134
144
|
if (name === 'min-h') this.iframe.style.minHeight = newValue;
|
|
135
145
|
if (name === 'max-h') this.iframe.style.maxHeight = newValue;
|
|
@@ -137,6 +147,7 @@ class ResizeIframe extends HTMLElement {
|
|
|
137
147
|
// denied outright unless the frame carries allow="storage-access", and a
|
|
138
148
|
// sandboxed frame also needs allow-storage-access-by-user-activation.
|
|
139
149
|
if (name === 'allow' || name === 'sandbox') this.iframe.setAttribute(name, newValue);
|
|
150
|
+
// warning-timeout is read once, in connectedCallback.
|
|
140
151
|
}
|
|
141
152
|
|
|
142
153
|
get height() {
|