0.10.0 is a significant release covering three major areas: a full extension architecture overhaul, speed control improvements, and developer infrastructure.
The biggest change is a ground-up rearchitecture of how the extension injects code into pages. The old bridge worked by injecting <script> and <style> DOM elements into <head> — a technique that caused intermittent player crashes on sites like YouTube that use internal MutationObservers during framework initialization. 0.10.0 replaces this entirely with Chrome's MV3 world:"MAIN" content script primitive (Chrome 111+), which evaluates the extension's JavaScript directly in the page's V8 context with zero DOM mutations. A thin ISOLATED world bridge (content-bridge.js) handles all chrome.* API access and communicates via CustomEvents on document.documentElement. Controller CSS is injected via document.adoptedStyleSheets rather than a <style> element — another zero-mutation API. Together these changes eliminate the entire class of extension-triggered rendering crashes. web_accessible_resources is also removed as inject.js no longer needs to be web-fetchable, closing an extension fingerprinting vector.
Speed control has been substantially improved. Fight-back logic (how the extension handles sites that reset playback speed) was redesigned with exponential backoff, replacing the brittle forceLastSavedSpeed approach. Speed increment/decrement now snaps to 1.0× when crossing the boundary, and drag-to-speed uses unified pointer events for mouse and touch. Keyboard bindings migrated from deprecated keyCode to event.code with modifier chord support. New per-site speed defaults let you define a baseline speed per domain via site rules, and the blacklist/enable lifecycle is now reactive — disabling the extension or adding a site to the blacklist tears down controllers immediately without a reload. The options page gained settings export/import, a user-editable controller CSS editor with syntax highlighting, and dirty-state feedback on save.
On the infrastructure side: the full test suite was migrated from a custom runner to Vitest (348 tests), GitHub Actions CI was added with lint + build + test on every push, release packaging is now reproducible with npm run release, and the codebase moved to ESLint flat config with husky + lint-staged pre-commit hooks.
Architecture
- refactor: replace DOM injection bridge with MV3 world:"MAIN" content scripts (93c1e3a)
- fix: use document_idle for inject.js to close settings handshake race (10d0037)
- Remove activeTab permission; make background sole icon authority (#1464)
- Restrict page→content bridge to lastSpeed writes only (#1463)
Speed control
- Separate speed user-intent from playback state (#1434)
- Redesign ratechange fight-back: exponential backoff, remove forceLastSavedSpeed
- feat: snap to 1.0× when speed increment/decrement crosses the boundary (#1438)
- feat: migrate keyboard identity from keyCode to event.code + modifier chord support
- feat: per-site speed defaults via unified site rules (#1444)
- Replace per-key force flag with global exclusiveKeys setting
- Fix reactive lifecycle for blacklist and enable/disable — teardown without reload (#1435)
Options page & UX
- feat: user-editable controller CSS with live preview
- CSS syntax highlighting in controller CSS editor
- Add settings export/import (#1445)
- Unify drag handling for mouse + touch; double-click to reset speed (#1436)
- Wheel scroll: two-layer filtering to prevent accidental speed changes (#1442)
- dirty-state feedback on options save button
- Remove close (×) button from on-video controller (#1460)
- Add toggle behavior to jump-to-marker action (#1439)
Bug fixes
- fix: restore reset/preferred speed cross-toggle (#1353)
- fix: guard against double-injection of content script (#1406)
- Skip speed overlay on gif-like video elements (#1461)
- fix: drop customElements.define() to eliminate es5-adapter conflict (#1458)
- fix: controller position on Dailymotion
- fix: YouTube embedded controller
- fix race condition: write only changed storage keys