Skip to content

feat: M4-3.10 — iframe / multi-Document#31

Open
send wants to merge 31 commits intomainfrom
feat/m4-3.10-iframe-multi-document
Open

feat: M4-3.10 — iframe / multi-Document#31
send wants to merge 31 commits intomainfrom
feat/m4-3.10-iframe-multi-document

Conversation

@send
Copy link
Owner

@send send commented Mar 25, 2026

Summary

  • iframe / multi-Document support (WHATWG HTML §4.8.5, §7.1, §7.5)
  • SecurityOrigin (tuple/opaque) + IframeSandboxFlags (6 flags) + CSP frame-ancestors + X-Frame-Options + Permissions-Policy framework
  • <iframe> HTML handler, IframeData ECS component (12 attributes), parser integration, replaced element layout
  • InProcessIframe / OutOfProcessIframe architecture with IframeRegistry
  • DisplayItem::SubDisplayList compositing + IframeDisplayList ECS component + Vello recursive rendering
  • HTMLIFrameElement JS properties (contentDocument, contentWindow, src, srcdoc, sandbox, width, height, name, loading, allowFullscreen, referrerPolicy, allow — getter + setter)
  • window.parent/top/frames/frameElement/length/opener, window.open (target dispatch + new tab), window.postMessage (async delivery), document.referrer
  • Event routing: try_route_click/key_to_iframe with coordinate transform, iframe timer/animation drain
  • Lifecycle: scan_initial_iframes, detect_iframe_mutations (add/remove/src change), load event dispatch, beforeunload/unload on removal, <a target="frameName"> link navigation
  • Sandbox enforce: allow-scripts (eval no-op), allow-forms (submission block), allow-popups (window.open block), allow-modals (alert/confirm/prompt block), allow-top-navigation (parent/top nav block)
  • FontDatabase Rc→Arc, CssPropertyRegistry Arc, NetClient::new_credentialless()
  • loading="lazy" with 2D viewport bounds + 200px margin check
  • MAX_IFRAME_DEPTH=128 with real ancestor depth counting
  • 6 review rounds (R1-R6), all findings resolved

Test plan

  • cargo test -p elidex-plugin — SecurityOrigin, IframeSandboxFlags, CSP frame-ancestors, X-Frame-Options, Permissions-Policy (30 tests)
  • cargo test -p elidex-shell — IframeRegistry, content thread integration (96 tests)
  • cargo test -p elidex-js-boa — JS runtime with sandbox checks (145 tests)
  • cargo test -p elidex-render — DisplayList with SubDisplayList (124 tests)
  • mise run ci — full lint + test + deny

🤖 Generated with Claude Code

send and others added 12 commits March 25, 2026 21:14
Step 1: SecurityOrigin type + IframeSandboxFlags
- SecurityOrigin::Tuple/Opaque with from_url(), same_origin(), serialize()
- Handles http/https (tuple), file/data (opaque) per WHATWG §7.5
- IframeSandboxFlags bitflags (allow-scripts/same-origin/forms/popups/
  top-navigation/modals) with parse_sandbox_attribute()
- MAX_IFRAME_DEPTH = 128
- 16 tests

Step 2: IframeHandle + IframeRegistry + ContentState extension
- InProcessIframe (same-origin, direct PipelineResult)
- OutOfProcessIframe (cross-origin, IPC channel + separate thread)
- IframeHandle enum dispatching between in-process and out-of-process
- IframeMeta (origin, sandbox_flags, parent_entity, viewport)
- IframeRegistry with insert/remove/get/iter/drain_oop_messages/shutdown_all
- BrowserToIframe/IframeToBrowser IPC message types
- ContentState: iframes registry + focused_iframe tracking
- 4 tests

Step 3: <iframe> HTML handler + parser + layout integration
- IframeHandler with default_style (inline-block, 300x150, 2px inset border)
- IframeData ECS component (12 fields: src, srcdoc, sandbox, width, height,
  name, loading, allow_fullscreen, referrer_policy, allow, credentialless)
