Skip to content

Update links, images and iframes in place when href or src changes - #201

Merged
joeldrapper merged 4 commits into
claude/project-thread-155s1mfrom
claude/project-thread-6ngtxu
Oct 10, 2026
Merged

joeldrapper merged 4 commits into
claude/project-thread-155s1mfrom
claude/project-thread-6ngtxu

Conversation

@joeldrapper

Copy link
Copy Markdown
Collaborator

Requested by Joel · project thread

Before: a link, image or iframe whose href or src changed was removed and a new one inserted. A focused link lost focus, an iframe was torn down, and a stylesheet <link> in the head flashed unstyled content.

After: these elements are morphed in place. href and src still pair elements that share them first, so reordered links keep their own elements; only the leftovers match by tag and position.

How: canSoftMatchByTagName no longer treats a non-empty href or src as identity. An id, a non-empty name, and form controls (including <input type="image" src>) keep their current rules.

Tests: new test/new/href-src-in-place.browser.test.ts (fails on main: in-place morphs for a, img, iframe and link, href pairing before position, focused link keeps focus; plus guards that name and form controls are still replaced). Three tests asserting the old replacement were removed. README's pairing list updated.

Head stylesheet check: changing a stylesheet link's href in place keeps the old sheet applied until the new one loads in Chromium and WebKit (no flash). Firefox drops the old sheet as soon as href changes, so it still flashes there. Main flashes in all three.

Benchmark vs main (perf-sweep harness, Chromium, 21 rounds, lower quartile): all scenarios within noise (−8% to +9%) except "links 500 hrefs change", 2.46 ms → 3.79 ms, because those 500 links are now morphed instead of replaced by the parsed nodes. That's the intended behaviour change.

🤖 Generated with Claude Code

https://claude.ai/code/session_01LSdDY3ErxNaeULsmoUQS4q


Generated by Claude Code

@joeldrapper joeldrapper self-assigned this Oct 8, 2026
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 8, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-08T16:10:13.931405Z 0e61474 New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

An href or src still pairs elements sharing it first, but no longer stops an
element matching by its tag, so a focused link keeps focus and an iframe or
image is updated rather than replaced. A name, an id and form controls keep
their rules.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LSdDY3ErxNaeULsmoUQS4q
@joeldrapper
joeldrapper force-pushed the claude/project-thread-6ngtxu branch from 4ad9b67 to 07bdf28 Compare October 8, 2026 15:36
@joeldrapper
joeldrapper changed the base branch from main to claude/project-thread-155s1m October 8, 2026 15:36

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 07bdf28d7f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/morphlex.ts Outdated
A script doesn't run again when its src changes, so morphing it in place
would stop the new script loading.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LSdDY3ErxNaeULsmoUQS4q

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 415d1428ce

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/morphlex.ts Outdated
Comment thread src/morphlex.ts Outdated
Comment thread src/morphlex.ts Outdated
An iframe's src or a link's href loads as soon as it changes, with the
attributes the element has then, so src, href and srcdoc are updated after
the rest, so sandbox and integrity apply to the new address. An SVG script's
href doesn't run it again either, so it's still replaced.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LSdDY3ErxNaeULsmoUQS4q

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b6a20eddad

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/morphlex.ts
Comment thread src/morphlex.ts Outdated
Comment thread src/morphlex.ts
;(addresses ??= []).push(attribute)
} else {
this.#noteVetoedAttribute(from, name, namespaceURI)
this.#updateAttribute(from, attribute, keepsOpen, details)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Neutralize the old link href before changing rel

When a connected link changes both relationship and address, such as <link rel="canonical" href="/old"> becoming <link rel="stylesheet" href="/new.css">, this branch applies rel while the old href is still present because href is deferred. Creating the stylesheet relationship on an already-connected link can therefore fetch /old as CSS before the final address is installed, whereas inserting the target would only fetch /new.css; the same problem occurs when removing disabled enables a stylesheet with its old address. Temporarily neutralize a changed link href before applying resource-enabling attributes, then install the target address last, consistent with the HTML link processing model.

AGENTS.md reference: AGENTS.md:L65-L67

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Not changing this. It needs a single <link> to change both its rel and its href in one update, for example canonical to stylesheet, which is rare in real pages. The fix would mean temporarily clearing href and then restoring it, with veto callbacks for each step, which is more code than the case justifies. The worst outcome is one extra request for the old URL, after which the new href is installed. I've noted this as a known limitation in AGENTS.md.


Generated by Claude Code

An iframe's srcdoc takes precedence over its src, so setting src first could
start a load the srcdoc replaces. A media element doesn't look at a source
again when its src changes, so a source is still replaced.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LSdDY3ErxNaeULsmoUQS4q

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0e61474f6d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/morphlex.ts
}

if (addresses) {
for (const attribute of addresses) this.#updateAttribute(from, attribute, keepsOpen, details)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Set the new iframe src before removing srcdoc

When a matched iframe changes from <iframe src="old" srcdoc="…"> to <iframe src="new">, the removal pass deletes srcdoc before this deferred loop installs the new src. Removing srcdoc immediately processes the iframe attributes, so the browser can start a navigation or request to the old fallback URL before navigating to the intended new URL; replacing the iframe did not expose that intermediate request. Set the new src while srcdoc still masks it, then remove srcdoc, as implied by the iframe processing model.

AGENTS.md reference: AGENTS.md:L65-L67

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Not changing this. It only happens when an iframe switches from srcdoc to a new src in a single morph. Even then, the worst case is that the old src starts loading and the new src replaces it straight away. Handling it would need yet another ordering special case in #visitAttributes for one rare transition. I've put the call to the maintainer.


Generated by Claude Code

Comment thread src/morphlex.ts
idOf(element) === "" &&
!isFormControl(element) &&
!getAttribute(element, "name") &&
!(REPLACED_ON_ADDRESS_CHANGE.has(localNameOf(element)) && (getAttribute(element, "src") || getAttribute(element, "href")))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Reset media elements when src is removed

When a live <audio src="old"> or <video src="old"> is matched to a target without src, this new soft-match eligibility reuses the media element and the attribute pass merely removes src. Removing a media element's src does not invoke its load algorithm, so the old resource can remain selected or playing, and any newly inserted <source> children are not selected; previously the non-empty src prevented this tag match and replacement reset the element. Exclude this transition from in-place matching or explicitly reload the media element after the morph, consistent with the media resource processing model.

AGENTS.md reference: AGENTS.md:L65-L67

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Not changing this. It needs a media element to switch from a src attribute to <source> children in a single update, which is rare. Changing src from one value to another still reloads the element, because setting src runs the load algorithm. Replacing every media element, or calling load() after the morph, would cost more than this case justifies. I've put the call to the maintainer.


Generated by Claude Code

@joeldrapper
joeldrapper added this pull request to stack #207 October 8, 2026 16:57
@joeldrapper
joeldrapper merged commit b6f5b2e into main Oct 10, 2026
7 checks passed
@joeldrapper
joeldrapper deleted the claude/project-thread-6ngtxu branch October 10, 2026 06:59
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