-
Notifications
You must be signed in to change notification settings - Fork 21
standalone doenetml in iframes can initialize in sequence #880
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
dqnykamp
wants to merge
9
commits into
Doenet:main
Choose a base branch
from
dqnykamp:serialize-standalone-initiation
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
7a75187
standalone doenetml in iframes can initialize in sequence
dqnykamp 2f173cc
add cypress e2e tests of coordinated iframe loading
dqnykamp d934234
delete testing description now that we have testing in the CI
dqnykamp 6494d71
make sure initial visibility is stored
dqnykamp d6f1c9a
verify event data is object
dqnykamp c8c1ac9
add wait parameters and their documentation
dqnykamp 979b83b
rename to childOrigin
dqnykamp 07e7b9c
add visibilityRootMargin parameter
dqnykamp d7f8f20
address more review comments
dqnykamp File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,173 @@ | ||
| # Cross-Iframe Coordination | ||
|
|
||
| When loading multiple DoenetML documents in separate iframes, the parent window can coordinate initialization to prevent performance issues caused by simultaneous rendering. | ||
|
|
||
| ## Quick Start | ||
|
|
||
| ### Parent Page (with iframes) | ||
|
|
||
| ```html | ||
| <!doctype html> | ||
| <html> | ||
| <head> | ||
| <script src="doenet-standalone.js"></script> | ||
| </head> | ||
| <body> | ||
| <h1>DoenetML Documents</h1> | ||
|
|
||
| <script> | ||
| // Initialize parent coordinator to serialize iframe initialization | ||
| initializeDoenetParentCoordinator({ | ||
| strategy: "viewport-first", | ||
| timeoutMs: 30000 | ||
| }); | ||
| </script> | ||
|
|
||
| <iframe src="doc1.html"></iframe> | ||
| <iframe src="doc2.html"></iframe> | ||
| <iframe src="doc3.html"></iframe> | ||
| </body> | ||
| </html> | ||
| ``` | ||
|
|
||
| ### Child Page (each iframe) | ||
|
|
||
| ```html | ||
| <!doctype html> | ||
| <html> | ||
| <head> | ||
| <script src="doenet-standalone.js"></script> | ||
| </head> | ||
| <body> | ||
| <div class="doenetml-viewer" data-doenet-enable-parent-coordination="true"> | ||
| <script type="text/doenetml"> | ||
| <p>My DoenetML content</p> | ||
| <graph> | ||
| <point>(2,3)</point> | ||
| </graph> | ||
| </script> | ||
| </div> | ||
|
|
||
| <script> | ||
| document.addEventListener('DOMContentLoaded', () => { | ||
| document.querySelectorAll('.doenetml-viewer').forEach((container) => { | ||
| renderDoenetViewerToContainer(container); | ||
| }); | ||
| }); | ||
| </script> | ||
| </body> | ||
| </html> | ||
| ``` | ||
|
|
||
| ## Strategies | ||
|
|
||
| ### dom-order (default) | ||
|
|
||
| Initializes iframes in DOM order (1st iframe first, then 2nd, then 3rd, etc.). | ||
|
|
||
| ```javascript | ||
| initializeDoenetParentCoordinator({ | ||
| strategy: "dom-order" | ||
| }); | ||
| ``` | ||
|
|
||
| ### viewport-first | ||
|
|
||
| Prioritizes visible iframes, initializing those in viewport first (sorted by DOM order), then initializes remaining iframes in DOM order. | ||
|
|
||
| ```javascript | ||
| initializeDoenetParentCoordinator({ | ||
| strategy: "viewport-first" | ||
| }); | ||
| ``` | ||
|
|
||
| Perfect for pages where users may scroll to see content - visible content initializes first for a responsive feel. | ||
|
|
||
| **Note**: Visibility is detected using an IntersectionObserver with a rootMargin | ||
| (default: 600px). Iframes are considered "visible" when they're within that margin | ||
| of the viewport edges. This is configurable via `visibilityRootMargin`. | ||
|
|
||
| ## Script Placement | ||
|
|
||
| The coordinator should be initialized before or immediately after creating iframes. | ||
| This ensures the parent is listening for `DOENET_REGISTER` messages when child frames load. | ||
|
|
||
| ## How It Works | ||
|
|
||
| 1. **Child Registration**: When a child iframe with `data-doenet-enable-parent-coordination="true"` loads, it registers with the parent after `registrationDelayMs` (default: 100ms). Visibility changes are reported separately. | ||
|
|
||
| 2. **Initial Wait**: The parent waits `initialWaitMs` (default: 300ms) to collect registrations from all iframes. This ensures: | ||
| - All DOM positions are captured | ||
| - Selection logic can make informed decisions | ||
|
|
||
| 3. **Selective Permission**: The parent grants initialization permission to one iframe at a time based on strategy: | ||
| - **dom-order**: Next iframe in DOM order | ||
| - **viewport-first**: Visible iframe (by DOM order), or if none visible, next in DOM order | ||
|
|
||
| 4. **Serialized Rendering**: Each iframe waits for permission before rendering. Only one iframe renders at a time. | ||
|
|
||
| 5. **Continued Selection**: When an iframe completes, the parent selects and grants permission to the next iframe. | ||
|
|
||
| ## Configuration Options | ||
|
|
||
| ```javascript | ||
| // Parent coordinator options | ||
| initializeDoenetParentCoordinator({ | ||
| // Initialization strategy (default: "dom-order") | ||
| strategy: "dom-order" | "viewport-first", | ||
|
|
||
| // Maximum time to wait for iframe to complete initialization (default: 30000ms) | ||
| // If exceeded, automatically proceeds to next iframe | ||
| timeoutMs: 30000, | ||
|
|
||
| // Time to wait for all iframes to register before granting (default: 300ms) | ||
| // Should be substantially larger (2-3x) than child registrationDelayMs | ||
| initialWaitMs: 300 | ||
| }); | ||
|
|
||
| // Child registration options (per iframe) | ||
| // The source argument is optional; when omitted/undefined the DoenetML is read | ||
| // from a <script type="text/doenetml"> child in the container. | ||
| renderDoenetViewerToContainer(container, source, { | ||
| // Enable coordination (default: false) | ||
| enableParentCoordination: true, | ||
|
|
||
| // Delay before registering with parent (default: 100ms) | ||
| // Must be substantially smaller than parent's initialWaitMs | ||
| registrationDelayMs: 150, | ||
|
|
||
| // IntersectionObserver rootMargin for visibility detection (default: "600px") | ||
| // Larger values treat near-viewport iframes as visible sooner | ||
| visibilityRootMargin: "400px", | ||
|
|
||
| // ...other DoenetViewer options | ||
| }); | ||
| ``` | ||
|
|
||
| These options can also be set via `data-doenet` attributes on the iframe child container: | ||
|
|
||
| ```html | ||
| <div | ||
| class="doenetml-viewer" | ||
| data-doenet-enable-parent-coordination="true" | ||
| data-doenet-registration-delay-ms="150" | ||
| data-doenet-visibility-root-margin="400px" | ||
| ></div> | ||
| ``` | ||
|
|
||
| ## Edge Cases | ||
|
|
||
| - **Unresponsive Iframe**: If an iframe fails to complete initialization, the parent waits up to `timeoutMs` before proceeding to the next iframe. | ||
| - **Rapid Registration**: All registrations during the initial wait window are captured and processed fairly. | ||
| - **Late Registrations**: Iframes that register after the initial wait window will be queued and processed after currently-active iframe completes. | ||
| - **Visibility Changes**: When using viewport-first, visibility changes after registration are tracked and can cause re-prioritization of queued iframes. | ||
|
|
||
| ## Backward Compatibility | ||
|
|
||
| - Pages without a coordinator work as before (immediate initialization) | ||
| - The `enableParentCoordination` flag is optional and defaults to false | ||
| - Pages with coordinator work whether or not child pages enable the flag; unflagged pages just initialize immediately | ||
|
|
||
| ## Console Logging | ||
|
|
||
| No debug logs are emitted by default. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| <!doctype html> | ||
| <html lang="en"> | ||
| <head> | ||
| <meta charset="UTF-8" /> | ||
| <link rel="shortcut icon" href="/favicon.ico" /> | ||
| <meta name="viewport" content="width=device-width, initial-scale=1.0" /> | ||
| <title>DoenetML Document</title> | ||
| <style> | ||
| body { | ||
| margin: 10px; | ||
| font-family: Arial, sans-serif; | ||
| } | ||
| </style> | ||
| </head> | ||
| <body> | ||
| <div class="doenetml-viewer" data-doenet-enable-parent-coordination="true"> | ||
| <script type="text/doenetml"> | ||
| <p>This is a DoenetML document.</p> | ||
| <graph> | ||
| <point>(2,3)</point> | ||
| <line through="(0,0) (1,1)" /> | ||
| </graph> | ||
| </script> | ||
| </div> | ||
| <script type="module"> | ||
| import { renderDoenetViewerToContainer } from '/src/index.tsx'; | ||
| document.addEventListener('DOMContentLoaded', () => { | ||
| document | ||
| .querySelectorAll(".doenetml-viewer") | ||
| .forEach((container) => { | ||
| renderDoenetViewerToContainer(container); | ||
| }); | ||
| }); | ||
| </script> | ||
| </body> | ||
| </html> |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.