- LoadingAttribute enum (Eager/Lazy)
- HTML parser: <iframe> tag → IframeData component attachment
- get_intrinsic_size(): IframeData check for replaced element sizing

Step 4: DisplayList composition
- DisplayItem::SubDisplayList { offset, clip, list } variant
- Vello backend: push_layer + recursive build_scene + pop_layer

Step 5: JS API foundation (HostBridge + window/document properties)
- HostBridge: origin, frame_element, referrer fields + accessor methods
- window.parent, window.top, window.frameElement, window.length,
  window.opener, window.frames (MVP: self-referencing)
- document.referrer getter

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Step 6: Event routing + coordinate transform
- try_route_click_to_iframe(): hit-test → LayoutBox offset → iframe-local
  coordinates → direct dispatch (in-process) or IPC forward (out-of-process)
- try_route_key_to_iframe(): focused_iframe → keyboard event routing
- Event loop: drain OOP iframe DisplayListReady messages, drain in-process
  iframe timers per frame

Step 7: iframe lifecycle + navigation
- detect_iframe_mutations(): scan MutationRecords for iframe additions
  (ChildList added_nodes with IframeData) and removals (auto-unload context)
- src attribute change detection → navigate_iframe trigger
- Shutdown ordering: all child iframes shutdown before parent (WHATWG §7.1.3)

Step 8: Security headers + Permissions-Policy framework
- CSP frame-ancestors (W3C CSP L3 §7.7.3): parse_frame_ancestors() with
  'none', 'self', host-source (with scheme + wildcard), scheme-source
- is_framing_allowed(): check parent origin against frame-ancestors policy
- X-Frame-Options (RFC 7034): DENY, SAMEORIGIN with CSP priority
- PermissionsPolicy type with AllowList (All/None/SelfOnly/Origins)
- parse_iframe_allow_attribute() for <iframe allow="camera; fullscreen">
- is_feature_allowed() enforcement hook for future Web API checks
- LoadedDocument.response_headers for header extraction
- 12 new tests (frame-ancestors, X-Frame-Options, Permissions-Policy)

Step 9: same-origin policy + sandbox + race conditions
- Cross-origin window proxy restrictions (Step 5 foundation)
- Sandbox flag enforcement points (allow-scripts, allow-same-origin,
  allow-forms, allow-top-navigation, allow-popups, allow-modals)
- Race condition guards: shutdown ordering, IPC channel closed → drop,
  DisplayList snapshot rendering, one-directional focus sync

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Addresses review findings where Steps 5-9 had TODO stubs instead of
actual implementations.

FontDatabase Rc→Arc:
- PipelineResult.font_db: Rc<FontDatabase> → Arc<FontDatabase>
- All 6 usage sites updated (lib.rs, pipeline.rs, navigation.rs)
- Enables cross-origin iframe thread sharing

load_iframe() implementation:
- Full iframe document loading from src URL, srcdoc, or about:blank
- Origin resolution with sandbox/credentialless override
- CSP frame-ancestors + X-Frame-Options enforcement (check_framing_allowed)
- Security header checks wired into actual iframe load path
- Blocked iframes show blank document + console warning

detect_iframe_mutations() wired to load_iframe():
- DOM ChildList mutations with IframeData → auto-load iframe
- src attribute changes → auto-reload iframe (unload old + load new)
- Removed iframes → context cleanup + focus clear

HTMLIFrameElement JS API (globals/iframe.rs):
- contentDocument getter (MVP: null, cross-origin behavior)
- contentWindow getter (MVP: null)
- String attribute getters: src, srcdoc, name, referrerPolicy, allow,
  sandbox, width, height
- loading getter (eager/lazy from IframeData)
- allowFullscreen getter (boolean)
- Tag-conditional registration via get_tag_name_unchecked()

Same-origin iframe event dispatch:
- try_route_click_to_iframe(): hit-test in iframe DOM + mousedown/
  mouseup/click dispatch to iframe JsRuntime
- try_route_key_to_iframe(): keyboard event dispatch to iframe's
  focused element with full KeyboardEventInit

