16 KiB
Iframe maintenance map
Package-name rollback must restore the application imports and saved dependency
lock together. The old plugin package exposes a CommonJS namespace with .default
and distinct helper files; installing its tarball under the current tool dependency
key does not preserve the current constructor or /legacy imports. The isolated
build/install/type rehearsal is yarn test:rollback:iframe; see
rollback maintenance. This does not replace
the package's cross-window, playback or device acceptance.
src/index.ts owns the existing class, constructor validation, bound parent
message listener, child injection/execution protocol and public callbacks.
src/requests.ts owns request IDs, pending promises, injection polling and
settlement/cancellation. Its host interface uses only the iframe, registry and
two lifecycle flags; it does not depend on ArtPlayer or import the entry class.
src/connection.ts owns listener acquisition/release and ensures request
cancellation runs even if listener removal throws. src/protocol.ts checks the
selected window peer and packet shape before either public receiver processes it.
src/navigation.ts owns parent document state, source observation and suspended
request snapshots. src/child-session.ts owns the child document marker and its
acknowledged pagehide/pageshow/hashchange notifications. Requests accept an
internal boundary hook rather than importing the connection or navigation modules;
there is no runtime dependency cycle or dependency on a particular core version.
The entry initializes the same seven enumerable, writable fields in the same
order. Internal request ownership lives in a module WeakMap, keeping instance
reflection unchanged. promises remains the same ordinary object, exposing the
historical resove spelling and reject. Do not rename resove or replace this
public object with a Map. The wire envelope remains { type, data, id }, with
numeric IDs and wildcard target origin. Non-error responses with a matching ID
are still accepted; message receives { type, data } with the instance as this.
Request lifecycle
Every call owns its promise immediately. Before injection it polls every 200ms, as before; injection does not flush the queue synchronously. On send it allocates a numeric ID from the greater of the current timestamp and the previous ID + 1. The counter is shared by instances in this module so two instances pointing at one child do not settle each other's requests. It also avoids reuse after clock rollback. Independently loaded copies of the library do not share this counter.
The request is recorded before native postMessage. Success, remote error, native
send failure or an explicit public callback settle once and remove ownership,
timer and registry entry. Native failures retain their original Error object,
including failures reached from polling. destroy() first marks the instance
destroyed, removes its listener, then immediately rejects both sent and waiting
requests with The instance has been destroyed. Saved listener/timer callbacks
become inert. Repeated destroy is harmless. There is no implicit request timeout.
If constructor setup fails, it marks the partial instance destroyed, releases
the acquired listener and cancels reentrant requests, preserving the original
setup error even if cleanup also fails. The listener receiver and owning window
are captured on acquisition; connection ownership is removed before releasing it.
These are deliberate defect corrections: callers holding a promise during
destroy must handle rejection; previously sent requests remained pending forever.
The public ID is a correlation number, and is no longer always equal to Date.now.
Public callback invocation now removes its completed registry entry immediately.
The own-property guard prevents inherited names such as toString being treated
as requests. These differences are recorded under PKG-IFRAME-03.
Internal cancellation holds the native rejection separately from public callback
properties. Replacing/deleting a public entry does not make its owned Promise
unreachable during destroy or document departure; ordinary response dispatch
continues to respect the public callbacks.
Document ownership
New inject packets advertise optional __artplayerIframe metadata. A cooperating
parent acknowledges it through tagged artplayer-tool-iframe:session traffic;
the public message callback still receives only its original type/data packet.
Ordinary child messages stay unmarked when the parent has not acknowledged the
extension. The document ID correlates work and is not an authentication token.
See ADR-026 for the complete wire boundary.
Parent source attribute changes begin a provisional navigation: capture the old pending set and pause new sends. Confirmed pagehide or a different document's inject cancels the captured set, preserving work queued for the new document. A hashchange report matching the actual target src completes a same-document transition without cancellation. This also handles relative/absolute src spelling and setting an old fragment after the child changed its own hash. Raw attribute tracking prevents a changed base URL resolution alone from inventing navigation.
Repeated inject for the active document keeps requests; an old inject cannot reactivate a document while its replacement is pending. Marked replies and notifications from other documents are discarded; marked old commands are not executed by a new child. No load-event reset is used, so inject-before-load works. Connection cleanup disposes the observer and request hook, then cancels all owned requests. Child listeners belong to the child document, are installed once, and are rolled back on partial acquisition failure. A destroyed parent ignores late private notifications. The existing API adds no static child destroy method.
Compatibility still under review
Child commit still extracts the function body and evaluates it with the old
resolve(...) convention. Simple commits post a response synchronously inside
the async handler; resolver commits await the result. Execution errors still
send an error packet and reject the handler. Expression arrow functions, closure
capture and CSP restrictions have not been replaced with a new RPC protocol.
Both receivers ignore malformed payloads (a non-null object with a string type
is required, including an empty string). The parent accepts native messages only
from the configured iframe's contentWindow; the child accepts them only from
window.parent. Public direct onMessage calls with a missing/null source remain
available to local callers. Same-page script or privileged injection is not
isolated by this check. Generic response types and commit payload error handling
are unchanged after the peer/envelope check.
The initial URL's origin is deliberately not pinned: redirects and sandboxed opaque-origin children still communicate with their selected parent. The peer's content and the embedding parent must be trusted; executable commit is not a sandbox or a parent-origin allowlist. Upgrading one side does not secure the unchanged historical receiver on the other side. See the independent decision in iframe-message-boundary.md.
Legacy peers keep the ordinary envelope and support observed changes to different source addresses; their same-address reloads/internal navigation cannot provide document identity without upgrading the child. No claim is made that a normal history reload is BFCache: actual persisted restoration, full-player integration, devices and externally interrupted navigation remain PKG-IFRAME-05/release gates. IFRAME-LIFE-01 and IFRAME-TRUST-01 remain open for those integration/review scopes.
Public declarations are maintained in types/artplayer-tool-iframe.d.ts.
The .d.cts bridge describes the actual CommonJS constructor and its named types;
.d.mts routes native ESM to the same class. Keep the root types field and
legacy typesVersions entry for TS 4.3 consumers. The runtime has no self-default
property. Ordinary default imports use interop in CommonJS consumers; modern
TypeScript can use direct import Iframe = require(...) without interop.
The default class intentionally retains required Message.data, void static onMessage, readonly instance fields, non-null callback and legacy commit inference. RuntimeConstructor/RuntimeInstance expose optional outgoing data, async static receiver, nullable callback and application-supplied response types. ResolverInstance offers an explicit result parameter for the old serialized resolve(...) protocol. These are erased type views, not new runtime methods or payload validators. The source class satisfies RuntimeConstructor; its nullable callback intentionally does not satisfy the old non-null class declaration, and tests pin that difference.
Old npm plugin-iframe used Function callbacks and a CommonJS default namespace; those differ from the frozen workspace before this refactor. Installed tests preserve that evidence and the separate helper's static destroy protocol. They do not claim the renamed tool supplies the old package name or helper entrypoints. PKG-IFRAME-06 still owns those distribution decisions and compatibility facades. No version bump or publication is implied by declaration validation.
Verification
yarn test:iframe: frozen historical contract/defect assertions and candidate lifecycle assertions. Historical failures are not candidate acceptance.yarn typecheck: strict source and existing consumers, including old extraction and new typed views in TS 5.9 and TS 4.3.yarn test:iframe-types-package: install the real old npm archive, frozen workspace pack and candidate outside the workspace with offline/frozen Yarn. Check declaration resolution, exact bytes, exports and positive/negative uses.yarn build:ts artplayer-tool-iframe: regenerate only this package's standalone editor declaration (plus the shared core declaration). The generator uses the actual uppercase class global and exports named types without external imports.yarn build artplayer-tool-iframe: normal main/legacy/ESM production output and generated docs copies. Never hand-edit those files.yarn test:browser test/browser/iframe.spec.js: real same/cross-origin windows in Chromium, Firefox and WebKit. This tool-only fixture creates no player.yarn test:browser test/browser/iframe-boundaries.spec.js: native peer rejection, malformed packets, actual HTTP redirects, opaque sandbox and normal new/old parent-child wire combinations.ARTPLAYER_IFRAME_BOUNDARIES_ONLY=1omits the mixed-version controls when reproducing a candidate boundary failure.yarn test:browser test/browser/iframe-navigation.spec.js: source/srcdoc, rapid navigation, self-navigation, load ordering, fragment identity, stale packets, legacy children and history with actual persisted-state reporting. There is no implicit request timeout. Handle rejection when a document leaves or is destroyed.- Set
ARTPLAYER_IFRAME_LIFECYCLE_ONLY=1for candidate lifecycle browser rows;ARTPLAYER_IFRAME_ARTIFACTselects an actual built file. For an unchanged candidate test against the old workspace, setARTPLAYER_IFRAME_BASELINE=1. Expected historical failures must be archived, not suppressed.
The existing local demo remains at
http://localhost:8082/?libs=./uncompiled/artplayer-tool-iframe/index.js&example=iframe.
iframe-player.spec.js runs the real parent example, child HTML and stylesheet
with core 4.5.9, frozen 5.4.0 and the candidate, all four new/old bridge pairings,
and same/cross-origin documents. Historical global-name aliases are test-only.
It exercises actual media playback, seek/rate, source switch, fullscreenWeb
controls and independent core/tool destruction. Hover the player before clicking
controls, as a user does; old cores hide their controls during playback.
iframe-editor.spec.js opens the actual docs index and local Monaco, clicks Run
repeatedly, and switches from an iframe example to two parent players and then
an empty example. The editor loads the core before Monaco's AMD loader so the
UMD core exposes its script global. Before each Run it dispatches the docs-only
artplayer:example:cleanup event, then destroys a snapshot of parent instances.
The iframe example subscribes once to destroy its tool and remove its frame;
its initial commit handles cancellation during cleanup. This is an editor
convention, not automatic DOM-removal behavior or a new player API. Standalone
integrations remain responsible for calling destroy themselves.
yarn test:iframe-history runs the separate cacheable HTTP fixture and full
Chromium channel, removing Playwright's BFCache-disabling default argument. It
checks actual whole-page restoration through native persisted events, parent/child
witnesses, media and the real commit protocol. Firefox/WebKit reload controls are
reported separately and never counted as cached restoration. Do not replace this
configuration with the default headless shell or simulated lifecycle events.
An entire cached page also freezes the parent. If the private leave message did not reach it, the original request can remain pending and settle after restoration. If leave was delivered, that request remains rejected; a late result cannot change its outcome. Resume retains the same document ID and emits no duplicate public inject. The test checks actual delivered phases against settlement.
The same command tests native stop, HTTP 204 and a truncated document response. After a failed navigation, new work still waits for actual injection into the target document; it is not sent to the unrelated old document. Navigate the frame to a valid source or destroy the tool to finish/cancel that work. There is no implicit timeout or automatic fallback to an old source.
Physical devices, native Firefox/WebKit cached restoration and final distribution remain separate gates. Desktop evidence does not establish the old helper or package-name migration. See ADR-026 and the task 05 history checkpoint for the exact fixture, automation limitations and source/main/legacy evidence.
Installed browser checks
yarn test:package --browser includes this tool in the shared installed roster.
The pack checker verifies the real artplayer-plugin-iframe@1.0.0 archive and
the frozen workspace independently. The tool's required dist/type filenames
come from its verified workspace manifest and files; the old plugin/helper
names are not silently treated as releases of the tool. Publishing or restoring
the predecessor package remains a separate distribution decision.
The five test/browser/iframe*.spec.js files use iframeBrowserCandidate() from
test/helpers/iframe.js. With an installed map it must load the verified tool
UMD, reject an explicit artifact or frozen-workspace override, and record the
package/file/archive/source identity for each candidate parent and child.
Without a map the same tests retain their source and historical controls.
iframeCandidate() keeps its existing unit/history semantics: history may
inherit a core-only map, which does not select a tool build. Do not merge these
two fixture entrypoints or claim the separate BFCache suite used an installed
tool when it used a source/explicit tool. Browser scope configuration rejects
diagnostic flags that would remove historical Iframe rows.
The installed tests cover protocol, peer/document boundaries, native navigation,
old/new player combinations and the real docs editor. They do not replace the
special test:iframe-history browser channel or physical-device evidence. See
../../refactor/changes/2026-09-15-CI-01-iframe-installed.md for measured results.