github igrigorik/videospeed v0.10.0

8 hours ago

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

Infrastructure

  • ci: GitHub Actions workflow with lint + build + tests
  • ESLint flat config, husky + lint-staged pre-commit hooks (#1456)
  • Full Vitest migration — 348 tests, fake timers, no real-timer waits (#1446)
  • Windows build support + instructions

Don't miss a new videospeed release

NewReleases is sending notifications on new releases.