Build a supplier attachment viewer for case 62. The host page owns the case identity, authorization, and selected attachment state. A separate-origin frame receives only a short-lived reference and the capabilities needed to display the attachment: script execution and its own origin. The real origin lets the host target and check exact origins in the ready handshake; an opaque-origin sandbox would not. The frame cannot redirect the host, open arbitrary windows, or access device features. A plain server-rendered preview remains available when the frame does not load or cannot be operated. A ready handshake checks the exact origin and current frame window before establishing a task-scoped MessageChannel. Every selection message has a protocol version, task ID, request ID, bounded payload, and attachment ID that the host checks against the active case. The server checks object permission before a durable selection. Closing or replacing the viewer closes ports, clears timers, and invalidates outstanding requests. A supplier confirmation popup may return a hint, but only a server status read settles that handoff. The same task has a redirect path when the popup is blocked. Keyboard focus returns to the initiating control after the viewer closes, and the host never treats a frame load event as proof that the viewer is ready.
Project: embedded attachment viewer contract
Build contract
- Reject a message from the right origin but the wrong window.
- Reject a replay after case or request ID changes.
- Close every port and timer when the frame reloads or the account changes.
- Complete the task through a preview and redirect when frames or popups fail.
Implementation checkpoint
function acceptSelection(activeCase, message) {
return message.version === 3 && message.caseId === activeCase.id &&
message.requestId === activeCase.pendingRequest &&
activeCase.allowedAttachmentIds.has(message.attachmentId);
}
console.log(acceptSelection({ id: 62, pendingRequest: 'r47', allowedAttachmentIds: new Set([29]) }, { version: 3, caseId: 62, requestId: 'r46', attachmentId: 29 }));
// Output: falseCost and boundaries
A frame adds a document, requests, layout work, script memory, and potentially isolated storage. Bound concurrent viewers and measure a realistic large attachment. A channel adds ports, handlers, pending promises, and progress traffic; close them on every exit path. Schema checks and correlation lookups are O(1) for the bounded protocol, but decoding a large message is proportional to payload size. Limit bytes and nesting before application logic. A server preview costs a second rendering path yet preserves task access during frame failure. Keep logs to rejection categories and counts rather than private attachment payloads.
Failure drill
Open case 62, start task 29, and then switch to case 47 before the viewer responds. Deliver the old response and prove no selection changes. Send a valid-looking message from another frame, then from the expected frame with the wrong origin; both must fail. Reload the viewer midway through channel transfer and confirm the old port cannot update the host. Block the popup and finish through the redirect. Let the frame return a load event without a ready handshake, then use the preview. Attempt a top-level redirect from the frame. Repeat the workflow by keyboard and screen reader, including focus restoration.
Acceptance checks
- Origin, source, version, request, and server permission are separate checks.
- A frame or popup failure preserves the task path.
- Ports, timers, and references are released on case and account changes.
- A return hint cannot mark a supplier confirmation complete.
Common Mistakes
- Accepting an origin match while ignoring the current source window.
- Keeping a channel alive after its frame navigates.
- Replacing the host's server authorization with a message token.
Related lessons
Frame Sandbox, Capabilities, and Fallback; Cross-Window Messages: Origin, Source, Schema, and Replay; MessageChannel Lifetime and Request Correlation; Popup Handoff, Return, and Window Ownership.