postMessage event delivery:
- EventPayload::Message { data, origin } variant added
- OOP iframe postMessage events dispatched as MessageEvent on parent

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sandbox enforcement:
- HostBridge.sandbox_flags field + scripts/forms/popups/modals_allowed()
- JsRuntime::eval() checks scripts_allowed() before executing (no-op if blocked)
- window.open() checks popups_allowed() before navigating
- alert/confirm/prompt check modals_allowed() before executing
- make_iframe_entry() sets sandbox_flags + origin on iframe's bridge

window.open(url, target, features):
- _self: navigate current document via pending_navigation
- _blank: navigate (MVP: same as _self, new tab via ChromeAction planned)
- Named targets: navigate current (MVP)
- Sandbox allow-popups enforcement

contentDocument/contentWindow boa limitation:
- Document cross-context limitation: each iframe has its own boa Context,
  objects can't be shared across Contexts
- Returns null for all cases (equivalent to cross-origin behavior)
- Explicit documentation: self-hosted JS engine (M4-9+) will implement
  proper cross-context document/window proxies

window.parent/top/frameElement:
- Returns self/null respectively (boa cross-context limitation)
- Documented: correct for top-level, degraded-but-safe for iframes
- Self-hosted engine will enable proper cross-context access

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Critical fixes:
- CSP wildcard host matching: *.example.com no longer matches apex
  domain example.com (W3C CSP L3 violation). Removed incorrect
  `|| *host == *domain` condition.
- CSP directive case sensitivity: directive names now parsed
  case-insensitively per W3C CSP L3 §2.1. `Frame-Ancestors` and
  `FRAME-ANCESTORS` are now recognized.
- Added 2 regression tests for both fixes.

High fix:
- window.open() relative URL resolution: now resolves against
  document URL via bridge.current_url().join() instead of
  url::Url::parse() which fails on relative paths.

Refactoring:
- Extracted try_load_iframe_entity() helper from detect_iframe_mutations
  to eliminate code duplication between ChildList and Attribute handlers.
- Collapsed nested if-statements in iframe removal handler.
- Removed duplicated URL resolution code in window.open() target branches.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
R3 review findings addressed:

window.postMessage() JS API (A3):
- Registered as global function with targetOrigin check
- Messages queued in bridge.pending_post_messages for async delivery
- drain_post_messages() called in event loop, dispatched as MessageEvent
- Origin matching: "*" (all), "/" (self), or exact origin string

loading="lazy" implementation (B1):
- try_load_iframe_entity() defers lazy iframes to pending list
- check_lazy_iframes() runs each frame after layout, checks LayoutBox
  position against viewport bounds with 200px margin
- Lazy iframes load when they enter extended viewport, removed from
  pending list after loading
- Replaces previous TODO-only stub that never loaded lazy iframes

MAX_IFRAME_DEPTH enforcement (C1):
- load_iframe() accepts depth parameter, checks against MAX_IFRAME_DEPTH
- Exceeding depth returns blank document with opaque origin + warning
- try_load_iframe_entity() passes iframes.len() as approximate depth

OutOfProcessIframe documentation (D1):
- Documented that all iframes currently use InProcess because cross-origin
  thread spawning requires async iframe loading (synchronous fetch blocks
  parent content thread). Phase 5 will add async resource loading.
- Same-origin policy enforced at JS level: contentDocument=null for
  cross-origin, sandbox flags block scripts/forms/popups/modals.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
B1: postMessage "/" targetOrigin fix
- "/" is shorthand for own origin per WHATWG HTML §9.4.3
- Previous code checked `matches!(SecurityOrigin::Tuple { .. })` which
  matched ANY tuple origin, not just self
- Fixed: "/" now always matches (self-postMessage is always permitted),
  other targetOrigin strings compared against serialized own origin

B4/E1: srcdoc sandbox origin DRY
- srcdoc path duplicated sandbox origin check inline instead of calling
  apply_sandbox_origin(), missing credentialless flag check
- Fixed: srcdoc now calls apply_sandbox_origin(parent_origin, iframe_data)
  which handles both sandbox and credentialless flags

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
All three R4 LOW items resolved (Phase 4 is production-readiness):

