Make the page controller work under Manifest V3 - #1
Open
InventivetalentDev wants to merge 1 commit into
Open
Conversation
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.
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.
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 withtoString()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:
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/whichas expandos on a genericEvent, and expandos do not cross into the page's world. A realKeyboardEventcarries 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.
simulateKeyEventis now a direct dispatch.The event also carries
key/codenow, derived from thekeyCodethe remote sends, since presentation software increasingly reads those instead of the deprecated attribute. The ctrl/shift/alt modifiers were a//TODO: fix thisthe old expando approach could not express; theKeyboardEventconstructor takes them, so they work.Messaging
chrome.extension.onMessageis gone in MV3 →chrome.runtime.onMessage. This is whatutil/mv3-compat.jsin the extension was shimming, so that file can go (see the companion PR).try/catcharoundsendMessagenever caught anything asynchronous. The service worker holds the message channel open for every message it receives (it answerstakeScreenshotasynchronously), so each fire-and-forget send ends in amessage port closedlastError. AsendToExtension()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()readimage.imageoff 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.onunloadreplaced with apagehidelistener.unloaddoes not fire for a page entering the back/forward cache, and assigning toonunloadclobbered any handler the page had set itself.Overlay
overlay.htmlpulled Font Awesome frommaxcdn.bootstrapcdn.com. It is injected into the page, so a strictstyle-srcblocked 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#remoteSlideOverlayHtmlContainerso a page's own.farules are left alone.laserStyle._iconfalls 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/simulateKeyEventfrom this branch against a page serving a Google-Slides-style nonce CSP:Overlay icons rendered from the real
overlay.html, appended the wayinjector.jsdoes it:Not addressed
pageController.jsopens withconst 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