Skip to content

Report the renderer's tag and attribute drops to HtmlChangeListener (#469) - #480

Merged
jmanico merged 1 commit into
mainfrom
fix/469-renderer-drops
Sep 11, 2026
Merged

Report the renderer's tag and attribute drops to HtmlChangeListener (#469)#480
jmanico merged 1 commit into
mainfrom
fix/469-renderer-drops

Conversation

@jmanico

@jmanico jmanico commented Sep 11, 2026

Copy link
Copy Markdown
Member

Fixes #469.

HtmlStreamRenderer has three drop sites that reached only the bad-HTML handler, which PolicyFactory.sanitize wires to Handler.DO_NOTHING: a start tag whose name is not a valid HTML name (an ElementPolicy renaming to a@b produces one), a tag arriving inside literal content the renderer is writing (a policy renaming div into style produces one), and an attribute whose name is not a valid HTML name. Since the policy had emitted each, HtmlChangeReporter counted it as kept and the listener heard nothing.

The renderer's package-private drop listener, added in #463 for literal content it could not emit, becomes a DropListener with a method per kind, and the reporter maps them onto the existing listener methods:

  • A refused start tag is reported as discardedTag under the input name. Attributes the policy rejected on it are still reported, as they are for a tag skipped for having none: rejecting them was the policy's decision and the tag went for another reason. The end tag the renderer refuses later is the same loss and is not reported again.
  • An attribute left off is reported through discardedAttributes and discardedAttribute, with the value the author wrote where an input copy accounted for it, and as the renderer received it where a policy added it.

The bad-HTML handler keeps receiving its messages unchanged; the existing renderer tests for those messages pass as they are. Five new tests: one on the renderer pins the channel for all three kinds, four on the reporter pin each mapping through PolicyFactory-built policies. HtmlChangeListener's Javadoc says what is now reported and under which conditions. Verified with ./mvnw clean verify on JDK 11, 17, 21 and 25.

One side effect of the literal-content site remains and is out of scope here: the policy's open-element stack still counts a tag the renderer refused as open, so text approved for a nested element inside a renamed style lands as raw style content. I will file that separately.

🤖 Generated with Claude Code

https://claude.ai/code/session_014Ydng7gBm6Ax5zfwt4vZip

HtmlStreamRenderer refuses a start tag whose name is not one HTML allows,
which an ElementPolicy can produce by renaming, refuses a tag arriving
inside literal content it is writing, and leaves off an attribute whose
name is not one HTML allows.  Each reached only the bad-HTML handler as a
message, which PolicyFactory.sanitize wires to Handler.DO_NOTHING, and
since the policy had emitted the tag or attribute, HtmlChangeReporter
counted it as kept and told the listener nothing.

The renderer's drop listener, which since #463 carried literal content it
could not emit, now carries these too, as a DropListener with a method
per kind.  The reporter maps a refused tag onto discardedTag under the
input name, treating the attributes the policy rejected on it as the
policy's own decisions, as for an attributeless skip; and maps an
attribute left off onto discardedAttributes and discardedAttribute, with
the value the author wrote where an input copy accounted for it and as
the renderer received it where a policy added it.  An end tag the
renderer refuses is the same loss as its start tag and is not reported
again.

Fixes #469

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014Ydng7gBm6Ax5zfwt4vZip
@jmanico
jmanico merged commit fad01c1 into main Sep 11, 2026
7 checks passed
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.

The renderer's other drops never reach HtmlChangeListener

1 participant