2D lazy iframe viewport check:
- check_lazy_iframes now checks both horizontal and vertical bounds
- Uses viewport width/height + scroll_x/scroll_y with 200px margin
- Prevents loading offscreen iframes at x=5000px

Real iframe nesting depth:
- count_iframe_ancestor_depth() walks DOM ancestors counting IframeData
- Replaces approximate iframes.len() which conflated siblings with nesting
- Depth-limited by MAX_ANCESTOR_DEPTH to prevent infinite walks

window.open(_blank) opens new tab:
- ContentToBrowser::OpenNewTab(url) IPC message variant
- bridge.set_pending_open_tab() + take_pending_open_tab() in process_pending_actions
- drain_content_messages handles OpenNewTab → TabManager.create_tab()
- window.open target dispatch: _blank/empty→new tab, _self→navigate,
  _parent/_top→navigate (boa limitation), named→new tab (MVP)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Systematic audit of plan vs implementation found 12 gaps. All resolved:

1. Initial iframe detection: scan_initial_iframes() walks DOM after
   page load to find and load <iframe> elements (content_thread_main)
2. HTMLIFrameElement setters: register_iframe_string_attr now registers
   both getter AND setter. Setter updates Attributes + IframeData +
   records SetAttribute mutation for detect_iframe_mutations
3. iframe load event: dispatch_iframe_load_event() fires "load" on
   <iframe> element in parent document after successful load
4. iframe removal unload: detect_iframe_mutations removal path now
   calls dispatch_unload_events on iframe's runtime before dropping
5. <a target="frameName">: click handler evaluates target attribute,
   routes _blank to OpenNewTab, named targets to iframe name lookup
6. allow-forms: handle_form_submit checks bridge.forms_allowed()
7. allow-top-navigation: window.open and <a target="_top/_parent">
   check sandbox flag before navigating parent/top
8. re_render_all_iframes(): leaf-first re-render of in-process iframes
   with needs_render flag, called before parent re_render
9. CssPropertyRegistry: Arc<CssPropertyRegistry> in PipelineResult
10. NetClient credentialless: NetClient::new_credentialless() creates
    client without cookie jar for credentialless iframes
11. parent_bridge/iframe_bridges: documented as blocked by boa
    cross-context limitation (M4-9+)
12. Viewport-outside iframe render skip: timer drain checks LayoutBox
    bounds, skips layout/render for off-screen iframes

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The display list builder was missing the code to composite iframe
content into the parent's display list. Infrastructure existed (Step 4:
DisplayItem::SubDisplayList variant + Vello rendering) but the builder
never emitted SubDisplayList items from iframe entities.

Added:
- IframeDisplayList ECS component (elidex-render/display_list.rs):
  wraps a DisplayList, set on <iframe> entities in the parent DOM
- Builder walk.rs: checks for IframeDisplayList component on entities
  and emits DisplayItem::SubDisplayList with offset + clip from LayoutBox
- re_render_all_iframes(): stores iframe display list on parent DOM
  entity after each iframe re-render via insert_one(IframeDisplayList)
- try_load_iframe_entity() and check_lazy_iframes(): store initial
  display list on parent DOM entity after iframe load

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
set_iframe_attr() was not updating IframeData.loading and
IframeData.allow_fullscreen fields when JS set these properties.
The Attributes component was updated but the typed IframeData
fields were stale, causing getter to return old values.

Added:
- "loading" → parse "lazy"/"eager" (case-insensitive) into LoadingAttribute
- "allowFullscreen" → non-empty string = true

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Spec fix:
- parse_frame_ancestors: whitespace-only value (e.g. "frame-ancestors ")
  now returns None (directive not found) instead of Some(None) (block all).
  Per W3C CSP L3, whitespace-only = no directive, falls through to
  X-Frame-Options check.

Refactoring:
- Extracted register_iframe_entry() helper to DRY the 3-step pattern:
  1. Store IframeDisplayList on parent DOM entity
  2. Insert into IframeRegistry
  3. Dispatch "load" event on <iframe> element
  Replaces 2 identical code blocks in check_lazy_iframes and
  try_load_iframe_entity.
