Skip to content

Make the page controller work under Manifest V3 - #1

Open
InventivetalentDev wants to merge 1 commit into
masterfrom
claude/manifest-v3-chrome-controller-ou4z84
Open

Make the page controller work under Manifest V3#1
InventivetalentDev wants to merge 1 commit into
masterfrom
claude/manifest-v3-chrome-controller-ou4z84

Conversation

@InventivetalentDev

Copy link
Copy Markdown
Member

Companion to RemoteSlide/RemoteSlide-Chrome#1. That PR migrates the extension to Manifest V3; this submodule still calls APIs MV3 removed, and its key simulation stops working altogether.

The blocker: key simulation is dead under MV3

simulateKeyEvent() built a function with toString() and appended it to the page as an inline <script>. MV3 applies the extension's own CSP (script-src 'self') to scripts a content script injects, so that script is refused — on every site, with or without a CSP of its own.

That is not a Google-Slides-only problem, which is what I assumed going in. Loading the extension unpacked against a page serving no CSP at all still produces:

Refused to execute inline script because it violates the following Content Security Policy
directive: "script-src 'self' 'wasm-unsafe-eval' 'inline-speculation-rules' ..."

Without this change the extension connects, shows a green badge, and controls nothing.

The fix is a deletion, not an addition

The code only injected into the page because it set keyCode/which as expandos on a generic Event, and expandos do not cross into the page's world. A real KeyboardEvent carries them on the event itself, so the page reads the values it expects with nothing injected — and dispatching an event is not script execution, so no CSP applies to it either.

I first built a main-world bridge script loaded from the extension origin to escape the CSP; testing showed it was unnecessary and it was thrown away. simulateKeyEvent is now a direct dispatch.

The event also carries key/code now, derived from the keyCode the remote sends, since presentation software increasingly reads those instead of the deprecated attribute. The ctrl/shift/alt modifiers were a //TODO: fix this the old expando approach could not express; the KeyboardEvent constructor takes them, so they work.

Messaging

  • chrome.extension.onMessage is gone in MV3 → chrome.runtime.onMessage. This is what util/mv3-compat.js in the extension was shimming, so that file can go (see the companion PR).
  • The try/catch around sendMessage never caught anything asynchronous. The service worker holds the message channel open for every message it receives (it answers takeScreenshot asynchronously), so each fire-and-forget send ends in a message port closed lastError. A sendToExtension() helper reads lastError in the callback so Chrome stops logging it, and still catches the synchronous throw from a content script orphaned by an extension reload.
  • sendScreenshot() read image.image off the response unguarded. That throws when the worker replies {} after a failed capture — a path the MV3 background newly introduces — or with nothing at all when it was torn down mid-request.

Lifecycle

window.onunload replaced with a pagehide listener. unload does not fire for a page entering the back/forward cache, and assigning to onunload clobbered any handler the page had set itself.

Overlay

overlay.html pulled Font Awesome from maxcdn.bootstrapcdn.com. It is injected into the page, so a strict style-src blocked it and the laser dot and calibration marks rendered as nothing on exactly the sites this targets. The glyphs it used are now drawn from characters every system font ships, scoped to #remoteSlideOverlayHtmlContainer so a page's own .fa rules are left alone. laserStyle._icon falls back to a dot when a remote asks for a shape the overlay cannot draw.

Verification

Run against Chromium with the extension loaded unpacked, driving the real describeKey/simulateKeyEvent from this branch against a page serving a Google-Slides-style nonce CSP:

CSP refusals from the extension : (none)
keydown events the page saw     : 10 of 10
full event types per key        : 30 (expect 3x keydown/keypress/keyup = 30)

