mirror of
https://github.com/hensm/fx_cast.git
synced 2026-06-11 10:09:59 +00:00
Split up extension background script
This commit is contained in:
@@ -7,21 +7,15 @@ import messaging from "../messaging";
|
|||||||
import options from "../lib/options";
|
import options from "../lib/options";
|
||||||
import bridge, { BridgeInfo } from "../lib/bridge";
|
import bridge, { BridgeInfo } from "../lib/bridge";
|
||||||
|
|
||||||
import { getChromeUserAgent } from "../lib/userAgents";
|
|
||||||
import { getMediaTypesForPageUrl, stringify } from "../lib/utils";
|
|
||||||
|
|
||||||
import { CAST_FRAMEWORK_LOADER_SCRIPT_URL
|
|
||||||
, CAST_LOADER_SCRIPT_URL } from "../lib/endpoints";
|
|
||||||
|
|
||||||
import { ReceiverSelectionActionType
|
|
||||||
, ReceiverSelectorMediaType } from "./receiverSelector";
|
|
||||||
|
|
||||||
import ReceiverSelectorManager
|
import ReceiverSelectorManager
|
||||||
from "./receiverSelector/ReceiverSelectorManager";
|
from "./receiverSelector/ReceiverSelectorManager";
|
||||||
|
|
||||||
import ShimManager from "./ShimManager";
|
import ShimManager from "./ShimManager";
|
||||||
import StatusManager from "./StatusManager";
|
import StatusManager from "./StatusManager";
|
||||||
|
|
||||||
|
import { initMenus } from "./menus";
|
||||||
|
import { initWhitelist } from "./whitelist";
|
||||||
|
|
||||||
|
|
||||||
const _ = browser.i18n.getMessage;
|
const _ = browser.i18n.getMessage;
|
||||||
|
|
||||||
@@ -74,554 +68,6 @@ async function initBrowserAction () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
async function initMenus () {
|
|
||||||
logger.info("init (menus)");
|
|
||||||
|
|
||||||
const URL_PATTERN_HTTP = "http://*/*";
|
|
||||||
const URL_PATTERN_HTTPS = "https://*/*";
|
|
||||||
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 menuIdMediaCast: MenuId;
|
|
||||||
let menuIdWhitelist: MenuId;
|
|
||||||
let menuIdWhitelistRecommended: MenuId;
|
|
||||||
|
|
||||||
const whitelistChildMenuPatterns = new Map<MenuId, string>();
|
|
||||||
|
|
||||||
|
|
||||||
const opts = await options.getAll();
|
|
||||||
|
|
||||||
// Global "Cast..." menu item
|
|
||||||
menuIdCast = browser.menus.create({
|
|
||||||
contexts: [ "browser_action", "page", "tools_menu" ]
|
|
||||||
, title: _("contextCast")
|
|
||||||
});
|
|
||||||
|
|
||||||
// <video>/<audio> "Cast..." context menu item
|
|
||||||
menuIdMediaCast = browser.menus.create({
|
|
||||||
contexts: [ "audio", "video" ]
|
|
||||||
, title: _("contextCast")
|
|
||||||
, visible: opts.mediaEnabled
|
|
||||||
, targetUrlPatterns: opts.localMediaEnabled
|
|
||||||
? URL_PATTERNS_ALL
|
|
||||||
: URL_PATTERNS_REMOTE
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
menuIdWhitelist = browser.menus.create({
|
|
||||||
contexts: [ "browser_action" ]
|
|
||||||
, title: _("contextAddToWhitelist")
|
|
||||||
, enabled: false
|
|
||||||
});
|
|
||||||
|
|
||||||
menuIdWhitelistRecommended = browser.menus.create({
|
|
||||||
title: _("contextAddToWhitelistRecommended")
|
|
||||||
, parentId: menuIdWhitelist
|
|
||||||
});
|
|
||||||
|
|
||||||
browser.menus.create({
|
|
||||||
type: "separator"
|
|
||||||
, parentId: menuIdWhitelist
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
browser.menus.onClicked.addListener(async (info, tab) => {
|
|
||||||
if (info.parentMenuItemId === menuIdWhitelist) {
|
|
||||||
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("userAgentWhitelist");
|
|
||||||
if (!whitelist.includes(pattern)) {
|
|
||||||
// Add to whitelist and update options
|
|
||||||
whitelist.push(pattern);
|
|
||||||
await options.set("userAgentWhitelist", whitelist);
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if (tab?.id === undefined) {
|
|
||||||
throw logger.error("Menu handler tab ID not found.");
|
|
||||||
}
|
|
||||||
if (!info.pageUrl) {
|
|
||||||
throw logger.error("Menu handler page URL not found.");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
const availableMediaTypes = getMediaTypesForPageUrl(info.pageUrl);
|
|
||||||
|
|
||||||
switch (info.menuItemId) {
|
|
||||||
case menuIdCast: {
|
|
||||||
const selection = await ReceiverSelectorManager.getSelection(
|
|
||||||
tab.id, info.frameId);
|
|
||||||
|
|
||||||
// Selection cancelled
|
|
||||||
if (!selection) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
loadSender({
|
|
||||||
tabId: tab.id
|
|
||||||
, frameId: info.frameId
|
|
||||||
, selection
|
|
||||||
});
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case menuIdMediaCast: {
|
|
||||||
const selection = await ReceiverSelectorManager.getSelection(
|
|
||||||
tab.id, info.frameId, true);
|
|
||||||
|
|
||||||
// Selection cancelled
|
|
||||||
if (!selection) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (selection.actionType) {
|
|
||||||
case ReceiverSelectionActionType.Cast: {
|
|
||||||
/**
|
|
||||||
* If the selected media type is App, that refers to the
|
|
||||||
* media sender in this context, so load media sender.
|
|
||||||
*/
|
|
||||||
if (selection.mediaType ===
|
|
||||||
ReceiverSelectorMediaType.App) {
|
|
||||||
|
|
||||||
await browser.tabs.executeScript(tab.id, {
|
|
||||||
code: stringify`
|
|
||||||
window.receiver = ${selection.receiver};
|
|
||||||
window.mediaUrl = ${info.srcUrl};
|
|
||||||
window.targetElementId = ${
|
|
||||||
info.targetElementId};
|
|
||||||
`
|
|
||||||
, frameId: info.frameId
|
|
||||||
});
|
|
||||||
|
|
||||||
await browser.tabs.executeScript(tab.id, {
|
|
||||||
file: "senders/media/bundle.js"
|
|
||||||
, frameId: info.frameId
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
|
|
||||||
// Handle other responses
|
|
||||||
loadSender({
|
|
||||||
tabId: tab.id
|
|
||||||
, frameId: info.frameId
|
|
||||||
, selection
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Hide cast item on extension pages
|
|
||||||
browser.menus.onShown.addListener(info => {
|
|
||||||
if (info.pageUrl?.startsWith(browser.runtime.getURL(""))) {
|
|
||||||
browser.menus.update(menuIdCast, {
|
|
||||||
visible: false
|
|
||||||
});
|
|
||||||
|
|
||||||
browser.menus.refresh();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
browser.menus.onHidden.addListener(() => {
|
|
||||||
browser.menus.update(menuIdCast, {
|
|
||||||
visible: true
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
browser.menus.onShown.addListener(async info => {
|
|
||||||
// Only rebuild menus if whitelist menu present
|
|
||||||
// WebExt typings are broken again here, so ugly casting
|
|
||||||
const menuIds = info.menuIds as unknown as number[];
|
|
||||||
if (!menuIds.includes(menuIdWhitelist as number)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* If page URL doesn't exist, we're not on a page and have
|
|
||||||
* nothing to whitelist, so disable the menu and return.
|
|
||||||
*/
|
|
||||||
if (!info.pageUrl) {
|
|
||||||
browser.menus.update(menuIdWhitelist, {
|
|
||||||
enabled: false
|
|
||||||
});
|
|
||||||
|
|
||||||
browser.menus.refresh();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
const url = new URL(info.pageUrl);
|
|
||||||
const urlHasOrigin = url.origin !== "null";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* If the page URL doesn't have an origin, we're not on a
|
|
||||||
* remote page and have nothing to whitelist, so disable the
|
|
||||||
* menu and return.
|
|
||||||
*/
|
|
||||||
if (!urlHasOrigin) {
|
|
||||||
browser.menus.update(menuIdWhitelist, {
|
|
||||||
enabled: false
|
|
||||||
});
|
|
||||||
|
|
||||||
browser.menus.refresh();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Enable the whitelist menu
|
|
||||||
browser.menus.update(menuIdWhitelist, {
|
|
||||||
enabled: true
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
for (const [ menuId ] of whitelistChildMenuPatterns) {
|
|
||||||
// Clear all page-specific temporary menus
|
|
||||||
if (menuId !== menuIdWhitelistRecommended) {
|
|
||||||
browser.menus.remove(menuId);
|
|
||||||
}
|
|
||||||
|
|
||||||
whitelistChildMenuPatterns.delete(menuId);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// If there is more than one subdomain, get the base domain
|
|
||||||
const baseDomain = (url.hostname.match(/\./g) || []).length > 1
|
|
||||||
? url.hostname.substring(url.hostname.indexOf(".") + 1)
|
|
||||||
: url.hostname;
|
|
||||||
|
|
||||||
const portlessOrigin = `${url.protocol}//${url.hostname}`;
|
|
||||||
|
|
||||||
const patternRecommended = `${portlessOrigin}/*`;
|
|
||||||
const patternSearch = `${portlessOrigin}${url.pathname}${url.search}`;
|
|
||||||
const patternWildcardProtocol = `*://${url.hostname}/*`;
|
|
||||||
const patternWildcardSubdomain = `${url.protocol}//*.${baseDomain}/*`;
|
|
||||||
const patternWildcardProtocolAndSubdomain = `*://*.${baseDomain}/*`;
|
|
||||||
|
|
||||||
|
|
||||||
// Update recommended menu item
|
|
||||||
browser.menus.update(menuIdWhitelistRecommended, {
|
|
||||||
title: _("contextAddToWhitelistRecommended", patternRecommended)
|
|
||||||
});
|
|
||||||
|
|
||||||
whitelistChildMenuPatterns.set(
|
|
||||||
menuIdWhitelistRecommended, patternRecommended);
|
|
||||||
|
|
||||||
|
|
||||||
if (url.search) {
|
|
||||||
const whitelistSearchMenuId = browser.menus.create({
|
|
||||||
title: _("contextAddToWhitelistAdvancedAdd", patternSearch)
|
|
||||||
, parentId: menuIdWhitelist
|
|
||||||
});
|
|
||||||
|
|
||||||
whitelistChildMenuPatterns.set(
|
|
||||||
whitelistSearchMenuId, patternSearch);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Split URL path into segments and add menu items for each
|
|
||||||
* partial path as the segments are removed.
|
|
||||||
*/
|
|
||||||
{
|
|
||||||
const pathTrimmed = url.pathname.endsWith("/")
|
|
||||||
? url.pathname.substring(0, url.pathname.length - 1)
|
|
||||||
: url.pathname;
|
|
||||||
|
|
||||||
const pathSegments = pathTrimmed.split("/")
|
|
||||||
.filter(segment => segment)
|
|
||||||
.reverse();
|
|
||||||
|
|
||||||
if (pathSegments.length) {
|
|
||||||
for (let i = 0; i < pathSegments.length; i++) {
|
|
||||||
const partialPath = pathSegments
|
|
||||||
.slice(i)
|
|
||||||
.reverse()
|
|
||||||
.join("/");
|
|
||||||
|
|
||||||
const pattern = `${portlessOrigin}/${partialPath}/*`;
|
|
||||||
|
|
||||||
const partialPathMenuId = browser.menus.create({
|
|
||||||
title: _("contextAddToWhitelistAdvancedAdd", pattern)
|
|
||||||
, parentId: menuIdWhitelist
|
|
||||||
});
|
|
||||||
|
|
||||||
whitelistChildMenuPatterns.set(
|
|
||||||
partialPathMenuId, pattern);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
const wildcardProtocolMenuId = browser.menus.create({
|
|
||||||
title: _("contextAddToWhitelistAdvancedAdd"
|
|
||||||
, patternWildcardProtocol)
|
|
||||||
, parentId: menuIdWhitelist
|
|
||||||
});
|
|
||||||
|
|
||||||
whitelistChildMenuPatterns.set(
|
|
||||||
wildcardProtocolMenuId, patternWildcardProtocol);
|
|
||||||
|
|
||||||
|
|
||||||
const wildcardSubdomainMenuId = browser.menus.create({
|
|
||||||
title: _("contextAddToWhitelistAdvancedAdd"
|
|
||||||
, patternWildcardSubdomain)
|
|
||||||
, parentId: menuIdWhitelist
|
|
||||||
});
|
|
||||||
|
|
||||||
whitelistChildMenuPatterns.set(
|
|
||||||
wildcardSubdomainMenuId, patternWildcardSubdomain);
|
|
||||||
|
|
||||||
|
|
||||||
const wildcardProtocolAndSubdomainMenuId = browser.menus.create({
|
|
||||||
title: _("contextAddToWhitelistAdvancedAdd"
|
|
||||||
, patternWildcardProtocolAndSubdomain)
|
|
||||||
, parentId: menuIdWhitelist
|
|
||||||
});
|
|
||||||
|
|
||||||
whitelistChildMenuPatterns.set(
|
|
||||||
wildcardProtocolAndSubdomainMenuId
|
|
||||||
, patternWildcardProtocolAndSubdomain);
|
|
||||||
|
|
||||||
|
|
||||||
await browser.menus.refresh();
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
options.addEventListener("changed", async ev => {
|
|
||||||
const alteredOpts = ev.detail;
|
|
||||||
const newOpts = await options.getAll();
|
|
||||||
|
|
||||||
if (alteredOpts.includes("mediaEnabled")) {
|
|
||||||
browser.menus.update(menuIdMediaCast, {
|
|
||||||
visible: newOpts.mediaEnabled
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (alteredOpts.includes("localMediaEnabled")) {
|
|
||||||
browser.menus.update(menuIdMediaCast, {
|
|
||||||
targetUrlPatterns: newOpts.localMediaEnabled
|
|
||||||
? URL_PATTERNS_ALL
|
|
||||||
: URL_PATTERNS_REMOTE
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
async function initWhitelist () {
|
|
||||||
logger.info("init (whitelist)");
|
|
||||||
|
|
||||||
// Missing on @types/firefox-webext-browser
|
|
||||||
type OnBeforeSendHeadersDetails = Parameters<Parameters<
|
|
||||||
typeof browser.webRequest.onBeforeSendHeaders.addListener>[0]>[0] & {
|
|
||||||
frameAncestors?: Array<{ url: string, frameId: number }>
|
|
||||||
};
|
|
||||||
type OnBeforeRequestDetails = Parameters<Parameters<
|
|
||||||
typeof browser.webRequest.onBeforeRequest.addListener>[0]>[0] & {
|
|
||||||
frameAncestors?: Array<{ url: string, frameId: number }>
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
const originUrlCache: string[] = [];
|
|
||||||
|
|
||||||
// TODO: Allow hybrid UA to be configurable
|
|
||||||
const platform = (await browser.runtime.getPlatformInfo()).os;
|
|
||||||
const chromeUserAgent = getChromeUserAgent(platform);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Web apps usually only load the sender library and
|
|
||||||
* provide cast functionality if the browser is detected
|
|
||||||
* as Chrome, so we should rewrite the User-Agent header
|
|
||||||
* to reflect this on whitelisted sites.
|
|
||||||
*/
|
|
||||||
async function onWhitelistedBeforeSendHeaders (
|
|
||||||
details: OnBeforeSendHeadersDetails) {
|
|
||||||
|
|
||||||
if (!details.requestHeaders) {
|
|
||||||
throw logger.error("OnBeforeSendHeaders handler details missing requestHeaders.");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (details.originUrl && !originUrlCache.includes(details.originUrl)) {
|
|
||||||
originUrlCache.push(details.originUrl);
|
|
||||||
}
|
|
||||||
|
|
||||||
const host = details.requestHeaders.find(
|
|
||||||
header => header.name === "Host");
|
|
||||||
|
|
||||||
for (const header of details.requestHeaders) {
|
|
||||||
if (header.name === "User-Agent") {
|
|
||||||
header.value = host?.value === "www.youtube.com"
|
|
||||||
? getChromeUserAgent(platform, true)
|
|
||||||
: chromeUserAgent;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
requestHeaders: details.requestHeaders
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Requests from within child frames should also adopt
|
|
||||||
* the modified User-Agent header to support embedded
|
|
||||||
* players on other origins (like CDN domains) when the
|
|
||||||
* main site is whitelisted.
|
|
||||||
*/
|
|
||||||
function onWhitelistedChildBeforeSendHeaders (
|
|
||||||
details: OnBeforeSendHeadersDetails) {
|
|
||||||
|
|
||||||
if (!details.requestHeaders || !details.frameAncestors) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const ancestor of details.frameAncestors) {
|
|
||||||
if (originUrlCache.includes(ancestor.url)) {
|
|
||||||
const host = details.requestHeaders.find(
|
|
||||||
header => header.name === "Host");
|
|
||||||
|
|
||||||
for (const header of details.requestHeaders) {
|
|
||||||
if (header.name === "User-Agent") {
|
|
||||||
header.value = host?.value === "www.youtube.com"
|
|
||||||
? getChromeUserAgent(platform, true)
|
|
||||||
: chromeUserAgent;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
requestHeaders: details.requestHeaders
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sender applications load a cast_sender.js script that
|
|
||||||
* functions as a loader for the internal chrome-extension:
|
|
||||||
* hosted script.
|
|
||||||
*
|
|
||||||
* We can redirect this and inject our own script to setup
|
|
||||||
* the API shim.
|
|
||||||
*/
|
|
||||||
async function onBeforeCastSDKRequest (details: OnBeforeRequestDetails) {
|
|
||||||
if (!details.originUrl) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check against whitelist if restricted mode is enabled
|
|
||||||
if (await options.get("userAgentWhitelistRestrictedEnabled")) {
|
|
||||||
if (!details?.frameAncestors?.length) {
|
|
||||||
if (!originUrlCache.includes(details.originUrl)) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let hasMatchingAncestor = false;
|
|
||||||
for (const ancestor of details.frameAncestors) {
|
|
||||||
if (originUrlCache.includes(ancestor.url)) {
|
|
||||||
hasMatchingAncestor = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!hasMatchingAncestor) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await browser.tabs.executeScript(details.tabId, {
|
|
||||||
code: `
|
|
||||||
window.isFramework = ${
|
|
||||||
details.url === CAST_FRAMEWORK_LOADER_SCRIPT_URL};
|
|
||||||
`
|
|
||||||
, frameId: details.frameId
|
|
||||||
, runAt: "document_start"
|
|
||||||
});
|
|
||||||
|
|
||||||
await browser.tabs.executeScript(details.tabId, {
|
|
||||||
file: "shim/contentBridge.js"
|
|
||||||
, frameId: details.frameId
|
|
||||||
, runAt: "document_start"
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
redirectUrl: browser.runtime.getURL("shim/bundle.js")
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
async function registerUserAgentWhitelist () {
|
|
||||||
const { userAgentWhitelist
|
|
||||||
, userAgentWhitelistEnabled } = await options.getAll();
|
|
||||||
|
|
||||||
browser.webRequest.onBeforeRequest.addListener(
|
|
||||||
onBeforeCastSDKRequest
|
|
||||||
, { urls: [
|
|
||||||
CAST_LOADER_SCRIPT_URL
|
|
||||||
, CAST_FRAMEWORK_LOADER_SCRIPT_URL ]}
|
|
||||||
, [ "blocking" ]);
|
|
||||||
|
|
||||||
if (!userAgentWhitelistEnabled || !userAgentWhitelist.length) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
browser.webRequest.onBeforeSendHeaders.addListener(
|
|
||||||
onWhitelistedBeforeSendHeaders
|
|
||||||
, { urls: userAgentWhitelist }
|
|
||||||
, [ "blocking", "requestHeaders" ]);
|
|
||||||
|
|
||||||
browser.webRequest.onBeforeSendHeaders.addListener(
|
|
||||||
onWhitelistedChildBeforeSendHeaders
|
|
||||||
, { urls: [ "<all_urls>" ] }
|
|
||||||
, [ "blocking", "requestHeaders" ]);
|
|
||||||
}
|
|
||||||
|
|
||||||
function unregisterUserAgentWhitelist () {
|
|
||||||
originUrlCache.length = 0;
|
|
||||||
|
|
||||||
browser.webRequest.onBeforeSendHeaders
|
|
||||||
.removeListener(onWhitelistedBeforeSendHeaders);
|
|
||||||
browser.webRequest.onBeforeSendHeaders
|
|
||||||
.removeListener(onWhitelistedChildBeforeSendHeaders);
|
|
||||||
browser.webRequest.onBeforeRequest
|
|
||||||
.removeListener(onBeforeCastSDKRequest);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Register on first run
|
|
||||||
await registerUserAgentWhitelist();
|
|
||||||
|
|
||||||
// Re-register when options change
|
|
||||||
options.addEventListener("changed", ev => {
|
|
||||||
const alteredOpts = ev.detail;
|
|
||||||
|
|
||||||
if (alteredOpts.includes("userAgentWhitelist")
|
|
||||||
|| alteredOpts.includes("userAgentWhitelistEnabled")) {
|
|
||||||
unregisterUserAgentWhitelist();
|
|
||||||
registerUserAgentWhitelist();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
async function initMediaOverlay () {
|
async function initMediaOverlay () {
|
||||||
logger.info("init (media overlay)");
|
logger.info("init (media overlay)");
|
||||||
|
|
||||||
|
|||||||
367
ext/src/background/menus.ts
Normal file
367
ext/src/background/menus.ts
Normal file
@@ -0,0 +1,367 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
import loadSender from "../lib/loadSender";
|
||||||
|
import logger from "../lib/logger";
|
||||||
|
import options from "../lib/options";
|
||||||
|
|
||||||
|
import { getMediaTypesForPageUrl, stringify } from "../lib/utils";
|
||||||
|
|
||||||
|
import { ReceiverSelectionActionType
|
||||||
|
, ReceiverSelectorMediaType } from "./receiverSelector";
|
||||||
|
|
||||||
|
import ReceiverSelectorManager
|
||||||
|
from "./receiverSelector/ReceiverSelectorManager";
|
||||||
|
|
||||||
|
const _ = browser.i18n.getMessage;
|
||||||
|
|
||||||
|
|
||||||
|
export async function initMenus () {
|
||||||
|
logger.info("init (menus)");
|
||||||
|
|
||||||
|
const URL_PATTERN_HTTP = "http://*/*";
|
||||||
|
const URL_PATTERN_HTTPS = "https://*/*";
|
||||||
|
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 menuIdMediaCast: MenuId;
|
||||||
|
let menuIdWhitelist: MenuId;
|
||||||
|
let menuIdWhitelistRecommended: MenuId;
|
||||||
|
|
||||||
|
const whitelistChildMenuPatterns = new Map<MenuId, string>();
|
||||||
|
|
||||||
|
|
||||||
|
const opts = await options.getAll();
|
||||||
|
|
||||||
|
// Global "Cast..." menu item
|
||||||
|
menuIdCast = browser.menus.create({
|
||||||
|
contexts: [ "browser_action", "page", "tools_menu" ]
|
||||||
|
, title: _("contextCast")
|
||||||
|
});
|
||||||
|
|
||||||
|
// <video>/<audio> "Cast..." context menu item
|
||||||
|
menuIdMediaCast = browser.menus.create({
|
||||||
|
contexts: [ "audio", "video" ]
|
||||||
|
, title: _("contextCast")
|
||||||
|
, visible: opts.mediaEnabled
|
||||||
|
, targetUrlPatterns: opts.localMediaEnabled
|
||||||
|
? URL_PATTERNS_ALL
|
||||||
|
: URL_PATTERNS_REMOTE
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
menuIdWhitelist = browser.menus.create({
|
||||||
|
contexts: [ "browser_action" ]
|
||||||
|
, title: _("contextAddToWhitelist")
|
||||||
|
, enabled: false
|
||||||
|
});
|
||||||
|
|
||||||
|
menuIdWhitelistRecommended = browser.menus.create({
|
||||||
|
title: _("contextAddToWhitelistRecommended")
|
||||||
|
, parentId: menuIdWhitelist
|
||||||
|
});
|
||||||
|
|
||||||
|
browser.menus.create({
|
||||||
|
type: "separator"
|
||||||
|
, parentId: menuIdWhitelist
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
browser.menus.onClicked.addListener(async (info, tab) => {
|
||||||
|
if (info.parentMenuItemId === menuIdWhitelist) {
|
||||||
|
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("userAgentWhitelist");
|
||||||
|
if (!whitelist.includes(pattern)) {
|
||||||
|
// Add to whitelist and update options
|
||||||
|
whitelist.push(pattern);
|
||||||
|
await options.set("userAgentWhitelist", whitelist);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (tab?.id === undefined) {
|
||||||
|
throw logger.error("Menu handler tab ID not found.");
|
||||||
|
}
|
||||||
|
if (!info.pageUrl) {
|
||||||
|
throw logger.error("Menu handler page URL not found.");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const availableMediaTypes = getMediaTypesForPageUrl(info.pageUrl);
|
||||||
|
|
||||||
|
switch (info.menuItemId) {
|
||||||
|
case menuIdCast: {
|
||||||
|
const selection = await ReceiverSelectorManager.getSelection(
|
||||||
|
tab.id, info.frameId);
|
||||||
|
|
||||||
|
// Selection cancelled
|
||||||
|
if (!selection) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
loadSender({
|
||||||
|
tabId: tab.id
|
||||||
|
, frameId: info.frameId
|
||||||
|
, selection
|
||||||
|
});
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case menuIdMediaCast: {
|
||||||
|
const selection = await ReceiverSelectorManager.getSelection(
|
||||||
|
tab.id, info.frameId, true);
|
||||||
|
|
||||||
|
// Selection cancelled
|
||||||
|
if (!selection) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (selection.actionType) {
|
||||||
|
case ReceiverSelectionActionType.Cast: {
|
||||||
|
/**
|
||||||
|
* If the selected media type is App, that refers to the
|
||||||
|
* media sender in this context, so load media sender.
|
||||||
|
*/
|
||||||
|
if (selection.mediaType ===
|
||||||
|
ReceiverSelectorMediaType.App) {
|
||||||
|
|
||||||
|
await browser.tabs.executeScript(tab.id, {
|
||||||
|
code: stringify`
|
||||||
|
window.receiver = ${selection.receiver};
|
||||||
|
window.mediaUrl = ${info.srcUrl};
|
||||||
|
window.targetElementId = ${
|
||||||
|
info.targetElementId};
|
||||||
|
`
|
||||||
|
, frameId: info.frameId
|
||||||
|
});
|
||||||
|
|
||||||
|
await browser.tabs.executeScript(tab.id, {
|
||||||
|
file: "senders/media/bundle.js"
|
||||||
|
, frameId: info.frameId
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
|
||||||
|
// Handle other responses
|
||||||
|
loadSender({
|
||||||
|
tabId: tab.id
|
||||||
|
, frameId: info.frameId
|
||||||
|
, selection
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Hide cast item on extension pages
|
||||||
|
browser.menus.onShown.addListener(info => {
|
||||||
|
if (info.pageUrl?.startsWith(browser.runtime.getURL(""))) {
|
||||||
|
browser.menus.update(menuIdCast, {
|
||||||
|
visible: false
|
||||||
|
});
|
||||||
|
|
||||||
|
browser.menus.refresh();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
browser.menus.onHidden.addListener(() => {
|
||||||
|
browser.menus.update(menuIdCast, {
|
||||||
|
visible: true
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
browser.menus.onShown.addListener(async info => {
|
||||||
|
// Only rebuild menus if whitelist menu present
|
||||||
|
// WebExt typings are broken again here, so ugly casting
|
||||||
|
const menuIds = info.menuIds as unknown as number[];
|
||||||
|
if (!menuIds.includes(menuIdWhitelist as number)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If page URL doesn't exist, we're not on a page and have
|
||||||
|
* nothing to whitelist, so disable the menu and return.
|
||||||
|
*/
|
||||||
|
if (!info.pageUrl) {
|
||||||
|
browser.menus.update(menuIdWhitelist, {
|
||||||
|
enabled: false
|
||||||
|
});
|
||||||
|
|
||||||
|
browser.menus.refresh();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const url = new URL(info.pageUrl);
|
||||||
|
const urlHasOrigin = url.origin !== "null";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If the page URL doesn't have an origin, we're not on a
|
||||||
|
* remote page and have nothing to whitelist, so disable the
|
||||||
|
* menu and return.
|
||||||
|
*/
|
||||||
|
if (!urlHasOrigin) {
|
||||||
|
browser.menus.update(menuIdWhitelist, {
|
||||||
|
enabled: false
|
||||||
|
});
|
||||||
|
|
||||||
|
browser.menus.refresh();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Enable the whitelist menu
|
||||||
|
browser.menus.update(menuIdWhitelist, {
|
||||||
|
enabled: true
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
for (const [ menuId ] of whitelistChildMenuPatterns) {
|
||||||
|
// Clear all page-specific temporary menus
|
||||||
|
if (menuId !== menuIdWhitelistRecommended) {
|
||||||
|
browser.menus.remove(menuId);
|
||||||
|
}
|
||||||
|
|
||||||
|
whitelistChildMenuPatterns.delete(menuId);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// If there is more than one subdomain, get the base domain
|
||||||
|
const baseDomain = (url.hostname.match(/\./g) || []).length > 1
|
||||||
|
? url.hostname.substring(url.hostname.indexOf(".") + 1)
|
||||||
|
: url.hostname;
|
||||||
|
|
||||||
|
const portlessOrigin = `${url.protocol}//${url.hostname}`;
|
||||||
|
|
||||||
|
const patternRecommended = `${portlessOrigin}/*`;
|
||||||
|
const patternSearch = `${portlessOrigin}${url.pathname}${url.search}`;
|
||||||
|
const patternWildcardProtocol = `*://${url.hostname}/*`;
|
||||||
|
const patternWildcardSubdomain = `${url.protocol}//*.${baseDomain}/*`;
|
||||||
|
const patternWildcardProtocolAndSubdomain = `*://*.${baseDomain}/*`;
|
||||||
|
|
||||||
|
|
||||||
|
// Update recommended menu item
|
||||||
|
browser.menus.update(menuIdWhitelistRecommended, {
|
||||||
|
title: _("contextAddToWhitelistRecommended", patternRecommended)
|
||||||
|
});
|
||||||
|
|
||||||
|
whitelistChildMenuPatterns.set(
|
||||||
|
menuIdWhitelistRecommended, patternRecommended);
|
||||||
|
|
||||||
|
|
||||||
|
if (url.search) {
|
||||||
|
const whitelistSearchMenuId = browser.menus.create({
|
||||||
|
title: _("contextAddToWhitelistAdvancedAdd", patternSearch)
|
||||||
|
, parentId: menuIdWhitelist
|
||||||
|
});
|
||||||
|
|
||||||
|
whitelistChildMenuPatterns.set(
|
||||||
|
whitelistSearchMenuId, patternSearch);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Split URL path into segments and add menu items for each
|
||||||
|
* partial path as the segments are removed.
|
||||||
|
*/
|
||||||
|
{
|
||||||
|
const pathTrimmed = url.pathname.endsWith("/")
|
||||||
|
? url.pathname.substring(0, url.pathname.length - 1)
|
||||||
|
: url.pathname;
|
||||||
|
|
||||||
|
const pathSegments = pathTrimmed.split("/")
|
||||||
|
.filter(segment => segment)
|
||||||
|
.reverse();
|
||||||
|
|
||||||
|
if (pathSegments.length) {
|
||||||
|
for (let i = 0; i < pathSegments.length; i++) {
|
||||||
|
const partialPath = pathSegments
|
||||||
|
.slice(i)
|
||||||
|
.reverse()
|
||||||
|
.join("/");
|
||||||
|
|
||||||
|
const pattern = `${portlessOrigin}/${partialPath}/*`;
|
||||||
|
|
||||||
|
const partialPathMenuId = browser.menus.create({
|
||||||
|
title: _("contextAddToWhitelistAdvancedAdd", pattern)
|
||||||
|
, parentId: menuIdWhitelist
|
||||||
|
});
|
||||||
|
|
||||||
|
whitelistChildMenuPatterns.set(
|
||||||
|
partialPathMenuId, pattern);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const wildcardProtocolMenuId = browser.menus.create({
|
||||||
|
title: _("contextAddToWhitelistAdvancedAdd"
|
||||||
|
, patternWildcardProtocol)
|
||||||
|
, parentId: menuIdWhitelist
|
||||||
|
});
|
||||||
|
|
||||||
|
whitelistChildMenuPatterns.set(
|
||||||
|
wildcardProtocolMenuId, patternWildcardProtocol);
|
||||||
|
|
||||||
|
|
||||||
|
const wildcardSubdomainMenuId = browser.menus.create({
|
||||||
|
title: _("contextAddToWhitelistAdvancedAdd"
|
||||||
|
, patternWildcardSubdomain)
|
||||||
|
, parentId: menuIdWhitelist
|
||||||
|
});
|
||||||
|
|
||||||
|
whitelistChildMenuPatterns.set(
|
||||||
|
wildcardSubdomainMenuId, patternWildcardSubdomain);
|
||||||
|
|
||||||
|
|
||||||
|
const wildcardProtocolAndSubdomainMenuId = browser.menus.create({
|
||||||
|
title: _("contextAddToWhitelistAdvancedAdd"
|
||||||
|
, patternWildcardProtocolAndSubdomain)
|
||||||
|
, parentId: menuIdWhitelist
|
||||||
|
});
|
||||||
|
|
||||||
|
whitelistChildMenuPatterns.set(
|
||||||
|
wildcardProtocolAndSubdomainMenuId
|
||||||
|
, patternWildcardProtocolAndSubdomain);
|
||||||
|
|
||||||
|
|
||||||
|
await browser.menus.refresh();
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
options.addEventListener("changed", async ev => {
|
||||||
|
const alteredOpts = ev.detail;
|
||||||
|
const newOpts = await options.getAll();
|
||||||
|
|
||||||
|
if (alteredOpts.includes("mediaEnabled")) {
|
||||||
|
browser.menus.update(menuIdMediaCast, {
|
||||||
|
visible: newOpts.mediaEnabled
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (alteredOpts.includes("localMediaEnabled")) {
|
||||||
|
browser.menus.update(menuIdMediaCast, {
|
||||||
|
targetUrlPatterns: newOpts.localMediaEnabled
|
||||||
|
? URL_PATTERNS_ALL
|
||||||
|
: URL_PATTERNS_REMOTE
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
205
ext/src/background/whitelist.ts
Normal file
205
ext/src/background/whitelist.ts
Normal file
@@ -0,0 +1,205 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
import logger from "../lib/logger";
|
||||||
|
import options from "../lib/options";
|
||||||
|
|
||||||
|
import { getChromeUserAgent } from "../lib/userAgents";
|
||||||
|
|
||||||
|
import { CAST_FRAMEWORK_LOADER_SCRIPT_URL
|
||||||
|
, CAST_LOADER_SCRIPT_URL } from "../lib/endpoints";
|
||||||
|
|
||||||
|
|
||||||
|
export async function initWhitelist () {
|
||||||
|
logger.info("init (whitelist)");
|
||||||
|
|
||||||
|
// Missing on @types/firefox-webext-browser
|
||||||
|
type OnBeforeSendHeadersDetails = Parameters<Parameters<
|
||||||
|
typeof browser.webRequest.onBeforeSendHeaders.addListener>[0]>[0] & {
|
||||||
|
frameAncestors?: Array<{ url: string, frameId: number }>
|
||||||
|
};
|
||||||
|
type OnBeforeRequestDetails = Parameters<Parameters<
|
||||||
|
typeof browser.webRequest.onBeforeRequest.addListener>[0]>[0] & {
|
||||||
|
frameAncestors?: Array<{ url: string, frameId: number }>
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const originUrlCache: string[] = [];
|
||||||
|
|
||||||
|
// TODO: Allow hybrid UA to be configurable
|
||||||
|
const platform = (await browser.runtime.getPlatformInfo()).os;
|
||||||
|
const chromeUserAgent = getChromeUserAgent(platform);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Web apps usually only load the sender library and
|
||||||
|
* provide cast functionality if the browser is detected
|
||||||
|
* as Chrome, so we should rewrite the User-Agent header
|
||||||
|
* to reflect this on whitelisted sites.
|
||||||
|
*/
|
||||||
|
async function onWhitelistedBeforeSendHeaders (
|
||||||
|
details: OnBeforeSendHeadersDetails) {
|
||||||
|
|
||||||
|
if (!details.requestHeaders) {
|
||||||
|
throw logger.error("OnBeforeSendHeaders handler details missing requestHeaders.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (details.originUrl && !originUrlCache.includes(details.originUrl)) {
|
||||||
|
originUrlCache.push(details.originUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
const host = details.requestHeaders.find(
|
||||||
|
header => header.name === "Host");
|
||||||
|
|
||||||
|
for (const header of details.requestHeaders) {
|
||||||
|
if (header.name === "User-Agent") {
|
||||||
|
header.value = host?.value === "www.youtube.com"
|
||||||
|
? getChromeUserAgent(platform, true)
|
||||||
|
: chromeUserAgent;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
requestHeaders: details.requestHeaders
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Requests from within child frames should also adopt
|
||||||
|
* the modified User-Agent header to support embedded
|
||||||
|
* players on other origins (like CDN domains) when the
|
||||||
|
* main site is whitelisted.
|
||||||
|
*/
|
||||||
|
function onWhitelistedChildBeforeSendHeaders (
|
||||||
|
details: OnBeforeSendHeadersDetails) {
|
||||||
|
|
||||||
|
if (!details.requestHeaders || !details.frameAncestors) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const ancestor of details.frameAncestors) {
|
||||||
|
if (originUrlCache.includes(ancestor.url)) {
|
||||||
|
const host = details.requestHeaders.find(
|
||||||
|
header => header.name === "Host");
|
||||||
|
|
||||||
|
for (const header of details.requestHeaders) {
|
||||||
|
if (header.name === "User-Agent") {
|
||||||
|
header.value = host?.value === "www.youtube.com"
|
||||||
|
? getChromeUserAgent(platform, true)
|
||||||
|
: chromeUserAgent;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
requestHeaders: details.requestHeaders
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sender applications load a cast_sender.js script that
|
||||||
|
* functions as a loader for the internal chrome-extension:
|
||||||
|
* hosted script.
|
||||||
|
*
|
||||||
|
* We can redirect this and inject our own script to setup
|
||||||
|
* the API shim.
|
||||||
|
*/
|
||||||
|
async function onBeforeCastSDKRequest (details: OnBeforeRequestDetails) {
|
||||||
|
if (!details.originUrl) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check against whitelist if restricted mode is enabled
|
||||||
|
if (await options.get("userAgentWhitelistRestrictedEnabled")) {
|
||||||
|
if (!details?.frameAncestors?.length) {
|
||||||
|
if (!originUrlCache.includes(details.originUrl)) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let hasMatchingAncestor = false;
|
||||||
|
for (const ancestor of details.frameAncestors) {
|
||||||
|
if (originUrlCache.includes(ancestor.url)) {
|
||||||
|
hasMatchingAncestor = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hasMatchingAncestor) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await browser.tabs.executeScript(details.tabId, {
|
||||||
|
code: `
|
||||||
|
window.isFramework = ${
|
||||||
|
details.url === CAST_FRAMEWORK_LOADER_SCRIPT_URL};
|
||||||
|
`
|
||||||
|
, frameId: details.frameId
|
||||||
|
, runAt: "document_start"
|
||||||
|
});
|
||||||
|
|
||||||
|
await browser.tabs.executeScript(details.tabId, {
|
||||||
|
file: "shim/contentBridge.js"
|
||||||
|
, frameId: details.frameId
|
||||||
|
, runAt: "document_start"
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
redirectUrl: browser.runtime.getURL("shim/bundle.js")
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async function registerUserAgentWhitelist () {
|
||||||
|
const { userAgentWhitelist
|
||||||
|
, userAgentWhitelistEnabled } = await options.getAll();
|
||||||
|
|
||||||
|
browser.webRequest.onBeforeRequest.addListener(
|
||||||
|
onBeforeCastSDKRequest
|
||||||
|
, { urls: [
|
||||||
|
CAST_LOADER_SCRIPT_URL
|
||||||
|
, CAST_FRAMEWORK_LOADER_SCRIPT_URL ]}
|
||||||
|
, [ "blocking" ]);
|
||||||
|
|
||||||
|
if (!userAgentWhitelistEnabled || !userAgentWhitelist.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
browser.webRequest.onBeforeSendHeaders.addListener(
|
||||||
|
onWhitelistedBeforeSendHeaders
|
||||||
|
, { urls: userAgentWhitelist }
|
||||||
|
, [ "blocking", "requestHeaders" ]);
|
||||||
|
|
||||||
|
browser.webRequest.onBeforeSendHeaders.addListener(
|
||||||
|
onWhitelistedChildBeforeSendHeaders
|
||||||
|
, { urls: [ "<all_urls>" ] }
|
||||||
|
, [ "blocking", "requestHeaders" ]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function unregisterUserAgentWhitelist () {
|
||||||
|
originUrlCache.length = 0;
|
||||||
|
|
||||||
|
browser.webRequest.onBeforeSendHeaders
|
||||||
|
.removeListener(onWhitelistedBeforeSendHeaders);
|
||||||
|
browser.webRequest.onBeforeSendHeaders
|
||||||
|
.removeListener(onWhitelistedChildBeforeSendHeaders);
|
||||||
|
browser.webRequest.onBeforeRequest
|
||||||
|
.removeListener(onBeforeCastSDKRequest);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register on first run
|
||||||
|
await registerUserAgentWhitelist();
|
||||||
|
|
||||||
|
// Re-register when options change
|
||||||
|
options.addEventListener("changed", ev => {
|
||||||
|
const alteredOpts = ev.detail;
|
||||||
|
|
||||||
|
if (alteredOpts.includes("userAgentWhitelist")
|
||||||
|
|| alteredOpts.includes("userAgentWhitelistEnabled")) {
|
||||||
|
unregisterUserAgentWhitelist();
|
||||||
|
registerUserAgentWhitelist();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user