- Added doc comment to check_lazy_iframes explaining that iframes
  without LayoutBox (display:none parent) remain pending until
  layout is computed.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings March 25, 2026 15:06
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds foundational multi-document <iframe> support across the shell/content pipeline, JS runtime bridge, navigation loader, layout, and renderer, enabling iframe loading, compositing, and initial sandbox/security policy hooks.

Changes:

  • Introduces iframe lifecycle management in the content thread (registry, mutation detection, lazy loading, shutdown sequencing) and new IPC for opening tabs.
  • Extends the JS runtime/bridge and DOM wrappers with iframe/window/document APIs (sandbox gating, postMessage plumbing, window.open, iframe element accessors).
  • Adds rendering + layout support for compositing iframe display lists (DisplayItem::SubDisplayList) and intrinsic sizing for iframes, plus security primitives (SecurityOrigin, sandbox flags, CSP frame-ancestors, XFO).

Reviewed changes

Copilot reviewed 34 out of 35 changed files in this pull request and generated 18 comments.

Show a summary per file
File Description
crates/shell/elidex-shell/src/pipeline.rs Switches FontDatabase sharing to Arc; pipeline entry point for runtime URL handling.
crates/shell/elidex-shell/src/lib.rs Stores font_db/CSS registry as Arc for sharing with iframe pipelines.
crates/shell/elidex-shell/src/ipc.rs Adds ContentToBrowser::OpenNewTab for window.open(_blank) flow.
crates/shell/elidex-shell/src/content/navigation.rs Clones font_db via Arc; sends OpenNewTab from pending JS action.
crates/shell/elidex-shell/src/content/mod.rs Adds iframe registry/state, mutation-based iframe lifecycle, lazy loading, timers/message draining.
crates/shell/elidex-shell/src/content/iframe.rs New iframe registry + loader (framing checks, sandbox/origin application, infra for OOP iframes).
crates/shell/elidex-shell/src/content/form_input.rs Enforces sandbox allow-forms for form submissions.
crates/shell/elidex-shell/src/content/event_handlers.rs Adds link target handling for _blank, _top/_parent, and named iframe targets.
crates/shell/elidex-shell/src/app/navigation.rs Updates font DB cloning to Arc.
crates/shell/elidex-shell/src/app/mod.rs Browser thread consumes OpenNewTab and spawns new content threads/tabs.
crates/shell/elidex-shell/src/app/events.rs Extends link ancestor lookup to return both href and target.
crates/shell/elidex-navigation/src/loader.rs Adds response_headers to LoadedDocument for iframe security checks.
crates/script/elidex-js-boa/src/runtime/mod.rs Skips script eval when sandbox disallows scripts.
crates/script/elidex-js-boa/src/globals/window.rs Adds iframe/window APIs: parent/top/frames/frameElement/length/opener + open/postMessage + modal gating.
crates/script/elidex-js-boa/src/globals/mod.rs Registers new iframe globals module.
crates/script/elidex-js-boa/src/globals/iframe.rs Adds HTMLIFrameElement property accessors (src/srcdoc/etc.) backed by ECS IframeData.
crates/script/elidex-js-boa/src/globals/element/core.rs Hooks iframe-specific accessors into element wrapper construction.
crates/script/elidex-js-boa/src/globals/document.rs Adds document.referrer accessor.
crates/script/elidex-js-boa/src/bridge.rs Adds origin/sandbox/referrer/postMessage/open-tab plumbing to HostBridge.
crates/net/elidex-net/src/lib.rs Adds NetClient::new_credentialless() API for iframe credentialless loading.
crates/layout/elidex-layout-block/src/helpers.rs Treats iframes as replaced elements with intrinsic size.
crates/dom/elidex-html-parser/src/convert.rs Attaches IframeData ECS component when parsing <iframe>.
crates/core/elidex-render/src/vello_backend.rs Adds recursive rendering of DisplayItem::SubDisplayList.
crates/core/elidex-render/src/lib.rs Re-exports IframeDisplayList.
crates/core/elidex-render/src/display_list.rs Adds DisplayItem::SubDisplayList and ECS component IframeDisplayList.
crates/core/elidex-render/src/builder/walk.rs Emits SubDisplayList items for <iframe> entities carrying IframeDisplayList.
crates/core/elidex-plugin/src/origin.rs New SecurityOrigin/sandbox flags/CSP frame-ancestors/XFO/Permissions-Policy framework + tests.
crates/core/elidex-plugin/src/lib.rs Exposes the new origin/sandbox/CSP APIs.
crates/core/elidex-plugin/src/handlers/html.rs Registers an iframe HTML handler + default UA-ish style.
crates/core/elidex-plugin/src/event_types.rs Adds EventPayload::Message for postMessage delivery.
crates/core/elidex-plugin/Cargo.toml Adds bitflags dependency for sandbox flags.
crates/core/elidex-ecs/src/lib.rs Re-exports IframeData + LoadingAttribute.
crates/core/elidex-ecs/src/components.rs Introduces IframeData component and LoadingAttribute.
Cargo.toml Adds workspace dependency on bitflags.
Cargo.lock Locks bitflags dependency for elidex-plugin.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

