The evidence for the frame-observation fixes was a reimplementation of the
logic measured in a browser console, not the shipped code. Two extension
specs now cover the observable behaviour end to end: a player frame that
swaps its document, and the same one level deeper.
The nested case fails against the pre-fix content.js and passes now. The
top-level case already passed before the fix, so that fix removed dead
observer registrations without changing what a user could see; recorded
here so the distinction is not lost.
Also adds bench-finder.mjs, which measures the extracted shipped finder
instead of a transcription of it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The delta model was only covered at module level; the popup wiring around
it was not exercised at all. Seven specs now drive the real settings UI in
the packed extension, including the migration path: a pre-v3.1.0 snapshot
is converted on open, the legacy key is removed, and a default missing
from that snapshot is delivered again.
Also from the audit pass:
- await the blacklist read in init instead of firing a floating promise
- unify the debug report on the finder's own candidate list, which the
separate traversal missed shadow-DOM videos from
- assert that a single candidate is always returned regardless of its
ranking signals, so no scoring signal can regress a single-player site
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The weighted score summed incomparable units, so size could outvote traits
that disqualify an element outright. Measured on a real page: a display:none
preload reports its full 1080p intrinsic size and scored 2073600, beating a
visible unmuted player at 509920.
Selection now compares an ordered list of signals, highest priority first:
has a source, is rendered, is not a silent background loop, rendered size
bucket, is playing, has controls, duration. Rendered size replaces intrinsic
resolution, and mute state is gone from the ranking entirely: it is a viewer
preference, not evidence about which element is the player.
It stays a ranking rather than a filter, so a page of only bad candidates
still yields one and findVideo never returns null where a video exists.
The new tests/e2e suite runs the shipped finder against real fixture pages
and drives the packed extension for injection, reinjection and remote
play/pause/seek into a first-party frame. All five scoring scenarios fail
against the previous implementation.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Saving the editor used to persist the full effective list, which froze the
shipped defaults at that moment: every default added in a later version
never reached anyone who had touched the list once.
Storage now keeps only { removedDefaults, addedDomains }, and the effective
list is resolved against whatever ships in the running version. Entries are
tagged default or user, the editor groups them under comment headers, and
'#' lines are ignored on save. Lists saved by earlier versions are migrated
to the delta form on first read and the legacy key is removed.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Audit follow-ups on the same-origin frame walk:
- prune detached frames from the load-hook set so ad/SPA frame churn
no longer grows it for the page's lifetime
- hook load on nested frames, not just top-level ones
- re-observe from scratch after a frame reload instead of leaving the
replaced document's tree registered
- reset the frame registry when the heartbeat error path disconnects
Also: a broad parent domain in the blacklist no longer hides a host with
its own supported player path (drive.google.com behind google.com), while
an exact user entry for that host still filters it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sites like jkanime.net render the real <video> inside a first-party
iframe, so the top document had zero video elements and the content
script reported "NO VIDEO ELEMENT".
findVideo() now descends into reachable frame documents, the
MutationObserver registers those documents too (frame mutations never
bubble to the parent), and frame load events re-trigger the scan so a
late-loading player is still picked up. Debug reports count videos
across frames and expose an "In Iframe" flag.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>