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