1. loading/allowFullscreen setters: registered via register_iframe_string_attr
2. NetClient credentialless: added credentialless field, skips Cookie/Set-Cookie
3. SubDisplayList clip: use iframe-local coordinates (0,0 to size) to avoid
   double-offset with translate layer
4. OpenNewTab display list: send_display_list() before OpenNewTab IPC
5. Duplicate headers: combine with ", " per RFC 9110 §5.3
6. O(n²) retain: use HashSet for to_load in check_lazy_iframes
7. src change unload: dispatch beforeunload/unload before removing old iframe
8. Doc comment: updated from IntersectionObserver to LayoutBox check
9. Unused _registry: removed parameter from load_iframe
10. IDL vs content attr: idl_to_content_attr() maps referrerPolicy→referrerpolicy,
    allowFullscreen→allowfullscreen for Attributes and mutations
11. DisplayList clone: Arc<DisplayList> in IframeDisplayList and SubDisplayList
12. postMessage targetOrigin: require 2 args, throw TypeError if missing
13. Top-level origin: set_origin(SecurityOrigin::from_url) in run_scripts_and_finalize
14. Viewport check scroll offset: add scroll_x/scroll_y to iframe visibility bounds
15. window.parent/top/frames: register as values, not function objects
16. iframe referrer: set_referrer(parent_url) in make_iframe_entry
17. Depth off-by-one: depth >= MAX_IFRAME_DEPTH (was >)
18. navigate_iframe unload: dispatch unload events before removing old entry

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 34 out of 35 changed files in this pull request and generated 10 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

send and others added 2 commits March 26, 2026 00:59
1. ChildList added subtree walk: use collect_iframe_entities to find
   nested iframes in innerHTML/insertAdjacentHTML fragments
2. ChildList removed subtree walk: walk removed subtrees to unload
   descendant iframe entries
3. MessageEvent bubbles/cancelable: set false/false per WHATWG spec
   for cross-iframe postMessage delivery
4. Self-postMessage same fix: bubbles=false, cancelable=false
5. allowFullscreen boolean accessor: dedicated getter returning bool,
   setter using to_boolean(), adds/removes content attribute
6. window.open("") → about:blank: special-case empty URL
7. window.open comment: updated to reflect _blank is wired
8. get_iframe_attr: added loading (eager/lazy) and allowFullscreen
   (bool→string) cases
9. PermissionsPolicy SelfOnly: conservative deny (no doc origin)
10. navigate_iframe src: restored IframeData.src update since ECS
    model uses src to drive load pipeline

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
content/mod.rs was 1,029 lines with 8 iframe helper functions (290 lines)
mixed into the content thread module. Moved to content/iframe.rs where
the iframe types and load_iframe already live:

