mirror of
https://github.com/hensm/fx_cast.git
synced 2026-06-08 08:39:59 +00:00
Convert extension to manifest v3
This commit is contained in:
17
.vscode/launch.json
vendored
Normal file
17
.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"name": "Bridge (daemon)",
|
||||||
|
"type": "node",
|
||||||
|
"request": "launch",
|
||||||
|
"program": "${workspaceFolder}/dist/bridge/src/main.js",
|
||||||
|
"args": ["--__name", "fx_cast_bridge", "--daemon"],
|
||||||
|
"env": {
|
||||||
|
"NODE_PATH": "${workspaceFolder}/bridge/node_modules"
|
||||||
|
},
|
||||||
|
"cwd": "${workspaceFolder}/dist/bridge",
|
||||||
|
"console": "integratedTerminal"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -64,16 +64,20 @@ export default class Remote extends CastClient {
|
|||||||
if (!application || application.isIdleScreen) {
|
if (!application || application.isIdleScreen) {
|
||||||
// Handle app close
|
// Handle app close
|
||||||
if (this.transportClient) {
|
if (this.transportClient) {
|
||||||
|
this.transportClient.disconnect();
|
||||||
this.transportClient = undefined;
|
this.transportClient = undefined;
|
||||||
this.options?.onApplicationClose?.();
|
this.options?.onApplicationClose?.();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.options?.onReceiverStatusUpdate?.(message.status);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update status before possible transport init
|
// Update status before possible transport init
|
||||||
this.options?.onReceiverStatusUpdate?.(message.status);
|
this.options?.onReceiverStatusUpdate?.(message.status);
|
||||||
|
|
||||||
// Handle app creation/discovery
|
// Handle app creation/discovery
|
||||||
if (application && !this.transportClient) {
|
if (!this.transportClient) {
|
||||||
this.transportClient = new RemoteTransport(
|
this.transportClient = new RemoteTransport(
|
||||||
application.transportId,
|
application.transportId,
|
||||||
message => this.onMediaMessage(message)
|
message => this.onMediaMessage(message)
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ const outPath = argv.package ? unpackedPath : distPath;
|
|||||||
/** @type esbuild.BuildOptions */
|
/** @type esbuild.BuildOptions */
|
||||||
const buildOpts = {
|
const buildOpts = {
|
||||||
bundle: true,
|
bundle: true,
|
||||||
target: "firefox64",
|
target: "firefox109",
|
||||||
logLevel: "info",
|
logLevel: "info",
|
||||||
sourcemap: "inline",
|
sourcemap: "inline",
|
||||||
|
|
||||||
@@ -113,10 +113,13 @@ const buildOpts = {
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
manifest.content_security_policy =
|
// In development, allow eval for source maps
|
||||||
argv.mode === "production"
|
if (argv.mode !== "production") {
|
||||||
? "script-src 'self'; object-src 'self'"
|
manifest.content_security_policy = {
|
||||||
: "script-src 'self' 'unsafe-eval'; object-src 'self'";
|
extension_pages:
|
||||||
|
"script-src 'self' 'unsafe-eval'; object-src 'self'"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
fs.writeFileSync(
|
fs.writeFileSync(
|
||||||
`${outPath}/manifest.json`,
|
`${outPath}/manifest.json`,
|
||||||
|
|||||||
17
extension/package-lock.json
generated
17
extension/package-lock.json
generated
@@ -5,7 +5,7 @@
|
|||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/firefox-webext-browser": "^94.0.1",
|
"@types/firefox-webext-browser": "^143.0.0",
|
||||||
"@types/semver": "^7.3.9",
|
"@types/semver": "^7.3.9",
|
||||||
"@types/uuid": "^8.3.4",
|
"@types/uuid": "^8.3.4",
|
||||||
"esbuild": "^0.25.0",
|
"esbuild": "^0.25.0",
|
||||||
@@ -881,10 +881,11 @@
|
|||||||
"peer": true
|
"peer": true
|
||||||
},
|
},
|
||||||
"node_modules/@types/firefox-webext-browser": {
|
"node_modules/@types/firefox-webext-browser": {
|
||||||
"version": "94.0.1",
|
"version": "143.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/@types/firefox-webext-browser/-/firefox-webext-browser-94.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/@types/firefox-webext-browser/-/firefox-webext-browser-143.0.0.tgz",
|
||||||
"integrity": "sha512-I6iHRQJSTZ+gYt2IxdH2RRAMvcUyK8v5Ig7fHQR0IwUNYP7hz9+cziBVIKxLCO6XI7fiyRsNOWObfl3/4Js2Lg==",
|
"integrity": "sha512-865dYKMOP0CllFyHmgXV4IQgVL51OSQQCwSoihQ17EwugePKFSAZRc0EI+y7Ly4q7j5KyURlA7LgRpFieO4JOw==",
|
||||||
"dev": true
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@types/http-cache-semantics": {
|
"node_modules/@types/http-cache-semantics": {
|
||||||
"version": "4.0.1",
|
"version": "4.0.1",
|
||||||
@@ -7509,9 +7510,9 @@
|
|||||||
"peer": true
|
"peer": true
|
||||||
},
|
},
|
||||||
"@types/firefox-webext-browser": {
|
"@types/firefox-webext-browser": {
|
||||||
"version": "94.0.1",
|
"version": "143.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/@types/firefox-webext-browser/-/firefox-webext-browser-94.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/@types/firefox-webext-browser/-/firefox-webext-browser-143.0.0.tgz",
|
||||||
"integrity": "sha512-I6iHRQJSTZ+gYt2IxdH2RRAMvcUyK8v5Ig7fHQR0IwUNYP7hz9+cziBVIKxLCO6XI7fiyRsNOWObfl3/4Js2Lg==",
|
"integrity": "sha512-865dYKMOP0CllFyHmgXV4IQgVL51OSQQCwSoihQ17EwugePKFSAZRc0EI+y7Ly4q7j5KyURlA7LgRpFieO4JOw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"@types/http-cache-semantics": {
|
"@types/http-cache-semantics": {
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
"lint": "eslint src --ext .ts,.tsx"
|
"lint": "eslint src --ext .ts,.tsx"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/firefox-webext-browser": "^94.0.1",
|
"@types/firefox-webext-browser": "^143.0.0",
|
||||||
"@types/semver": "^7.3.9",
|
"@types/semver": "^7.3.9",
|
||||||
"@types/uuid": "^8.3.4",
|
"@types/uuid": "^8.3.4",
|
||||||
"esbuild": "^0.25.0",
|
"esbuild": "^0.25.0",
|
||||||
|
|||||||
@@ -40,8 +40,8 @@ export function updateActionState(state: ActionState, tabId?: number) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
browser.browserAction.setTitle({ tabId, title });
|
browser.action.setTitle({ tabId, title });
|
||||||
browser.browserAction.setIcon({ tabId, path });
|
browser.action.setIcon({ tabId, path });
|
||||||
}
|
}
|
||||||
|
|
||||||
export function initAction() {
|
export function initAction() {
|
||||||
@@ -49,7 +49,7 @@ export function initAction() {
|
|||||||
|
|
||||||
updateActionState(ActionState.Default);
|
updateActionState(ActionState.Default);
|
||||||
|
|
||||||
browser.browserAction.onClicked.addListener(async tab => {
|
browser.action.onClicked.addListener(async tab => {
|
||||||
if (tab.id === undefined) {
|
if (tab.id === undefined) {
|
||||||
logger.error("Tab ID not found in browser action handler.");
|
logger.error("Tab ID not found in browser action handler.");
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import logger from "../lib/logger";
|
import logger from "../lib/logger";
|
||||||
import options from "../lib/options";
|
import options from "../lib/options";
|
||||||
import { stringify } from "../lib/utils";
|
|
||||||
|
|
||||||
import * as menuIds from "../menuIds";
|
import * as menuIds from "../menuIds";
|
||||||
|
import { MenuId } from "../menuIds";
|
||||||
|
|
||||||
import castManager from "./castManager";
|
import castManager from "./castManager";
|
||||||
|
|
||||||
@@ -15,32 +15,30 @@ const URL_PATTERN_FILE = "file://*/*";
|
|||||||
const URL_PATTERNS_REMOTE = [URL_PATTERN_HTTP, URL_PATTERN_HTTPS];
|
const URL_PATTERNS_REMOTE = [URL_PATTERN_HTTP, URL_PATTERN_HTTPS];
|
||||||
const URL_PATTERNS_ALL = [...URL_PATTERNS_REMOTE, URL_PATTERN_FILE];
|
const URL_PATTERNS_ALL = [...URL_PATTERNS_REMOTE, URL_PATTERN_FILE];
|
||||||
|
|
||||||
type MenuId = string | number;
|
|
||||||
|
|
||||||
let menuIdCast: MenuId;
|
|
||||||
let menuIdCastMedia: MenuId;
|
|
||||||
let menuIdWhitelist: MenuId;
|
|
||||||
let menuIdWhitelistRecommended: MenuId;
|
|
||||||
|
|
||||||
/** Match patterns for the whitelist option menus. */
|
/** Match patterns for the whitelist option menus. */
|
||||||
const whitelistChildMenuPatterns = new Map<MenuId, string>();
|
const whitelistChildMenuPatterns = new Map<string | number, string>();
|
||||||
|
|
||||||
/** Handles initial menu setup. */
|
/** Handles initial menu setup. */
|
||||||
export async function initMenus() {
|
export async function initMenus() {
|
||||||
logger.info("init (menus)");
|
logger.info("init (menus)");
|
||||||
|
|
||||||
|
// Clear any existing menus from a previous event page load
|
||||||
|
await browser.menus.removeAll();
|
||||||
|
|
||||||
const opts = await options.getAll();
|
const opts = await options.getAll();
|
||||||
|
|
||||||
// Global "Cast..." menu item
|
// Global "Cast..." menu item
|
||||||
menuIdCast = browser.menus.create({
|
browser.menus.create({
|
||||||
contexts: ["browser_action", "page", "tools_menu"],
|
id: MenuId.Cast,
|
||||||
|
contexts: ["action", "page", "tools_menu"],
|
||||||
title: _("contextCast"),
|
title: _("contextCast"),
|
||||||
documentUrlPatterns: ["http://*/*", "https://*/*"],
|
documentUrlPatterns: ["http://*/*", "https://*/*"],
|
||||||
icons: { "16": "icons/icon.svg" } // browser_action context
|
icons: { "16": "icons/icon.svg" }
|
||||||
});
|
});
|
||||||
|
|
||||||
// <video>/<audio> "Cast..." context menu item
|
// <video>/<audio> "Cast..." context menu item
|
||||||
menuIdCastMedia = browser.menus.create({
|
browser.menus.create({
|
||||||
|
id: MenuId.CastMedia,
|
||||||
contexts: ["audio", "video", "image"],
|
contexts: ["audio", "video", "image"],
|
||||||
title: _("contextCast"),
|
title: _("contextCast"),
|
||||||
visible: opts.mediaEnabled,
|
visible: opts.mediaEnabled,
|
||||||
@@ -49,67 +47,81 @@ export async function initMenus() {
|
|||||||
: URL_PATTERNS_REMOTE
|
: URL_PATTERNS_REMOTE
|
||||||
});
|
});
|
||||||
|
|
||||||
menuIdWhitelist = browser.menus.create({
|
// Whitelist menu parent item
|
||||||
contexts: ["browser_action"],
|
browser.menus.create({
|
||||||
|
id: MenuId.Whitelist,
|
||||||
|
contexts: ["action"],
|
||||||
title: _("contextAddToWhitelist"),
|
title: _("contextAddToWhitelist"),
|
||||||
enabled: false
|
enabled: false
|
||||||
});
|
});
|
||||||
|
// Top item in the whitelist submenu, which is the recommended pattern based
|
||||||
menuIdWhitelistRecommended = browser.menus.create({
|
// on the current page URL and is always present.
|
||||||
|
browser.menus.create({
|
||||||
|
id: MenuId.WhitelistRecommended,
|
||||||
title: _("contextAddToWhitelistRecommended"),
|
title: _("contextAddToWhitelistRecommended"),
|
||||||
parentId: menuIdWhitelist
|
parentId: MenuId.Whitelist
|
||||||
});
|
});
|
||||||
|
// Separator between recommended and advanced patterns
|
||||||
|
browser.menus.create({
|
||||||
|
id: MenuId.WhitelistSeparator,
|
||||||
|
type: "separator",
|
||||||
|
parentId: MenuId.Whitelist
|
||||||
|
});
|
||||||
|
|
||||||
|
const popupMenuProps = {
|
||||||
|
visible: false,
|
||||||
|
documentUrlPatterns: [`${browser.runtime.getURL("ui/popup")}/*`]
|
||||||
|
} satisfies browser.menus._CreateCreateProperties;
|
||||||
|
|
||||||
browser.menus.create({
|
browser.menus.create({
|
||||||
type: "separator",
|
...popupMenuProps,
|
||||||
parentId: menuIdWhitelist
|
id: MenuId.PopupMediaPlayPause,
|
||||||
});
|
|
||||||
|
|
||||||
// Popup context menus
|
|
||||||
const createPopupMenu = (props: browser.menus._CreateCreateProperties) =>
|
|
||||||
browser.menus.create({
|
|
||||||
visible: false,
|
|
||||||
documentUrlPatterns: [`${browser.runtime.getURL("ui/popup")}/*`],
|
|
||||||
...props
|
|
||||||
});
|
|
||||||
|
|
||||||
createPopupMenu({
|
|
||||||
id: menuIds.POPUP_MEDIA_PLAY_PAUSE,
|
|
||||||
title: _("popupMediaPlay")
|
title: _("popupMediaPlay")
|
||||||
});
|
});
|
||||||
createPopupMenu({
|
browser.menus.create({
|
||||||
id: menuIds.POPUP_MEDIA_MUTE,
|
...popupMenuProps,
|
||||||
|
id: MenuId.PopupMediaMute,
|
||||||
type: "checkbox",
|
type: "checkbox",
|
||||||
title: _("popupMediaMute")
|
title: _("popupMediaMute")
|
||||||
});
|
});
|
||||||
createPopupMenu({
|
browser.menus.create({
|
||||||
id: menuIds.POPUP_MEDIA_SKIP_PREVIOUS,
|
...popupMenuProps,
|
||||||
|
id: MenuId.PopupMediaSkipPrevious,
|
||||||
title: _("popupMediaSkipPrevious")
|
title: _("popupMediaSkipPrevious")
|
||||||
});
|
});
|
||||||
createPopupMenu({
|
browser.menus.create({
|
||||||
id: menuIds.POPUP_MEDIA_SKIP_NEXT,
|
...popupMenuProps,
|
||||||
|
id: MenuId.PopupMediaSkipNext,
|
||||||
title: _("popupMediaSkipNext")
|
title: _("popupMediaSkipNext")
|
||||||
});
|
});
|
||||||
createPopupMenu({
|
browser.menus.create({
|
||||||
id: menuIds.POPUP_MEDIA_CC,
|
...popupMenuProps,
|
||||||
|
id: MenuId.PopupMediaCaptions,
|
||||||
title: _("popupMediaSubtitlesCaptions")
|
title: _("popupMediaSubtitlesCaptions")
|
||||||
});
|
});
|
||||||
createPopupMenu({
|
browser.menus.create({
|
||||||
id: menuIds.POPUP_MEDIA_CC_OFF,
|
...popupMenuProps,
|
||||||
parentId: menuIds.POPUP_MEDIA_CC,
|
id: MenuId.PopupMediaCaptionsOff,
|
||||||
|
parentId: MenuId.PopupMediaCaptions,
|
||||||
type: "radio",
|
type: "radio",
|
||||||
title: _("popupMediaSubtitlesCaptionsOff")
|
title: _("popupMediaSubtitlesCaptionsOff")
|
||||||
});
|
});
|
||||||
|
|
||||||
createPopupMenu({ id: menuIds.POPUP_MEDIA_SEPARATOR, type: "separator" });
|
browser.menus.create({
|
||||||
|
...popupMenuProps,
|
||||||
|
id: MenuId.PopupMediaSeparator,
|
||||||
|
type: "separator"
|
||||||
|
});
|
||||||
|
|
||||||
createPopupMenu({
|
browser.menus.create({
|
||||||
id: menuIds.POPUP_CAST,
|
...popupMenuProps,
|
||||||
|
id: MenuId.PopupCast,
|
||||||
title: _("popupCastButtonTitle"),
|
title: _("popupCastButtonTitle"),
|
||||||
icons: { 16: "icons/icon.svg" }
|
icons: { 16: "icons/icon.svg" }
|
||||||
});
|
});
|
||||||
createPopupMenu({
|
browser.menus.create({
|
||||||
id: menuIds.POPUP_STOP,
|
...popupMenuProps,
|
||||||
|
id: MenuId.PopupStop,
|
||||||
title: _("popupStopButtonTitle")
|
title: _("popupStopButtonTitle")
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -120,14 +132,13 @@ export async function initMenus() {
|
|||||||
const alteredOpts = ev.detail;
|
const alteredOpts = ev.detail;
|
||||||
const newOpts = await options.getAll();
|
const newOpts = await options.getAll();
|
||||||
|
|
||||||
if (menuIdCastMedia && alteredOpts.includes("mediaEnabled")) {
|
if (MenuId.CastMedia && alteredOpts.includes("mediaEnabled")) {
|
||||||
browser.menus.update(menuIdCastMedia, {
|
browser.menus.update(MenuId.CastMedia, {
|
||||||
visible: newOpts.mediaEnabled
|
visible: newOpts.mediaEnabled
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
if (MenuId.CastMedia && alteredOpts.includes("localMediaEnabled")) {
|
||||||
if (menuIdCastMedia && alteredOpts.includes("localMediaEnabled")) {
|
browser.menus.update(MenuId.CastMedia, {
|
||||||
browser.menus.update(menuIdCastMedia, {
|
|
||||||
targetUrlPatterns: newOpts.localMediaEnabled
|
targetUrlPatterns: newOpts.localMediaEnabled
|
||||||
? URL_PATTERNS_ALL
|
? URL_PATTERNS_ALL
|
||||||
: URL_PATTERNS_REMOTE
|
: URL_PATTERNS_REMOTE
|
||||||
@@ -138,10 +149,8 @@ export async function initMenus() {
|
|||||||
|
|
||||||
/** Handle updating menus when shown. */
|
/** Handle updating menus when shown. */
|
||||||
async function onMenuShown(info: browser.menus._OnShownInfo) {
|
async function onMenuShown(info: browser.menus._OnShownInfo) {
|
||||||
const menuIds = info.menuIds as unknown as number[];
|
|
||||||
|
|
||||||
// Only rebuild menus if whitelist menu present
|
// Only rebuild menus if whitelist menu present
|
||||||
if (menuIds.includes(menuIdWhitelist as number)) {
|
if (info.menuIds.includes(MenuId.Whitelist)) {
|
||||||
updateWhitelistMenu(info.pageUrl);
|
updateWhitelistMenu(info.pageUrl);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -153,51 +162,51 @@ async function onMenuClicked(
|
|||||||
tab?: browser.tabs.Tab
|
tab?: browser.tabs.Tab
|
||||||
) {
|
) {
|
||||||
// Handle whitelist menus
|
// Handle whitelist menus
|
||||||
if (info.parentMenuItemId === menuIdWhitelist) {
|
if (info.parentMenuItemId === MenuId.Whitelist) {
|
||||||
const pattern = whitelistChildMenuPatterns.get(info.menuItemId);
|
const pattern = whitelistChildMenuPatterns.get(info.menuItemId);
|
||||||
if (!pattern) {
|
if (!pattern) {
|
||||||
throw logger.error(
|
throw logger.error(
|
||||||
`Whitelist pattern not found for menu item ID ${info.menuItemId}.`
|
`Whitelist pattern not found for menu item ID ${info.menuItemId}.`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const whitelist = await options.get("siteWhitelist");
|
const whitelist = await options.get("siteWhitelist");
|
||||||
if (!whitelist.find(item => item.pattern === pattern)) {
|
if (!whitelist.find(item => item.pattern === pattern)) {
|
||||||
// Add to whitelist and update options
|
// Add to whitelist and update options
|
||||||
whitelist.push({ pattern, isEnabled: true });
|
whitelist.push({ pattern, isEnabled: true });
|
||||||
await options.set("siteWhitelist", whitelist);
|
await options.set("siteWhitelist", whitelist);
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tab?.id === undefined) {
|
if (tab?.id !== undefined) {
|
||||||
logger.error("Menu handler tab ID not found.");
|
switch (info.menuItemId) {
|
||||||
return;
|
case MenuId.Cast: {
|
||||||
}
|
castManager.triggerCast(tab.id, info.frameId);
|
||||||
|
break;
|
||||||
switch (info.menuItemId) {
|
|
||||||
case menuIdCast: {
|
|
||||||
castManager.triggerCast(tab.id, info.frameId);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case menuIdCastMedia:
|
|
||||||
if (info.srcUrl) {
|
|
||||||
await browser.tabs.executeScript(tab.id, {
|
|
||||||
code: stringify`
|
|
||||||
window.mediaUrl = ${info.srcUrl};
|
|
||||||
window.targetElementId = ${info.targetElementId};
|
|
||||||
`,
|
|
||||||
frameId: info.frameId
|
|
||||||
});
|
|
||||||
|
|
||||||
await browser.tabs.executeScript(tab.id, {
|
|
||||||
file: "cast/senders/media.js",
|
|
||||||
frameId: info.frameId
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
|
case MenuId.CastMedia: {
|
||||||
|
if (info.srcUrl) {
|
||||||
|
const frameIds = info.frameId ? [info.frameId] : undefined;
|
||||||
|
await browser.scripting.executeScript({
|
||||||
|
target: { tabId: tab.id, frameIds },
|
||||||
|
func: (
|
||||||
|
mediaUrl: string,
|
||||||
|
targetElementId: number | undefined
|
||||||
|
) => {
|
||||||
|
(window as any).mediaUrl = mediaUrl;
|
||||||
|
(window as any).targetElementId = targetElementId;
|
||||||
|
},
|
||||||
|
args: [info.srcUrl, info.targetElementId]
|
||||||
|
});
|
||||||
|
await browser.scripting.executeScript({
|
||||||
|
target: { tabId: tab.id, frameIds },
|
||||||
|
files: ["cast/senders/media.js"]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -208,7 +217,7 @@ async function updateWhitelistMenu(pageUrl?: string) {
|
|||||||
* to whitelist, so disable the menu and return.
|
* to whitelist, so disable the menu and return.
|
||||||
*/
|
*/
|
||||||
if (!pageUrl) {
|
if (!pageUrl) {
|
||||||
browser.menus.update(menuIdWhitelist, {
|
browser.menus.update(MenuId.Whitelist, {
|
||||||
enabled: false
|
enabled: false
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -225,7 +234,7 @@ async function updateWhitelistMenu(pageUrl?: string) {
|
|||||||
* menu and return.
|
* menu and return.
|
||||||
*/
|
*/
|
||||||
if (!urlHasOrigin) {
|
if (!urlHasOrigin) {
|
||||||
browser.menus.update(menuIdWhitelist, {
|
browser.menus.update(MenuId.Whitelist, {
|
||||||
enabled: false
|
enabled: false
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -234,15 +243,14 @@ async function updateWhitelistMenu(pageUrl?: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Enable the whitelist menu
|
// Enable the whitelist menu
|
||||||
browser.menus.update(menuIdWhitelist, {
|
browser.menus.update(MenuId.Whitelist, {
|
||||||
enabled: true
|
enabled: true
|
||||||
});
|
});
|
||||||
|
|
||||||
for (const [menuId] of whitelistChildMenuPatterns) {
|
for (const [menuId] of whitelistChildMenuPatterns) {
|
||||||
// Clear all page-specific temporary menus
|
// Clear all page-specific temporary menus
|
||||||
if (menuId !== menuIdWhitelistRecommended) {
|
if (menuId !== MenuId.WhitelistRecommended)
|
||||||
browser.menus.remove(menuId);
|
browser.menus.remove(menuId);
|
||||||
}
|
|
||||||
|
|
||||||
whitelistChildMenuPatterns.delete(menuId);
|
whitelistChildMenuPatterns.delete(menuId);
|
||||||
}
|
}
|
||||||
@@ -262,22 +270,23 @@ async function updateWhitelistMenu(pageUrl?: string) {
|
|||||||
const patternWildcardProtocolAndSubdomain = `*://*.${baseDomain}/*`;
|
const patternWildcardProtocolAndSubdomain = `*://*.${baseDomain}/*`;
|
||||||
|
|
||||||
// Update recommended menu item
|
// Update recommended menu item
|
||||||
browser.menus.update(menuIdWhitelistRecommended, {
|
browser.menus.update(MenuId.WhitelistRecommended, {
|
||||||
title: _("contextAddToWhitelistRecommended", patternRecommended)
|
title: _("contextAddToWhitelistRecommended", patternRecommended)
|
||||||
});
|
});
|
||||||
|
|
||||||
whitelistChildMenuPatterns.set(
|
whitelistChildMenuPatterns.set(
|
||||||
menuIdWhitelistRecommended,
|
MenuId.WhitelistRecommended,
|
||||||
patternRecommended
|
patternRecommended
|
||||||
);
|
);
|
||||||
|
|
||||||
if (url.search) {
|
if (url.search) {
|
||||||
const whitelistSearchMenuId = browser.menus.create({
|
whitelistChildMenuPatterns.set(
|
||||||
title: _("contextAddToWhitelistAdvancedAdd", patternSearch),
|
browser.menus.create({
|
||||||
parentId: menuIdWhitelist
|
id: MenuId.WhitelistSearch,
|
||||||
});
|
title: _("contextAddToWhitelistAdvancedAdd", patternSearch),
|
||||||
|
parentId: MenuId.Whitelist
|
||||||
whitelistChildMenuPatterns.set(whitelistSearchMenuId, patternSearch);
|
}),
|
||||||
|
patternSearch
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -300,9 +309,11 @@ async function updateWhitelistMenu(pageUrl?: string) {
|
|||||||
|
|
||||||
const pattern = `${portlessOrigin}/${partialPath}/*`;
|
const pattern = `${portlessOrigin}/${partialPath}/*`;
|
||||||
|
|
||||||
const partialPathMenuId = browser.menus.create({
|
const partialPathMenuId = `${MenuId.WhitelistPath}-${i}`;
|
||||||
|
browser.menus.create({
|
||||||
|
id: partialPathMenuId,
|
||||||
title: _("contextAddToWhitelistAdvancedAdd", pattern),
|
title: _("contextAddToWhitelistAdvancedAdd", pattern),
|
||||||
parentId: menuIdWhitelist
|
parentId: MenuId.Whitelist
|
||||||
});
|
});
|
||||||
|
|
||||||
whitelistChildMenuPatterns.set(partialPathMenuId, pattern);
|
whitelistChildMenuPatterns.set(partialPathMenuId, pattern);
|
||||||
@@ -310,36 +321,38 @@ async function updateWhitelistMenu(pageUrl?: string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const wildcardProtocolMenuId = browser.menus.create({
|
|
||||||
title: _("contextAddToWhitelistAdvancedAdd", patternWildcardProtocol),
|
|
||||||
parentId: menuIdWhitelist
|
|
||||||
});
|
|
||||||
|
|
||||||
whitelistChildMenuPatterns.set(
|
whitelistChildMenuPatterns.set(
|
||||||
wildcardProtocolMenuId,
|
browser.menus.create({
|
||||||
|
id: MenuId.WhitelistWildcardProtocol,
|
||||||
|
title: _(
|
||||||
|
"contextAddToWhitelistAdvancedAdd",
|
||||||
|
patternWildcardProtocol
|
||||||
|
),
|
||||||
|
parentId: MenuId.Whitelist
|
||||||
|
}),
|
||||||
patternWildcardProtocol
|
patternWildcardProtocol
|
||||||
);
|
);
|
||||||
|
|
||||||
const wildcardSubdomainMenuId = browser.menus.create({
|
|
||||||
title: _("contextAddToWhitelistAdvancedAdd", patternWildcardSubdomain),
|
|
||||||
parentId: menuIdWhitelist
|
|
||||||
});
|
|
||||||
|
|
||||||
whitelistChildMenuPatterns.set(
|
whitelistChildMenuPatterns.set(
|
||||||
wildcardSubdomainMenuId,
|
browser.menus.create({
|
||||||
|
id: MenuId.WhitelistWildcardSubdomain,
|
||||||
|
title: _(
|
||||||
|
"contextAddToWhitelistAdvancedAdd",
|
||||||
|
patternWildcardSubdomain
|
||||||
|
),
|
||||||
|
parentId: MenuId.Whitelist
|
||||||
|
}),
|
||||||
patternWildcardSubdomain
|
patternWildcardSubdomain
|
||||||
);
|
);
|
||||||
|
|
||||||
const wildcardProtocolAndSubdomainMenuId = browser.menus.create({
|
|
||||||
title: _(
|
|
||||||
"contextAddToWhitelistAdvancedAdd",
|
|
||||||
patternWildcardProtocolAndSubdomain
|
|
||||||
),
|
|
||||||
parentId: menuIdWhitelist
|
|
||||||
});
|
|
||||||
|
|
||||||
whitelistChildMenuPatterns.set(
|
whitelistChildMenuPatterns.set(
|
||||||
wildcardProtocolAndSubdomainMenuId,
|
browser.menus.create({
|
||||||
|
id: MenuId.WhitelistWildcardProtocolAndSubdomain,
|
||||||
|
title: _(
|
||||||
|
"contextAddToWhitelistAdvancedAdd",
|
||||||
|
patternWildcardProtocolAndSubdomain
|
||||||
|
),
|
||||||
|
parentId: MenuId.Whitelist
|
||||||
|
}),
|
||||||
patternWildcardProtocolAndSubdomain
|
patternWildcardProtocolAndSubdomain
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -194,20 +194,19 @@ async function onBeforeCastSDKRequest(details: OnBeforeRequestDetails) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await browser.tabs.executeScript(details.tabId, {
|
await browser.scripting.executeScript({
|
||||||
code: `
|
target: { tabId: details.tabId, frameIds: [details.frameId] },
|
||||||
window.isFramework = ${
|
func: (isFramework: boolean) => {
|
||||||
details.url === CAST_FRAMEWORK_LOADER_SCRIPT_URL
|
(window as any).isFramework = isFramework;
|
||||||
};
|
},
|
||||||
`,
|
args: [details.url === CAST_FRAMEWORK_LOADER_SCRIPT_URL],
|
||||||
frameId: details.frameId,
|
injectImmediately: true
|
||||||
runAt: "document_start"
|
|
||||||
});
|
});
|
||||||
|
|
||||||
await browser.tabs.executeScript(details.tabId, {
|
await browser.scripting.executeScript({
|
||||||
file: "cast/contentBridge.js",
|
target: { tabId: details.tabId, frameIds: [details.frameId] },
|
||||||
frameId: details.frameId,
|
files: ["cast/contentBridge.js"],
|
||||||
runAt: "document_start"
|
injectImmediately: true
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -255,15 +254,23 @@ async function registerSiteWhitelist() {
|
|||||||
["blocking", "requestHeaders"]
|
["blocking", "requestHeaders"]
|
||||||
);
|
);
|
||||||
|
|
||||||
browser.contentScripts.register({
|
try {
|
||||||
matches: siteWhitelist.map(item => item.pattern),
|
await browser.scripting.unregisterContentScripts({
|
||||||
js: [{ file: "cast/contentInitial.js" }],
|
ids: ["whitelist-content"]
|
||||||
runAt: "document_start",
|
});
|
||||||
allFrames: true
|
} catch {}
|
||||||
});
|
await browser.scripting.registerContentScripts([
|
||||||
|
{
|
||||||
|
id: "whitelist-content",
|
||||||
|
matches: siteWhitelist.map(item => item.pattern),
|
||||||
|
js: ["cast/contentInitial.js"],
|
||||||
|
runAt: "document_start",
|
||||||
|
allFrames: true
|
||||||
|
}
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
function unregisterSiteWhitelist() {
|
async function unregisterSiteWhitelist() {
|
||||||
browser.webRequest.onBeforeSendHeaders.removeListener(
|
browser.webRequest.onBeforeSendHeaders.removeListener(
|
||||||
onWhitelistedBeforeSendHeaders
|
onWhitelistedBeforeSendHeaders
|
||||||
);
|
);
|
||||||
@@ -271,4 +278,9 @@ function unregisterSiteWhitelist() {
|
|||||||
onWhitelistedChildBeforeSendHeaders
|
onWhitelistedChildBeforeSendHeaders
|
||||||
);
|
);
|
||||||
browser.webRequest.onBeforeRequest.removeListener(onBeforeCastSDKRequest);
|
browser.webRequest.onBeforeRequest.removeListener(onBeforeCastSDKRequest);
|
||||||
|
try {
|
||||||
|
await browser.scripting.unregisterContentScripts({
|
||||||
|
ids: ["whitelist-content"]
|
||||||
|
});
|
||||||
|
} catch {}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,7 +23,13 @@ if (document.currentScript) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (currentScriptParams.get("loadCastFramework") === "1") {
|
if (currentScriptParams.get("loadCastFramework") === "1") {
|
||||||
frameworkScriptPromise = loadScript(CAST_FRAMEWORK_SCRIPT_URL);
|
frameworkScriptPromise = new Promise((resolve, reject) => {
|
||||||
|
const scriptEl = document.createElement("script");
|
||||||
|
scriptEl.src = CAST_FRAMEWORK_SCRIPT_URL;
|
||||||
|
(document.head ?? document.documentElement).append(scriptEl);
|
||||||
|
scriptEl.addEventListener("load", () => resolve(scriptEl));
|
||||||
|
scriptEl.addEventListener("error", () => reject());
|
||||||
|
});
|
||||||
frameworkScriptPromise.catch(() => {
|
frameworkScriptPromise.catch(() => {
|
||||||
logger.error("Failed to load CAF script!");
|
logger.error("Failed to load CAF script!");
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ let existingInstance = new CastSDK();
|
|||||||
export default existingInstance;
|
export default existingInstance;
|
||||||
|
|
||||||
interface EnsureInitOpts {
|
interface EnsureInitOpts {
|
||||||
contextTabId?: number;
|
|
||||||
/** Skip receiver selection. */
|
/** Skip receiver selection. */
|
||||||
receiverDevice?: ReceiverDevice;
|
receiverDevice?: ReceiverDevice;
|
||||||
}
|
}
|
||||||
@@ -32,7 +31,7 @@ interface EnsureInitOpts {
|
|||||||
* provides a messaging port so consumers of this module can communicate
|
* provides a messaging port so consumers of this module can communicate
|
||||||
* with the cast manager.
|
* with the cast manager.
|
||||||
*/
|
*/
|
||||||
export function ensureInit(opts: EnsureInitOpts): Promise<CastPort> {
|
export function ensureInit(opts?: EnsureInitOpts): Promise<CastPort> {
|
||||||
return new Promise(async (resolve, reject) => {
|
return new Promise(async (resolve, reject) => {
|
||||||
// If already initialized
|
// If already initialized
|
||||||
if (existingPort) {
|
if (existingPort) {
|
||||||
@@ -40,101 +39,39 @@ export function ensureInit(opts: EnsureInitOpts): Promise<CastPort> {
|
|||||||
existingInstance = new CastSDK();
|
existingInstance = new CastSDK();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
const managerPort = messaging.connect({ name: "trusted-cast" });
|
||||||
* If imported into a background script context, the location
|
|
||||||
* will be the internal extension URL, whereas in a content
|
|
||||||
* script, it will be the content page URL.
|
|
||||||
*/
|
|
||||||
if (
|
|
||||||
window.location.protocol === "moz-extension:" &&
|
|
||||||
window.location.pathname === "_generated_background_page.html"
|
|
||||||
) {
|
|
||||||
const { default: castManager } = await import(
|
|
||||||
"../background/castManager"
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
// Cast manager -> cast instance
|
||||||
* port1 will handle cast manager messages.
|
managerPort.onMessage.addListener(message => {
|
||||||
* port2 will handle cast instance messages.
|
if (message.subject === "cast:instanceCreated") {
|
||||||
*/
|
if (message.data.isAvailable) {
|
||||||
const { port1: managerPort, port2: instancePort } =
|
resolve(pageMessaging.page.messagePort);
|
||||||
new MessageChannel();
|
} else {
|
||||||
|
reject();
|
||||||
/**
|
}
|
||||||
* Provide cast manager with a port to send messages to
|
|
||||||
* cast instance.
|
|
||||||
*/
|
|
||||||
if (opts.contextTabId) {
|
|
||||||
await castManager.createInstance(instancePort, {
|
|
||||||
tabId: opts.contextTabId,
|
|
||||||
frameId: 0
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
await castManager.createInstance(instancePort);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// cast manager -> cast instance
|
pageMessaging.extension.sendMessage(message);
|
||||||
managerPort.addEventListener("message", ev => {
|
});
|
||||||
const message = ev.data as Message;
|
|
||||||
if (message.subject === "cast:instanceCreated") {
|
|
||||||
if (message.data.isAvailable) {
|
|
||||||
resolve(existingPort);
|
|
||||||
} else {
|
|
||||||
reject();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pageMessaging.extension.sendMessage(message);
|
// Cast instance -> cast manager
|
||||||
});
|
pageMessaging.extension.addListener(message => {
|
||||||
managerPort.start();
|
// Skip receiver selection
|
||||||
|
if (opts?.receiverDevice) {
|
||||||
|
message = rewriteTrustedRequestSession(
|
||||||
|
message,
|
||||||
|
opts.receiverDevice
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// Cast instance -> cast manager
|
managerPort.postMessage(message);
|
||||||
pageMessaging.extension.addListener(message => {
|
});
|
||||||
// Skip receiver selection
|
|
||||||
if (opts.receiverDevice) {
|
|
||||||
message = rewriteTrustedRequestSession(
|
|
||||||
message,
|
|
||||||
opts.receiverDevice
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
managerPort.postMessage(message);
|
managerPort.onDisconnect.addListener(() => {
|
||||||
});
|
pageMessaging.extension.close();
|
||||||
} else {
|
});
|
||||||
const managerPort = messaging.connect({ name: "trusted-cast" });
|
|
||||||
|
|
||||||
// Cast manager -> cast instance
|
existingPort = pageMessaging.page.messagePort;
|
||||||
managerPort.onMessage.addListener(message => {
|
|
||||||
if (message.subject === "cast:instanceCreated") {
|
|
||||||
if (message.data.isAvailable) {
|
|
||||||
resolve(pageMessaging.page.messagePort);
|
|
||||||
} else {
|
|
||||||
reject();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pageMessaging.extension.sendMessage(message);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Cast instance -> cast manager
|
|
||||||
pageMessaging.extension.addListener(message => {
|
|
||||||
// Skip receiver selection
|
|
||||||
if (opts.receiverDevice) {
|
|
||||||
message = rewriteTrustedRequestSession(
|
|
||||||
message,
|
|
||||||
opts.receiverDevice
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
managerPort.postMessage(message);
|
|
||||||
});
|
|
||||||
|
|
||||||
managerPort.onDisconnect.addListener(() => {
|
|
||||||
pageMessaging.extension.close();
|
|
||||||
});
|
|
||||||
|
|
||||||
existingPort = pageMessaging.page.messagePort;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ const logger = new Logger("fx_cast [media sender]");
|
|||||||
|
|
||||||
interface MediaSenderOpts {
|
interface MediaSenderOpts {
|
||||||
mediaUrl: string;
|
mediaUrl: string;
|
||||||
contextTabId?: number;
|
|
||||||
mediaElement?: HTMLMediaElement;
|
mediaElement?: HTMLMediaElement;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -22,7 +21,6 @@ export default class MediaSender {
|
|||||||
private port?: CastPort;
|
private port?: CastPort;
|
||||||
|
|
||||||
private mediaUrl: string;
|
private mediaUrl: string;
|
||||||
private contextTabId?: number;
|
|
||||||
|
|
||||||
/** Target media element if loaded as a content script. */
|
/** Target media element if loaded as a content script. */
|
||||||
private mediaElement?: HTMLMediaElement;
|
private mediaElement?: HTMLMediaElement;
|
||||||
@@ -38,7 +36,6 @@ export default class MediaSender {
|
|||||||
|
|
||||||
constructor(opts: MediaSenderOpts) {
|
constructor(opts: MediaSenderOpts) {
|
||||||
this.mediaUrl = opts.mediaUrl;
|
this.mediaUrl = opts.mediaUrl;
|
||||||
this.contextTabId = opts.contextTabId;
|
|
||||||
this.mediaElement = opts.mediaElement;
|
this.mediaElement = opts.mediaElement;
|
||||||
|
|
||||||
this.init();
|
this.init();
|
||||||
@@ -51,7 +48,7 @@ export default class MediaSender {
|
|||||||
|
|
||||||
private async init() {
|
private async init() {
|
||||||
try {
|
try {
|
||||||
this.port = await ensureInit({ contextTabId: this.contextTabId });
|
this.port = await ensureInit();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logger.error("Failed to initialize cast API", err);
|
logger.error("Failed to initialize cast API", err);
|
||||||
}
|
}
|
||||||
|
|||||||
17
extension/src/global.d.ts
vendored
17
extension/src/global.d.ts
vendored
@@ -81,20 +81,3 @@ declare namespace browser.events {
|
|||||||
removeListener(...args: any[]): void | Promise<void>;
|
removeListener(...args: any[]): void | Promise<void>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
declare namespace browser.runtime {
|
|
||||||
interface Port {
|
|
||||||
error?: { message: string };
|
|
||||||
|
|
||||||
/**
|
|
||||||
* https://git.io/fjmzb
|
|
||||||
* addListener cb `() => void` is wrong
|
|
||||||
*/
|
|
||||||
onMessage: browser.events.Event;
|
|
||||||
}
|
|
||||||
|
|
||||||
function connect(connectInfo: {
|
|
||||||
name?: string;
|
|
||||||
includeTlsChannelId?: boolean;
|
|
||||||
}): browser.runtime.Port;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -7,26 +7,6 @@ export function getNextEllipsis(ellipsis: string): string {
|
|||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Template literal tag function, JSON-encodes substitutions.
|
|
||||||
*/
|
|
||||||
export function stringify(
|
|
||||||
templateStrings: TemplateStringsArray,
|
|
||||||
...substitutions: unknown[]
|
|
||||||
) {
|
|
||||||
let formattedString = "";
|
|
||||||
|
|
||||||
for (const templateString of templateStrings) {
|
|
||||||
if (formattedString) {
|
|
||||||
formattedString += JSON.stringify(substitutions.shift());
|
|
||||||
}
|
|
||||||
|
|
||||||
formattedString += templateString;
|
|
||||||
}
|
|
||||||
|
|
||||||
return formattedString;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function loadScript(
|
export function loadScript(
|
||||||
scriptUrl: string,
|
scriptUrl: string,
|
||||||
doc: Document = document
|
doc: Document = document
|
||||||
|
|||||||
@@ -7,14 +7,14 @@
|
|||||||
"url": "https://matt.tf/"
|
"url": "https://matt.tf/"
|
||||||
},
|
},
|
||||||
|
|
||||||
"applications": {
|
"browser_specific_settings": {
|
||||||
"gecko": {
|
"gecko": {
|
||||||
"id": "fx_cast@matt.tf",
|
"id": "fx_cast@matt.tf",
|
||||||
"strict_min_version": "64.0",
|
"strict_min_version": "109.0",
|
||||||
"update_url": "https://hensm.github.io/fx_cast/updates.json"
|
"update_url": "https://hensm.github.io/fx_cast/updates.json"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"browser_action": {
|
"action": {
|
||||||
"theme_icons": [
|
"theme_icons": [
|
||||||
{
|
{
|
||||||
"light": "icons/cast-default-light.svg",
|
"light": "icons/cast-default-light.svg",
|
||||||
@@ -35,7 +35,7 @@
|
|||||||
"96": "icons/icon.svg"
|
"96": "icons/icon.svg"
|
||||||
},
|
},
|
||||||
|
|
||||||
"manifest_version": 2,
|
"manifest_version": 3,
|
||||||
|
|
||||||
"options_ui": {
|
"options_ui": {
|
||||||
"page": "ui/options/index.html"
|
"page": "ui/options/index.html"
|
||||||
@@ -46,12 +46,18 @@
|
|||||||
"menus.overrideContext",
|
"menus.overrideContext",
|
||||||
"nativeMessaging",
|
"nativeMessaging",
|
||||||
"notifications",
|
"notifications",
|
||||||
|
"scripting",
|
||||||
"storage",
|
"storage",
|
||||||
"tabs",
|
"tabs",
|
||||||
"webNavigation",
|
"webNavigation",
|
||||||
"webRequest",
|
"webRequest",
|
||||||
"webRequestBlocking",
|
"webRequestBlocking"
|
||||||
"<all_urls>"
|
|
||||||
],
|
],
|
||||||
"web_accessible_resources": ["cast/content.js"]
|
"host_permissions": ["<all_urls>"],
|
||||||
|
"web_accessible_resources": [
|
||||||
|
{
|
||||||
|
"resources": ["cast/content.js"],
|
||||||
|
"matches": ["<all_urls>"]
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,30 +1,21 @@
|
|||||||
export const POPUP_CAST = "popupCastMenuId";
|
export enum MenuId {
|
||||||
export const POPUP_STOP = "popupStopMenuId";
|
Cast = "cast",
|
||||||
|
CastMedia = "cast_media",
|
||||||
export const POPUP_MEDIA_SEPARATOR = "popupMediaSeparatorMenuId";
|
Whitelist = "whitelist",
|
||||||
export const POPUP_MEDIA_PLAY_PAUSE = "popupMediaPlayPauseMenuId";
|
WhitelistRecommended = "whitelist_recommended",
|
||||||
export const POPUP_MEDIA_MUTE = "popupMediaMuteMenuId";
|
WhitelistSeparator = "whitelist_separator",
|
||||||
export const POPUP_MEDIA_SKIP_PREVIOUS = "popupMediaSkipPreviousMenuId";
|
WhitelistSearch = "whitelist_search",
|
||||||
export const POPUP_MEDIA_SKIP_NEXT = "popupMediaSkipNextMenuId";
|
WhitelistPath = "whitelist_path",
|
||||||
export const POPUP_MEDIA_CC = "popupMediaSubtitlesCaptionsMenuId";
|
WhitelistWildcardProtocol = "whitelist_wildcard_protocol",
|
||||||
export const POPUP_MEDIA_CC_OFF = "popupMediaSubtitlesCaptionsOffMenuId";
|
WhitelistWildcardSubdomain = "whitelist_wildcard_subdomain",
|
||||||
|
WhitelistWildcardProtocolAndSubdomain = "whitelist_wildcard_protocol_and_subdomain",
|
||||||
export const receiverMenuIds = [
|
PopupCast = "popup_cast",
|
||||||
POPUP_CAST,
|
PopupStop = "popup_stop",
|
||||||
POPUP_STOP,
|
PopupMediaSeparator = "popup_media_separator",
|
||||||
POPUP_MEDIA_SEPARATOR,
|
PopupMediaPlayPause = "popup_media_play_pause",
|
||||||
POPUP_MEDIA_PLAY_PAUSE,
|
PopupMediaMute = "popup_media_mute",
|
||||||
POPUP_MEDIA_MUTE,
|
PopupMediaSkipPrevious = "popup_media_skip_previous",
|
||||||
POPUP_MEDIA_SKIP_PREVIOUS,
|
PopupMediaSkipNext = "popup_media_skip_next",
|
||||||
POPUP_MEDIA_SKIP_NEXT,
|
PopupMediaCaptions = "popup_media_captions",
|
||||||
POPUP_MEDIA_CC
|
PopupMediaCaptionsOff = "popup_media_captions_off"
|
||||||
];
|
}
|
||||||
|
|
||||||
export const mediaMenuIds = [
|
|
||||||
POPUP_MEDIA_SEPARATOR,
|
|
||||||
POPUP_MEDIA_PLAY_PAUSE,
|
|
||||||
POPUP_MEDIA_MUTE,
|
|
||||||
POPUP_MEDIA_SKIP_PREVIOUS,
|
|
||||||
POPUP_MEDIA_SKIP_NEXT,
|
|
||||||
POPUP_MEDIA_CC
|
|
||||||
];
|
|
||||||
|
|||||||
@@ -56,8 +56,10 @@
|
|||||||
|
|
||||||
async function onEditKeydown(ev: KeyboardEvent) {
|
async function onEditKeydown(ev: KeyboardEvent) {
|
||||||
key: switch (ev.key) {
|
key: switch (ev.key) {
|
||||||
// Finish editing on enter
|
// Finish editing on enter, stopping propagation so we don't
|
||||||
|
// immediately begin editing again.
|
||||||
case "Enter":
|
case "Enter":
|
||||||
|
ev.stopPropagation();
|
||||||
finishEditing();
|
finishEditing();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -145,10 +147,20 @@
|
|||||||
bind:checked={item.isEnabled}
|
bind:checked={item.isEnabled}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="whitelist__title"
|
class="whitelist__title"
|
||||||
on:dblclick={() => beginEditing(i)}
|
on:dblclick={() => beginEditing(i)}
|
||||||
|
on:keydown={ev => {
|
||||||
|
switch (ev.key) {
|
||||||
|
case "Enter":
|
||||||
|
case " ":
|
||||||
|
ev.preventDefault();
|
||||||
|
beginEditing(i);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
role="button"
|
||||||
|
tabindex="0"
|
||||||
>
|
>
|
||||||
{#if isEditingItem}
|
{#if isEditingItem}
|
||||||
<input
|
<input
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
body {
|
body {
|
||||||
background: var(--box-background) !important;
|
background: var(--box-background) !important;
|
||||||
color: var(--box-color) !important;
|
color: var(--box-color) !important;
|
||||||
|
font-family: system-ui;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
overflow-y: hidden;
|
overflow-y: hidden;
|
||||||
padding: 20px 10px;
|
padding: 20px 10px;
|
||||||
@@ -417,7 +418,7 @@ fieldset:disabled .option__description {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.whitelist__input-pattern {
|
.whitelist__input-pattern {
|
||||||
font: inherit;
|
font-family: monospace;
|
||||||
width: -moz-available;
|
width: -moz-available;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
import options, { type Options } from "../../lib/options";
|
import options, { type Options } from "../../lib/options";
|
||||||
import { RemoteMatchPattern } from "../../lib/matchPattern";
|
import { RemoteMatchPattern } from "../../lib/matchPattern";
|
||||||
|
|
||||||
import { receiverMenuIds } from "../../menuIds";
|
import { MenuId } from "../../menuIds";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
type ReceiverDevice,
|
type ReceiverDevice,
|
||||||
@@ -274,6 +274,17 @@
|
|||||||
/** Device ID associated with the last receiver menu that was shown. */
|
/** Device ID associated with the last receiver menu that was shown. */
|
||||||
let lastMenuShownDeviceId: string;
|
let lastMenuShownDeviceId: string;
|
||||||
|
|
||||||
|
const receiverMenuIds = [
|
||||||
|
MenuId.PopupCast,
|
||||||
|
MenuId.PopupStop,
|
||||||
|
MenuId.PopupMediaSeparator,
|
||||||
|
MenuId.PopupMediaPlayPause,
|
||||||
|
MenuId.PopupMediaMute,
|
||||||
|
MenuId.PopupMediaSkipPrevious,
|
||||||
|
MenuId.PopupMediaSkipNext,
|
||||||
|
MenuId.PopupMediaCaptions
|
||||||
|
];
|
||||||
|
|
||||||
/** Handle show events for receiver context menus. */
|
/** Handle show events for receiver context menus. */
|
||||||
function onMenuShown(info: browser.menus._OnShownInfo) {
|
function onMenuShown(info: browser.menus._OnShownInfo) {
|
||||||
// Only handle menu events on this page
|
// Only handle menu events on this page
|
||||||
@@ -287,10 +298,8 @@
|
|||||||
|
|
||||||
const receiverElement = targetElement.closest(".receiver");
|
const receiverElement = targetElement.closest(".receiver");
|
||||||
if (!receiverElement) {
|
if (!receiverElement) {
|
||||||
for (const menuId of receiverMenuIds) {
|
for (const menuId of receiverMenuIds)
|
||||||
browser.menus.update(menuId, { visible: false });
|
browser.menus.update(menuId, { visible: false });
|
||||||
}
|
|
||||||
|
|
||||||
browser.menus.refresh();
|
browser.menus.refresh();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,10 +4,13 @@
|
|||||||
|
|
||||||
import type { Options } from "../../lib/options";
|
import type { Options } from "../../lib/options";
|
||||||
|
|
||||||
import { type ReceiverDevice, ReceiverDeviceCapabilities } from "../../types";
|
import {
|
||||||
|
type ReceiverDevice,
|
||||||
|
ReceiverDeviceCapabilities
|
||||||
|
} from "../../types";
|
||||||
import type { Port } from "../../messaging";
|
import type { Port } from "../../messaging";
|
||||||
|
|
||||||
import * as menuIds from "../../menuIds";
|
import { MenuId } from "../../menuIds";
|
||||||
|
|
||||||
import type { Volume } from "../../cast/sdk/classes";
|
import type { Volume } from "../../cast/sdk/classes";
|
||||||
import { PlayerState, TrackType } from "../../cast/sdk/media/enums";
|
import { PlayerState, TrackType } from "../../cast/sdk/media/enums";
|
||||||
@@ -169,7 +172,7 @@
|
|||||||
|
|
||||||
lastMenuShownDeviceId = device.id;
|
lastMenuShownDeviceId = device.id;
|
||||||
|
|
||||||
browser.menus.update(menuIds.POPUP_CAST, {
|
browser.menus.update(MenuId.PopupCast, {
|
||||||
visible: true,
|
visible: true,
|
||||||
title: _("popupCastMenuTitle", device.friendlyName),
|
title: _("popupCastMenuTitle", device.friendlyName),
|
||||||
enabled:
|
enabled:
|
||||||
@@ -181,7 +184,7 @@
|
|||||||
isAnyMediaTypeAvailable
|
isAnyMediaTypeAvailable
|
||||||
});
|
});
|
||||||
|
|
||||||
browser.menus.update(menuIds.POPUP_STOP, {
|
browser.menus.update(MenuId.PopupStop, {
|
||||||
visible: !!application && !application.isIdleScreen,
|
visible: !!application && !application.isIdleScreen,
|
||||||
title: application?.displayName
|
title: application?.displayName
|
||||||
? _("popupStopMenuTitle", [
|
? _("popupStopMenuTitle", [
|
||||||
@@ -234,10 +237,10 @@
|
|||||||
if (!isTarget(info)) return;
|
if (!isTarget(info)) return;
|
||||||
|
|
||||||
switch (info.menuItemId) {
|
switch (info.menuItemId) {
|
||||||
case menuIds.POPUP_MEDIA_PLAY_PAUSE:
|
case MenuId.PopupMediaPlayPause:
|
||||||
handleMediaPlayPause();
|
handleMediaPlayPause();
|
||||||
break;
|
break;
|
||||||
case menuIds.POPUP_MEDIA_MUTE:
|
case MenuId.PopupMediaMute:
|
||||||
if (
|
if (
|
||||||
!device.status?.volume.muted &&
|
!device.status?.volume.muted &&
|
||||||
device.status?.volume.level === 0
|
device.status?.volume.level === 0
|
||||||
@@ -247,24 +250,24 @@
|
|||||||
handleVolumeChange({ muted: !device.status?.volume.muted });
|
handleVolumeChange({ muted: !device.status?.volume.muted });
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case menuIds.POPUP_MEDIA_SKIP_PREVIOUS:
|
case MenuId.PopupMediaSkipPrevious:
|
||||||
handleMediaSkipPrevious();
|
handleMediaSkipPrevious();
|
||||||
break;
|
break;
|
||||||
case menuIds.POPUP_MEDIA_SKIP_NEXT:
|
case MenuId.PopupMediaSkipNext:
|
||||||
handleMediaSkipNext();
|
handleMediaSkipNext();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case menuIds.POPUP_CAST:
|
case MenuId.PopupCast:
|
||||||
isConnecting = true;
|
isConnecting = true;
|
||||||
dispatch("cast", { device });
|
dispatch("cast", { device });
|
||||||
break;
|
break;
|
||||||
case menuIds.POPUP_STOP:
|
case MenuId.PopupStop:
|
||||||
dispatch("stop", { device });
|
dispatch("stop", { device });
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle caption submenu items
|
// Handle caption submenu items
|
||||||
if (info.parentMenuItemId === menuIds.POPUP_MEDIA_CC) {
|
if (info.parentMenuItemId === MenuId.PopupMediaCaptions) {
|
||||||
// Filter and append active track IDs array
|
// Filter and append active track IDs array
|
||||||
if (!mediaStatus?.activeTrackIds) return;
|
if (!mediaStatus?.activeTrackIds) return;
|
||||||
const activeTrackIds = mediaStatus.activeTrackIds.filter(
|
const activeTrackIds = mediaStatus.activeTrackIds.filter(
|
||||||
@@ -284,6 +287,15 @@
|
|||||||
browser.menus.overrideContext({ showDefaults: false });
|
browser.menus.overrideContext({ showDefaults: false });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const mediaMenuIds = [
|
||||||
|
MenuId.PopupMediaSeparator,
|
||||||
|
MenuId.PopupMediaPlayPause,
|
||||||
|
MenuId.PopupMediaMute,
|
||||||
|
MenuId.PopupMediaSkipPrevious,
|
||||||
|
MenuId.PopupMediaSkipNext,
|
||||||
|
MenuId.PopupMediaCaptions
|
||||||
|
];
|
||||||
|
|
||||||
/** Updates media menu items from media status. */
|
/** Updates media menu items from media status. */
|
||||||
function updateMediaMenus(shownMenuIds: (number | string)[] = []) {
|
function updateMediaMenus(shownMenuIds: (number | string)[] = []) {
|
||||||
// Clear caption submenu for re-build
|
// Clear caption submenu for re-build
|
||||||
@@ -306,19 +318,18 @@
|
|||||||
|
|
||||||
// Hide all media menu items if no media status
|
// Hide all media menu items if no media status
|
||||||
if (!mediaStatus) {
|
if (!mediaStatus) {
|
||||||
for (const menuId of menuIds.mediaMenuIds) {
|
for (const menuId of mediaMenuIds)
|
||||||
browser.menus.update(menuId, { visible: false });
|
browser.menus.update(menuId, { visible: false });
|
||||||
}
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
browser.menus.update(menuIds.POPUP_MEDIA_SEPARATOR, {
|
browser.menus.update(MenuId.PopupMediaSeparator, {
|
||||||
visible: true
|
visible: true
|
||||||
});
|
});
|
||||||
|
|
||||||
// Play/pause menu item
|
// Play/pause menu item
|
||||||
if (mediaStatus.supportedMediaCommands & _MediaCommand.PAUSE) {
|
if (mediaStatus.supportedMediaCommands & _MediaCommand.PAUSE) {
|
||||||
browser.menus.update(menuIds.POPUP_MEDIA_PLAY_PAUSE, {
|
browser.menus.update(MenuId.PopupMediaPlayPause, {
|
||||||
visible: true,
|
visible: true,
|
||||||
title:
|
title:
|
||||||
mediaStatus.playerState === PlayerState.PLAYING ||
|
mediaStatus.playerState === PlayerState.PLAYING ||
|
||||||
@@ -330,7 +341,7 @@
|
|||||||
mediaStatus.playerState === PlayerState.PAUSED
|
mediaStatus.playerState === PlayerState.PAUSED
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
browser.menus.update(menuIds.POPUP_MEDIA_PLAY_PAUSE, {
|
browser.menus.update(MenuId.PopupMediaPlayPause, {
|
||||||
visible: false
|
visible: false
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -339,24 +350,24 @@
|
|||||||
if (device.status?.volume) {
|
if (device.status?.volume) {
|
||||||
const volume = device.status.volume;
|
const volume = device.status.volume;
|
||||||
|
|
||||||
browser.menus.update(menuIds.POPUP_MEDIA_MUTE, {
|
browser.menus.update(MenuId.PopupMediaMute, {
|
||||||
visible: true,
|
visible: true,
|
||||||
title: _("popupMediaMute"),
|
title: _("popupMediaMute"),
|
||||||
checked: volume.muted || volume.level === 0,
|
checked: volume.muted || volume.level === 0,
|
||||||
enabled: "muted" in volume
|
enabled: "muted" in volume
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
browser.menus.update(menuIds.POPUP_MEDIA_MUTE, {
|
browser.menus.update(MenuId.PopupMediaMute, {
|
||||||
visible: false
|
visible: false
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
browser.menus.update(menuIds.POPUP_MEDIA_SKIP_PREVIOUS, {
|
browser.menus.update(MenuId.PopupMediaSkipPrevious, {
|
||||||
visible: !!(
|
visible: !!(
|
||||||
mediaStatus.supportedMediaCommands & _MediaCommand.QUEUE_PREV
|
mediaStatus.supportedMediaCommands & _MediaCommand.QUEUE_PREV
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
browser.menus.update(menuIds.POPUP_MEDIA_SKIP_NEXT, {
|
browser.menus.update(MenuId.PopupMediaSkipNext, {
|
||||||
visible: !!(
|
visible: !!(
|
||||||
mediaStatus.supportedMediaCommands & _MediaCommand.QUEUE_NEXT
|
mediaStatus.supportedMediaCommands & _MediaCommand.QUEUE_NEXT
|
||||||
)
|
)
|
||||||
@@ -367,8 +378,8 @@
|
|||||||
textTracks?.length &&
|
textTracks?.length &&
|
||||||
mediaStatus.supportedMediaCommands & _MediaCommand.EDIT_TRACKS
|
mediaStatus.supportedMediaCommands & _MediaCommand.EDIT_TRACKS
|
||||||
) {
|
) {
|
||||||
browser.menus.update(menuIds.POPUP_MEDIA_CC, { visible: true });
|
browser.menus.update(MenuId.PopupMediaCaptions, { visible: true });
|
||||||
browser.menus.update(menuIds.POPUP_MEDIA_CC_OFF, {
|
browser.menus.update(MenuId.PopupMediaCaptionsOff, {
|
||||||
visible: true,
|
visible: true,
|
||||||
checked: activeTextTrackId === undefined
|
checked: activeTextTrackId === undefined
|
||||||
});
|
});
|
||||||
@@ -377,7 +388,7 @@
|
|||||||
const menuId = browser.menus.create({
|
const menuId = browser.menus.create({
|
||||||
id: `subtitle-${track.trackId}`,
|
id: `subtitle-${track.trackId}`,
|
||||||
title: track.name ?? track.trackId.toString(),
|
title: track.name ?? track.trackId.toString(),
|
||||||
parentId: menuIds.POPUP_MEDIA_CC,
|
parentId: MenuId.PopupMediaCaptions,
|
||||||
type: "radio",
|
type: "radio",
|
||||||
checked: track.trackId === activeTextTrackId
|
checked: track.trackId === activeTextTrackId
|
||||||
});
|
});
|
||||||
@@ -385,7 +396,7 @@
|
|||||||
captionSubmenus.set(menuId, track.trackId);
|
captionSubmenus.set(menuId, track.trackId);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
browser.menus.update(menuIds.POPUP_MEDIA_CC, {
|
browser.menus.update(MenuId.PopupMediaCaptions, {
|
||||||
visible: false
|
visible: false
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user