From 44731c10ed4e84ce7ec953a2a5c4a706bb41b39f Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 18 Oct 2026 12:32:19 +0000 Subject: [PATCH 1/2] Load and run the extension in Firefox Firefox runs the existing MV3 extension with a few additions to the shared manifest: - browser_specific_settings.gecko: the add-on ID (mandatory for MV3 on AMO), strict_min_version 140 (the first release with the built-in data consent prompt; ESR 140), and data_collection_permissions declaring websiteContent, since screenshots of the presented tab go to the server (AMO rejects new add-ons without this key). - background.scripts next to service_worker: Firefox has no background service workers and runs the scripts as an event page instead. Chrome ignores background.scripts from 121 on and refused the manifest before that, hence minimum_chrome_version 121. In the event page sessionGrabber.js comes from that list, as importScripts() only exists in the worker. - The popup reports the host as extension_firefox in Firefox, so the connection info tells the two browsers apart. Bumps the controller for the fix that lets scripting.executeScript() resolve in Firefox, without which the overlay and laser pointer never loaded. Checked end to end against a local copy of the site, in Firefox 158 and ESR 140 and, for regressions, in Chromium 141: background start, controller injection, host join, key presses, overlay, laser dot, badge, screenshots and the popup. One known gap: for an MV3 add-on, Firefox 140-151 never counts the granted host permission in the check captureVisibleTab() makes (changed in 152; seen working in ESR 153 and 158). There, screenshots rely on the activeTab grant from clicking the toolbar button, which is how control is started anyway. The grant lasts until the tab loads a new document, which also removes the controller. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01P3g3vcYRESg89oZuTf7eP5 --- background.js | 7 ++++++- index.js | 5 ++++- inject/controller | 2 +- manifest.json | 19 +++++++++++++++++-- 4 files changed, 28 insertions(+), 5 deletions(-) diff --git a/background.js b/background.js index 323105f..6b554f5 100644 --- a/background.js +++ b/background.js @@ -1,4 +1,9 @@ -importScripts("sessionGrabber.js"); +// Chrome runs this file as a service worker, which pulls in its siblings itself. +// Firefox has no background service workers and runs it as an event page +// instead, with sessionGrabber.js already loaded from background.scripts. +if (typeof importScripts === "function") { + importScripts("sessionGrabber.js"); +} chrome.runtime.onMessageExternal.addListener(function (msg, sender, sendResponse) { if (msg.ping && msg.ping == 'hello') { diff --git a/index.js b/index.js index abafb37..36b5f10 100644 --- a/index.js +++ b/index.js @@ -1,5 +1,8 @@ var app = angular.module("app", []); +// Shown in the session's connection info, so tell the two builds apart +var INJECTOR = chrome.runtime.getURL("").indexOf("moz-extension:") === 0 ? "extension_firefox" : "extension_chrome"; + // chrome.tabs.getSelected is gone in MV3; the popup always belongs to the current window. function withActiveTab(callback) { chrome.tabs.query({active: true, currentWindow: true}, function (tabs) { @@ -71,7 +74,7 @@ app.controller("mainCtrl", function ($scope, $timeout) { }, args: [{ session: $scope.session.session, - injector: 'extension_chrome' + injector: INJECTOR }] }); }).then(function () { diff --git a/inject/controller b/inject/controller index b9fe1f8..7f11f75 160000 --- a/inject/controller +++ b/inject/controller @@ -1 +1 @@ -Subproject commit b9fe1f8002b5cc7a11140b4ec5947cf991c139ae +Subproject commit 7f11f75ce07e102795953f0c9c89fac13f0ee7e6 diff --git a/manifest.json b/manifest.json index 433750a..368e4b1 100644 --- a/manifest.json +++ b/manifest.json @@ -5,7 +5,18 @@ "version": "1.2.0", "author": "inventivetalent", "homepage_url": "https://remote-sli.de", - "minimum_chrome_version": "102", + "minimum_chrome_version": "121", + "browser_specific_settings": { + "gecko": { + "id": "remoteslide@remote-sli.de", + "strict_min_version": "140.0", + "data_collection_permissions": { + "required": [ + "websiteContent" + ] + } + } + }, "action": { "default_icon": "img/logo-128.png", "default_popup": "index.html" @@ -32,7 +43,11 @@ "extension_pages": "script-src 'self'; object-src 'self'" }, "background": { - "service_worker": "background.js" + "service_worker": "background.js", + "scripts": [ + "sessionGrabber.js", + "background.js" + ] }, "content_scripts": [ { From 53cb728ef024da002f2cc47976a9826d63056a75 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 18 Oct 2026 13:58:11 +0000 Subject: [PATCH 2/2] Take sessions from remote-sli.de in Firefox too Firefox doesn't implement externally_connectable, so remote-sli.de cannot reach the extension with chrome.runtime.sendMessage() there (Mozilla bug 1319168), and the popup never got a session. util/siteBridge.js, a content script on remote-sli.de, now relays what the site posts to its own window to the background and posts the answer back. The site falls back to that channel when chrome.runtime isn't exposed (RemoteSlide/remote-sli.de, same branch). Only messages from the page's own window are relayed, and the background only takes them from remote-sli.de tabs, the same origin externally_connectable allows. Both channels end up in handleSiteMessage(), which now always answers, so the site can close the ?sessionOnly tab once the extension has the session. sessionGrabber.js becomes the grabSession() it calls. Sets the Firefox add-on ID to remoteslide@inventivetalent.org. Checked against the local site with remote-sli.de pointed at it, in Firefox 158 and ESR 140 (bridge) and Chromium 141 (externally_connectable): ping/pong, the stored session, the ?sessionOnly tab closing itself, and no answer on other sites. The unchanged site fails the same check in Firefox. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01P3g3vcYRESg89oZuTf7eP5 --- background.js | 27 ++++++++++++++++++++++++--- manifest.json | 11 ++++++++++- sessionGrabber.js | 44 +++++++++++++++++++++----------------------- util/siteBridge.js | 20 ++++++++++++++++++++ 4 files changed, 75 insertions(+), 27 deletions(-) create mode 100644 util/siteBridge.js diff --git a/background.js b/background.js index 6b554f5..6ee71d1 100644 --- a/background.js +++ b/background.js @@ -5,10 +5,28 @@ if (typeof importScripts === "function") { importScripts("sessionGrabber.js"); } -chrome.runtime.onMessageExternal.addListener(function (msg, sender, sendResponse) { - if (msg.ping && msg.ping == 'hello') { - sendResponse({pong: "hello"}); +// Messages from remote-sli.de. Chrome lets the site send them here directly +// (externally_connectable); Firefox doesn't let web pages message an extension +// at all, so there util/siteBridge.js relays what the site posts to its window. +function handleSiteMessage(msg, sendResponse) { + if (msg.session) { + grabSession(msg.session); + } + // Always answer: the site closes the tab it opened for a session once it hears back + sendResponse(msg.ping == 'hello' ? {pong: "hello"} : {}); +} + +function isSiteSender(sender) { + try { + var host = new URL(sender.url).hostname; + return host == "remote-sli.de" || host.endsWith(".remote-sli.de"); + } catch (ignored) { + return false; } +} + +chrome.runtime.onMessageExternal.addListener(function (msg, sender, sendResponse) { + handleSiteMessage(msg, sendResponse); }); // The badge belongs to the tab the controller runs in. Messages from the injected @@ -32,6 +50,9 @@ function setRemoteCountBadge(sender, data) { // Handle messages from the pageController script chrome.runtime.onMessage.addListener(function (msg, sender, sendResponse) { + if (msg.action == 'siteMessage' && isSiteSender(sender)) { + handleSiteMessage(msg.message || {}, sendResponse); + } if (msg.action == 'socketEvent') { var event = msg.event; var data = msg.data; diff --git a/manifest.json b/manifest.json index 368e4b1..01f5508 100644 --- a/manifest.json +++ b/manifest.json @@ -8,7 +8,7 @@ "minimum_chrome_version": "121", "browser_specific_settings": { "gecko": { - "id": "remoteslide@remote-sli.de", + "id": "remoteslide@inventivetalent.org", "strict_min_version": "140.0", "data_collection_permissions": { "required": [ @@ -69,6 +69,15 @@ "util/preziInjector.js" ], "all_frames": true + }, + { + "matches": [ + "*://*.remote-sli.de/*" + ], + "js": [ + "util/siteBridge.js" + ], + "run_at": "document_start" } ], "web_accessible_resources": [ diff --git a/sessionGrabber.js b/sessionGrabber.js index 4027353..a48727d 100644 --- a/sessionGrabber.js +++ b/sessionGrabber.js @@ -1,24 +1,22 @@ -console.log("Waiting for session message...") -chrome.runtime.onMessageExternal.addListener(function (msg, sender, sendResponse) { - if (msg.session) { - console.log("Session msg: " + JSON.stringify(msg)) - chrome.storage.local.get(["session"], function (items) { - var oldSession = items.session; +// Stores a session that remote-sli.de handed over (see handleSiteMessage in background.js) +function grabSession(session) { + console.log("Session msg: " + JSON.stringify(session)) + chrome.storage.local.get(["session"], function (items) { + var oldSession = items.session; - msg.session.sessionTime = new Date().valueOf(); - chrome.storage.local.set({"session": msg.session}); - if (!oldSession || oldSession.session != msg.session.session) { - console.log("notification created") - chrome.notifications.create('new-session-created-notification', { - type: 'basic', - iconUrl: 'img/logo-128.png', - title: 'Session Updated', - message: 'A new session has been created\n' + - 'Click the icon to start presenting!', - contextMessage: msg.session.session - }, function (notificationId) { - }); - } - }); - } -}); \ No newline at end of file + session.sessionTime = new Date().valueOf(); + chrome.storage.local.set({"session": session}); + if (!oldSession || oldSession.session != session.session) { + console.log("notification created") + chrome.notifications.create('new-session-created-notification', { + type: 'basic', + iconUrl: 'img/logo-128.png', + title: 'Session Updated', + message: 'A new session has been created\n' + + 'Click the icon to start presenting!', + contextMessage: session.session + }, function (notificationId) { + }); + } + }); +} diff --git a/util/siteBridge.js b/util/siteBridge.js new file mode 100644 index 0000000..7cc0280 --- /dev/null +++ b/util/siteBridge.js @@ -0,0 +1,20 @@ +// Firefox has no externally_connectable, so remote-sli.de can't message the +// extension the way it does in Chrome. Instead it posts the same messages to its +// own window, and this relays them to the background and posts the answers back: +// page -> {channel: "remoteslide", direction: "to-extension", id, message} +// page <- {channel: "remoteslide", direction: "to-page", id, response} +window.addEventListener("message", function (event) { + var data = event.data; + if (event.source !== window || !data || data.channel !== "remoteslide" || data.direction !== "to-extension") { + return; + } + chrome.runtime.sendMessage({action: "siteMessage", message: data.message}, function (response) { + void chrome.runtime.lastError; + window.postMessage({ + channel: "remoteslide", + direction: "to-page", + id: data.id, + response: response + }, window.location.origin); + }); +});