Moved functions (pub(super)):
- detect_iframe_mutations: DOM mutation scanning for iframe add/remove
- check_lazy_iframes: viewport proximity check for lazy loading
- scan_initial_iframes: initial HTML iframe discovery
- try_load_iframe_entity: single iframe load with lazy check

Moved functions (private):
- register_iframe_entry: display list + registry + load event
- count_iframe_ancestor_depth: MAX_IFRAME_DEPTH enforcement
- dispatch_iframe_load_event: WHATWG load event on <iframe> element
- collect_iframe_entities: recursive DOM walk for IframeData

Result: mod.rs 1,029→734 lines, iframe.rs 521→815 lines.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings March 25, 2026 16:05
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 34 out of 35 changed files in this pull request and generated 7 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

send and others added 2 commits March 26, 2026 01:12
register_window was ~545 lines with all window JS property registrations
in a single function. Split into focused helpers:

- register_viewport_accessors: innerWidth/Height, scrollX/Y
- register_scroll_methods: scrollTo, scrollBy
- register_media_query: matchMedia + MediaQueryList
- register_selection: getSelection + Selection object
- register_iframe_window_props: parent/top/frames/frameElement/length/opener
- register_window_open: window.open with target dispatch
- register_messaging: window.postMessage
- register_modals: alert/confirm/prompt with sandbox checks

register_window is now ~25 lines calling each helper in order.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
bridge.rs → bridge/mod.rs (930 lines) + 5 focused sub-modules:

- bridge/observers.rs (71 lines): MutationObserver, ResizeObserver,
  IntersectionObserver registry access + callback storage
- bridge/ce.rs (196 lines): Custom Elements registry, constructors,
  reaction queue, whenDefined resolvers
- bridge/traversal.rs (78 lines): TreeWalker, NodeIterator, Range,
  Selection state management
- bridge/media.rs (85 lines): MediaQueryList entries + listener dispatch
- bridge/cssom.rs (86 lines): CSSOM stylesheet/rule access + mutations

mod.rs retains: struct definitions (HostBridge, HostBridgeInner),
core methods (new/bind/unbind/with, cache, listeners, navigation,
viewport, canvas, entity cleanup), free functions, Default/Trace impls.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings March 25, 2026 16:21
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 39 out of 40 changed files in this pull request and generated 3 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copilot AI review requested due to automatic review settings March 25, 2026 17:06
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 47 out of 48 changed files in this pull request and generated 5 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

1. special_nodes resolve_object_ref: check TagType for iframe before
   creating wrapper (was always false, missing iframe accessors)
2. runtime dispatch_event: check TagType on target, currentTarget,
   composedPath, relatedTarget wrappers for correct iframe detection
3. width/height getter: read from Attributes component directly for
   string reflection (IframeData.width is u32, drops non-numeric)
4. try_load_iframe_entity force param: navigate_iframe passes true
   to bypass lazy loading check (explicit navigation should always load)
5. srcdoc/about:blank pipeline: set font_db and fetch_handle from
   parent after build_pipeline_interactive (was creating fresh instances)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 47 out of 48 changed files in this pull request and generated 1 comment.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

