0.3.0: quality, chapters, captions and a player that stays standing - #6
Merged
Merged
Conversation
A clean profile was showing a film that had already been adjusted. Stored values are now range-checked: anything outside the range the panel can produce, or of the wrong type, falls back to the neutral value rather than tinting a film for reasons the viewer cannot see.
The badge sat in the bottom corner of the video, on top of the play button and the progress line the site draws there, and swallowed taps meant for them. It now sits halfway up the right edge, which is the one part of a video no player puts a control on, and is clamped to stay on screen.
Two sources of mis-taps, both from a target that was larger than what the viewer could see. The whole picture was a hidden pause button, so a thumb resting on the screen stopped the film. Pausing is now the play button and nothing else; a tap anywhere else brings the controls up or puts them away. The seek band reached the bottom edge, where the Android home swipe starts, so putting the app away threw the film into the middle. The band and the bar it belongs to now sit above that strip, and the bar's centre is measured rather than assumed so the precision curve follows it. The chrome also gets one easing curve throughout, scrims and shadows under the controls so they read over a bright picture, and a reduced-motion rule.
Everything a site's own player knows about itself lives in the page's JavaScript. Firefox lets a content script read that through waived Xray wrappers, so the extension asks the player rather than re-implementing it: no code is injected into the page and nothing is evaluated from a string. Quality is an adapter per engine — <source> lists, YouTube, hls.js, dash.js, Shaka — probed again each time the sheet opens, because a stream only learns its ladder after the first segments land. A player that exposes no way in reports the resolution it is playing instead of offering a choice that would do nothing. Captions cover the players that paint their own instead of exposing a text track. YouTube's list comes from its player API and the text it renders is mirrored into our cue layer, which is why subtitles were missing there. The picture used to slide to an edge or stretch across the screen because the site kept rewriting the video's inline style. Position and offsets are now pinned with the size and re-pinned whenever the site writes over them, and the zoom is re-derived from the viewer's intent on every relayout. Losing fullscreen while the app is on its way to the background is what Android's floating-window hand-off looks like, so it is no longer read as the viewer leaving the player. The button uses the standard API where Gecko has one, and a Fullscreen switch in the sheet runs the player as an overlay for anyone who would rather keep the ordinary single swipe home. Pausing no longer fades the picture to grey: people pause to look at it. And the subtitle file button now has the handler it was always missing.
Also corrects the parts of the README that no longer described the extension: YouTube is in scope now, the pause fade is gone, and the volume strip and gesture lock were never built.
The guard compared the video's inline style against the values it had been asked to write, but the browser serialises them differently — `left: 0` reads back as `0px` — so it never once found the style already pinned. Every mutation record, including the ones its own writes produced, triggered twenty more !important writes: an unbounded loop of style recalculation that made the player crawl, the picture stutter and fullscreen arrive late. It now compares against what the browser serialised, which converges after a single write, and gives up after a bounded number of re-pins so a site that insists on !important of its own cannot trade writes with us all film long.
Range-checking was not enough: a saturation of zero is a value the panel can produce, so a profile that had been left there kept playing every film in black and white, and a contrast of 1.3 kept every film tinted. Neither is something a viewer would connect to a setting they touched once. Schema 2 drops the four colour values once on upgrade; after that they are theirs again.
Opening the player walked the page's globals once per streaming engine, through cross-compartment wrappers, before fullscreen was even requested. The three sweeps are now one, and the ladder is only looked up when the sheet is opened rather than while the viewer waits for the picture. Also: the caption mirror no longer reads the site's caption layer while nothing is being mirrored, the fill retry gives up instead of ticking for the length of the film on a stream that never reports its dimensions, and the vignette is lighter — it was washing the picture out.
Two changes to the thing that sits on an inline video. There is now a second button under the launcher: plain fullscreen on the site's own player, for a film you are happy to watch with the site's own controls and only want bigger. And both of them fade out when nothing has happened for a few seconds. Two buttons parked on somebody's video are two buttons in the way; a touch anywhere on the page brings them back.
YouTube ships its chapter list inside the data it renders the page from. The shape of that blob differs between the phone and desktop sites and changes without notice, so the renderer is looked for by name rather than followed down one path, with a node budget so a page that turns out to be a maze cannot cost more than a moment. The bar gets a divider at each chapter and the name of the section being played sits above it.
Quality could not be changed anywhere, and this is the likeliest reason: the adapters looked for the site's player object by searching the document, but by then the video had already been lifted out of it and onto our stage. The player API is now read from the video's own ancestors at the moment the session starts, before anything is moved, and handed to the quality adapters and the caption reader. The document search stays as the fallback. The sheet also says which of the two things happened — no player found, or a player that is offering nothing — rather than the same flat note for both.
Android floats a video when the app it is playing in goes to the background, and Gecko gives a page no way to ask for that directly. It does leave one way to go to the background: an intent URL for the launcher, loaded in a throwaway frame so a browser that does not handle the scheme fails quietly instead of navigating the film away. Also in this change: the button order at the top is picture-in-picture, colour, settings; the scrim behind the controls is heavier, because the film keeps playing underneath and the buttons have to stay legible; choosing a quality now says so, since the change itself takes a second to show; and the chapter list is read once the picture is up rather than in the way of it.
The phone's YouTube player answers getAvailableQualityLevels() with an empty list, which is why the sheet kept saying it had found the player but had nothing to offer. The ladder is now worked out from the formats the video actually has — each one carries the height it was encoded at, which is the same thing the quality names stand for — and the note says which of the two ways in produced something. Chapters are read from the player's own response first, then from the two page blobs, in both the shapes YouTube writes them in, and the read is tried again twice: a page navigated to from the feed fills its data in late. The badge is one button again, wearing the fullscreen arrows: what it does is make this video fill the screen, and that is what a viewer looks for.
The changelog stopped at the middle of the release: the home-screen hand-off, the chapters, the button on the site's own player and the ladder read off the formats all landed after it was written. Reviewers get build instructions, because a bundled package without them is a package that waits in the queue.
Every call into a site's player failed with "Permission denied to access property length", which is why the quality ladder, the caption list and the chapter list all came back empty on YouTube: method.apply reaches the page's own Function.prototype.apply, and the page — the less privileged side — cannot read the length of an array built in our compartment. Reflect.apply is ours. The arguments are unpacked on this side and the page only ever sees the values. Found in the phone's log, after the sheet had spent three versions politely reporting that YouTube offers no quality levels at all.
Three things took the player down or left it looking broken, all found by driving the emulator rather than by reading the code: A drag along the seek bar threw the viewer back to the page. YouTube removes its <video> element outright when it is told a seek is final, and a drag was telling it that every 120ms. Seeks now go through the site's own seekTo where it has one, and a preview says so: isFinal is true only when the finger comes off. The teardown watch is slower and harder to convince as well — any sign of life cancels it, and only NETWORK_EMPTY counts as empty. The picture stopped filling the screen after a quality switch. The crop lives in `transform`, which the style guard knew nothing about, so the first time the site rewrote the video's inline style the picture fell back into its letterbox and stayed there. The fullscreen button did not go fullscreen. It obeyed a stored switch that a stray tap could turn off for good. A button drawn as a fullscreen icon takes the screen; the switch in the sheet still drops back to the overlay, for this session only, the way playback speed does. Also gone: the toast on every quality tap and the stale swipe-up hint. The chip lighting up already says the choice was taken.
Quality leads the sheet now. It is the row people open it for, it is the longest, and on a phone held sideways the sheet scrolls — so anything below the first two rows was a row somebody had to go looking for. The rows themselves were climbing over each other: a fixed row height with a nine-rung quality ladder that wraps meant the second line of chips lay across the row beneath. Rows are as tall as their chips, a hairline separates them, and the sheet is bounded above the seek bar rather than running off the bottom of the screen. It also opens at the top, instead of wherever a chip left the focus last time.
The release notes described three features that did not work on a phone and a fullscreen switch that outlived its session. They now describe what the build does.
40% to 200% in steps of ten, rather than 50% to 150% in steps of five. Measured on the emulator, the old control did move the picture — a full sweep took the frame from 20 to 85 mean luminance — but it took ten presses to do anything a viewer would call brightening, and a control that needs ten presses reads as a control that does not work. A film mastered dark for a cinema can now be lifted far enough to watch on a phone.
The store page has to answer one question — what does this do that the browser does not — and the old summary did not. Every claim is checkable against the source, the limits are stated up front rather than discovered, and the privacy section says what is enforced by the build instead of what is promised.
Playerjs, which many film sites embed, publishes the hls.js constructor and a player object with one opaque method. The instance holding the ladder never leaves the closure, so there is no way in from an extension. Better said in the notes than discovered in the sheet.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Everything here comes from watching real films on a real phone with 0.2.0
installed, and from driving the same build on an Android emulator.
The one that mattered
Every call into a site's player was failing with
Permission denied to access property "length".method.apply()reaches the page's ownFunction.prototype.apply, and the page — the less privileged side — cannotread the
lengthof an array built in the content script's compartment. Callsgo through
Reflect.applynow. That single bug is why quality, captions andchapters were all missing on YouTube; all three work.
Also fixed
its
<video>element when told a seek is final, and a drag said so every120ms. Seeks go through the site's own
seekTo, and only the finger comingoff is final.
lives in
transform, which the style guard did not know about.good. It takes the screen now; the switch in the sheet is per session.
mutation, which is what made the player crawl.
Added
Quality selection, chapter markers with section titles, captions from players
that paint their own, a fullscreen button on the inline video that fades when
untouched, PiP as an Android home-screen hand-off, and a settings sheet that
leads with quality and no longer overlaps itself.
Checks
eslint+prettier+web-ext lint(0 errors, 0 warnings) + the privacycheck (28 files, permissions exactly
storageandactiveTab) + 35 unittests, all green. Verified on an Android 16 emulator with Firefox 153: quality
switching on YouTube, chapters, captions, scrubbing, fullscreen and colour.
Docs:
CHANGELOG.md,REVIEWERS.md(build instructions for AMO) andAMO-LISTING.md(store copy).