PASS  keyCode 39  -> key="ArrowRight" code=ArrowRight keyCode=39 which=39 ctrl=false shift=false
PASS  keyCode 37  -> key="ArrowLeft" code=ArrowLeft keyCode=37 which=37 ctrl=false shift=false
PASS  keyCode 34  -> key="PageDown" code=PageDown keyCode=34 which=34 ctrl=false shift=false
PASS  keyCode 32  -> key=" " code=Space keyCode=32 which=32 ctrl=false shift=false
PASS  keyCode 27  -> key="Escape" code=Escape keyCode=27 which=27 ctrl=false shift=false
PASS  keyCode 66  -> key="B" code=KeyB keyCode=66 which=66 ctrl=false shift=true
PASS  keyCode 70  -> key="f" code=KeyF keyCode=70 which=70 ctrl=false shift=false
PASS  keyCode 13  -> key="Enter" code=Enter keyCode=13 which=13 ctrl=true shift=false
PASS  keyCode 112 -> key="F1" code=F1 keyCode=112 which=112 ctrl=false shift=false
PASS  keyCode 190 -> key="." code=Period keyCode=190 which=190 ctrl=false shift=false

10/10 passed

Overlay icons rendered from the real overlay.html, appended the way injector.js does it:

external network requests : (none - fully self-contained)
PASS  .fa-circle             content="●"    19x32px
PASS  .fa-plus               content="✚"    27x32px
PASS  .fa-check-circle-o     content="✔"    27x32px
   ... 11/11 icons render
Page's own .fa outside the overlay: content=none  (PASS - not clobbered)

Not addressed

pageController.js opens with const remoteSlideIo = ..., so injecting twice into the same tab throws a redeclaration error and opens a second socket. That behaves identically under MV2, so it is left out of this diff — worth a separate fix if double-clicking the popup button is a real path.


Generated by Claude Code

The Chrome extension's MV3 migration (RemoteSlide-Chrome#1) leaves this
submodule calling APIs that MV3 removed, and its key simulation stops
working altogether. Verified against Chromium with the extension loaded
unpacked.

Key simulation:
- simulateKeyEvent() built a function with toString() and appended it to
  the page as an inline <script>. MV3 applies the extension's own CSP
  ("script-src 'self'") to scripts a content script injects, so that
  script is refused - on every site, with or without a CSP of its own.
  The remote would connect, show a green badge and control nothing.
- It only injected into the page because it set keyCode/which as expandos
  on a generic Event, and expandos do not cross into the page's world. A
  real KeyboardEvent carries them on the event itself, so the page reads
  the values it expects with nothing injected. Dispatching an event is not
  script execution, so no CSP applies to it either.
- The event now also carries key/code, derived from the keyCode the remote
  sends, since presentation software increasingly reads those instead. The
  ctrl/shift/alt modifiers were a TODO that the old expando approach could
  not express; the KeyboardEvent constructor takes them, so they work now.

Messaging:
- chrome.extension.onMessage is gone in MV3 -> chrome.runtime.onMessage.
  This is what util/mv3-compat.js in the extension was shimming, so that
  file can go.
- The try/catch around sendMessage never caught anything asynchronous. The
  service worker holds the message channel open for every message it gets
  (it answers takeScreenshot asynchronously), so each fire-and-forget send
  ends in a "message port closed" lastError. A sendToExtension() helper
  reads lastError in the callback so Chrome stops logging it, and still
  catches the synchronous throw from an orphaned content script.
- sendScreenshot() read image.image off the response unguarded, which
  throws when the worker replies with {} after a failed capture, or with
  nothing at all when it was torn down mid-request.

Lifecycle:
- window.onunload replaced with a pagehide listener. unload does not fire
  for a page entering the back/forward cache, and assigning to onunload
  clobbered any handler the page had set itself.

Overlay:
- overlay.html pulled Font Awesome from maxcdn.bootstrapcdn.com. It is
  injected into the page, so a strict style-src blocked it and the laser
  dot and calibration marks rendered as nothing on exactly the sites this
  targets. The glyphs it used are now drawn from characters every system
  font ships, scoped to the overlay container so a page's own .fa rules
  are left alone. laserStyle._icon falls back to a dot when a remote asks
  for a shape the overlay cannot draw.
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.

1 participant