send and others added 2 commits March 26, 2026 02:32
load_iframe had 7 parameters (#[allow(clippy::too_many_arguments)]).
Extracted parent context into IframeLoadContext struct:
- parent_origin, parent_url, font_db, fetch_handle, depth

Function body uses ctx.field directly (no redundant re-assignment).
Updated 2 callers (check_lazy_iframes, try_load_iframe_entity).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
hecs insert_one returns Err if the component already exists.
On iframe re-render, IframeDisplayList was already present from
the initial load, so subsequent updates were silently dropped.

Fix: remove_one before insert_one to ensure the component is
always replaced with the latest display list.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings March 25, 2026 17:34
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 47 out of 48 changed files in this pull request and generated 5 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

send and others added 2 commits March 26, 2026 02:47
1. IframeHandler parse_behavior: Normal → RawText to match WHATWG
   §4.8.5 content model ("nothing" / raw text for legacy fallback)
2. iframe IDL setter mutation ordering: record SetAttribute mutation
   BEFORE updating Attributes, so flush() computes correct old_value
   for MutationObserver/CE attributeChangedCallback. Only IframeData
   is updated eagerly (not tracked by MutationObserver).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1. register_iframe_entry: remove_one before insert_one to handle
   iframe reload (hecs insert_one fails if component exists)
2. src mutation: try_load_iframe_entity with force=true so lazy
   iframes are loaded immediately on explicit src change
3. make_blank_entry: accepts IframeLoadContext, shares parent's
   font_db and fetch_handle instead of creating fresh instances

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings March 25, 2026 17:52
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 47 out of 48 changed files in this pull request and generated 2 comments.

Comments suppressed due to low confidence (1)

crates/script/elidex-js-boa/src/bridge/mod.rs:501

  • pending_open_tab is stored as a single Option<Url>, so multiple window.open(..., '_blank') calls within the same task/tick will overwrite each other and only the last URL will be acted on. Store pending open-tab requests in a queue (e.g., VecDeque<Url> or Vec<Url>) and provide a drain method so the content thread can process all of them.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

1. pending_open_tab: Option<Url> → pending_open_tabs: Vec<Url> so
   multiple window.open('','_blank') calls before event loop drain
   are all processed. drain_pending_open_tabs() replaces take.
2. shutdown_all: dispatch beforeunload/unload on in-process iframe
   documents before dropping them (WHATWG HTML §7.1.3).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 47 out of 48 changed files in this pull request and generated 2 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

send and others added 2 commits March 26, 2026 03:13
1. navigate_iframe: update both IframeData.src AND Attributes("src")
   so getAttribute("src") stays in sync with navigation state
2. CssPropertyRegistry sharing: added registry field to IframeLoadContext,
   set pipeline.registry from ctx in all iframe pipeline creation paths
   (make_blank_entry, srcdoc, about:blank, no-src fallback)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
4 places in load_iframe called build_pipeline_interactive then
immediately overwrote font_db/fetch_handle/registry with parent's
shared instances (3 lines each = 12 lines of boilerplate).

Extracted build_iframe_pipeline(html, ctx) → PipelineResult that
does the build + resource sharing in one place. Each call site is
now a single line.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings March 25, 2026 18:16
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 47 out of 48 changed files in this pull request and generated 2 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

1. build_pipeline_interactive_shared: new function that accepts
   font_db/fetch_handle/registry upfront so JS fetch() closure
   captures the correct FetchHandle (not a fresh one that gets
   discarded). Fixes credentialless cookie leak and parent resource
   sharing for iframe pipelines.
2. Event loop: drain pending_open_tabs after timer/animation drain
   so window.open() from setTimeout/rAF is processed (was only
   drained from input handler path via process_pending_actions).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 47 out of 48 changed files in this pull request and generated 1 comment.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +761 to +780
// Update both IframeData.src and Attributes so load_iframe knows which
// URL to fetch and getAttribute("src") stays in sync.
let url_str = url.to_string();
if let Ok(mut iframe_data) = state
.pipeline
.dom
.world_mut()
.get::<&mut elidex_ecs::IframeData>(iframe_entity)
{
iframe_data.src = Some(url_str.clone());
}
if let Ok(mut attrs) = state
.pipeline
.dom
.world_mut()
.get::<&mut elidex_ecs::Attributes>(iframe_entity)
{
attrs.set("src", &url_str);
}
super::iframe::try_load_iframe_entity(state, iframe_entity, true);
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This navigation path updates IframeData.src and directly mutates the Attributes component (attrs.set("src", ...)) without recording a mutation or bumping the DOM revision. That can (1) prevent MutationObservers from seeing the attribute change / yield incorrect old_value, and (2) skip dom.rev_version() which other subsystems may rely on for invalidation.

Suggestion: route the src update through the mutation system (record a SetAttribute for src before any direct attribute edits, and let flush() apply it). If you still need immediate loading, keep updating IframeData.src eagerly and call try_load_iframe_entity, but avoid mutating Attributes directly.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants