Add site-specific custom user agent option and new whitelist option UI

This commit is contained in:
hensm
2022-08-08 17:29:14 +01:00
parent 0c8106ffef
commit 23a8c0b62f
7 changed files with 194 additions and 41 deletions

View File

@@ -329,10 +329,6 @@
"message": "Add Item", "message": "Add Item",
"description": "Add new whitelist item button title." "description": "Add new whitelist item button title."
}, },
"optionsSiteWhitelistUserAgent": {
"message": "Disable UA",
"description": "Whitelist item user agent checkbox title."
},
"optionsSiteWhitelistEditItem": { "optionsSiteWhitelistEditItem": {
"message": "Edit", "message": "Edit",
"description": "Edit whitelist item button title. Displayed on each item." "description": "Edit whitelist item button title. Displayed on each item."
@@ -350,13 +346,29 @@
"description": "Default <option> for knownApps <select>." "description": "Default <option> for knownApps <select>."
}, },
"optionsSiteWhitelistCustomUserAgent": { "optionsSiteWhitelistCustomUserAgent": {
"message": "Custom user agent:", "message": "User agent:",
"description": "Custom user agent option label." "description": "Custom user agent option label."
}, },
"optionsSiteWhitelistCustomUserAgentDescription": { "optionsSiteWhitelistCustomUserAgentDescription": {
"message": "If specified, a custom user agent string to use for whitelisted sites.", "message": "If specified, a custom user agent string to use for whitelisted sites.",
"description": "Custom user agent option description." "description": "Custom user agent option description."
}, },
"optionsSiteWhitelistUserAgentDisabled": {
"message": "Disable user agent",
"description": "Whitelist item user agent disabled checkbox label."
},
"optionsSiteWhitelistUserAgentDisabledDescription": {
"message": "Entirely disable user agent replacement for sites matching this pattern.",
"description": "Whitelist item user agent disabled checkbox description."
},
"optionsSiteWhitelistSiteSpecificUserAgent": {
"message": "User agent:",
"description": "Whitelist item user agent option label."
},
"optionsSiteWhitelistSiteSpecificUserAgentDescription": {
"message": "If specified, a custom user agent string to use specifically for this sites matching this pattern.",
"description": "Whitelist item user agent option label."
},
"optionsMirroringCategoryName": { "optionsMirroringCategoryName": {
"message": "Screen/tab casting", "message": "Screen/tab casting",

View File

@@ -4,6 +4,7 @@ import logger from "../lib/logger";
import options from "../lib/options"; import options from "../lib/options";
import { getChromeUserAgent } from "../lib/userAgents"; import { getChromeUserAgent } from "../lib/userAgents";
import { RemoteMatchPattern } from "../lib/matchPattern";
import { import {
CAST_FRAMEWORK_LOADER_SCRIPT_URL, CAST_FRAMEWORK_LOADER_SCRIPT_URL,
@@ -25,6 +26,7 @@ type OnBeforeRequestDetails = Parameters<
export interface WhitelistItemData { export interface WhitelistItemData {
pattern: string; pattern: string;
isUserAgentDisabled?: boolean; isUserAgentDisabled?: boolean;
customUserAgent?: string;
} }
const originUrlCache: string[] = []; const originUrlCache: string[] = [];
@@ -33,6 +35,8 @@ let platform: string;
let chromeUserAgent: string | undefined; let chromeUserAgent: string | undefined;
let chromeUserAgentHybrid: string | undefined; let chromeUserAgentHybrid: string | undefined;
let siteWhitelistEnabled = false;
let siteWhitelist: Nullable<WhitelistItemData[]> = null;
let customUserAgent: string | undefined; let customUserAgent: string | undefined;
export async function initWhitelist() { export async function initWhitelist() {
@@ -65,7 +69,6 @@ export async function initWhitelist() {
if (alteredOpts.includes("siteWhitelistCustomUserAgent")) { if (alteredOpts.includes("siteWhitelistCustomUserAgent")) {
customUserAgent = await options.get("siteWhitelistCustomUserAgent"); customUserAgent = await options.get("siteWhitelistCustomUserAgent");
} }
if ( if (
alteredOpts.includes("siteWhitelist") || alteredOpts.includes("siteWhitelist") ||
alteredOpts.includes("siteWhitelistEnabled") alteredOpts.includes("siteWhitelistEnabled")
@@ -76,6 +79,30 @@ export async function initWhitelist() {
}); });
} }
/**
* Returns the configured user agent matching the specified URL or
* undefined if the user agent is disabled.
*/
function getUserAgent(url: string, host?: string): Optional<string> {
if (!siteWhitelistEnabled || !siteWhitelist) return;
// Search site-specific user agents
const matchingItem = siteWhitelist.find(
item =>
item.customUserAgent &&
new RemoteMatchPattern(item.pattern).matches(url)
);
if (matchingItem) {
if (matchingItem.isUserAgentDisabled) return;
return matchingItem.customUserAgent;
}
return (
customUserAgent ||
(host === "www.youtube.com" ? chromeUserAgentHybrid : chromeUserAgent)
);
}
/** /**
* Web apps usually only load the sender library and * Web apps usually only load the sender library and
* provide cast functionality if the browser is detected * provide cast functionality if the browser is detected
@@ -99,11 +126,7 @@ async function onWhitelistedBeforeSendHeaders(
for (const header of details.requestHeaders) { for (const header of details.requestHeaders) {
if (header.name === "User-Agent") { if (header.name === "User-Agent") {
header.value = header.value = getUserAgent(details.url, host?.value);
customUserAgent ||
(host?.value === "www.youtube.com"
? chromeUserAgentHybrid
: chromeUserAgent);
break; break;
} }
} }
@@ -134,11 +157,7 @@ function onWhitelistedChildBeforeSendHeaders(
for (const header of details.requestHeaders) { for (const header of details.requestHeaders) {
if (header.name === "User-Agent") { if (header.name === "User-Agent") {
header.value = header.value = getUserAgent(details.url, host?.value);
customUserAgent ||
(host?.value === "www.youtube.com"
? chromeUserAgentHybrid
: chromeUserAgent);
break; break;
} }
} }
@@ -205,7 +224,9 @@ async function onBeforeCastSDKRequest(details: OnBeforeRequestDetails) {
} }
async function registerSiteWhitelist() { async function registerSiteWhitelist() {
const { siteWhitelist, siteWhitelistEnabled } = await options.getAll(); const opts = await options.getAll();
siteWhitelist = opts.siteWhitelist;
siteWhitelistEnabled = opts.siteWhitelistEnabled;
browser.webRequest.onBeforeRequest.addListener( browser.webRequest.onBeforeRequest.addListener(
onBeforeCastSDKRequest, onBeforeCastSDKRequest,

View File

@@ -18,11 +18,12 @@
let isFormValid = true; let isFormValid = true;
let isSavedIndicatorVisible = false; let isSavedIndicatorVisible = false;
let platform: string; let defaultUserAgent: Optional<string>;
let opts: Options | undefined; let opts: Options | undefined;
onMount(async () => { onMount(async () => {
platform = (await browser.runtime.getPlatformInfo()).os; const platform = (await browser.runtime.getPlatformInfo()).os;
defaultUserAgent = getChromeUserAgent(platform);
opts = await options.getAll(); opts = await options.getAll();
options.addEventListener("changed", async () => { options.addEventListener("changed", async () => {
@@ -45,6 +46,9 @@
if (item.isUserAgentDisabled === false) { if (item.isUserAgentDisabled === false) {
delete item.isUserAgentDisabled; delete item.isUserAgentDisabled;
} }
if (item.customUserAgent === "") {
delete item.customUserAgent;
}
} }
await options.setAll(opts); await options.setAll(opts);
@@ -68,7 +72,7 @@
} }
</script> </script>
{#if opts && platform} {#if opts}
<form <form
id="form" id="form"
bind:this={formElement} bind:this={formElement}
@@ -281,7 +285,7 @@
id="siteWhitelistCustomUserAgent" id="siteWhitelistCustomUserAgent"
type="text" type="text"
bind:value={opts.siteWhitelistCustomUserAgent} bind:value={opts.siteWhitelistCustomUserAgent}
placeholder={getChromeUserAgent(platform)} placeholder={defaultUserAgent}
/> />
<div class="option__description"> <div class="option__description">
{_("optionsSiteWhitelistCustomUserAgentDescription")} {_("optionsSiteWhitelistCustomUserAgentDescription")}
@@ -294,7 +298,11 @@
{_("optionsSiteWhitelistContent")} {_("optionsSiteWhitelistContent")}
</div> </div>
<div class="option__control"> <div class="option__control">
<Whitelist bind:items={opts.siteWhitelist} /> <Whitelist
bind:items={opts.siteWhitelist}
{opts}
{defaultUserAgent}
/>
</div> </div>
</div> </div>
</fieldset> </fieldset>

View File

@@ -4,11 +4,14 @@
import knownApps, { KnownApp } from "../../cast/knownApps"; import knownApps, { KnownApp } from "../../cast/knownApps";
import { WhitelistItemData } from "../../background/whitelist"; import { WhitelistItemData } from "../../background/whitelist";
import { REMOTE_MATCH_PATTERN_REGEX } from "../../lib/matchPattern"; import { REMOTE_MATCH_PATTERN_REGEX } from "../../lib/matchPattern";
import { Options } from "../../lib/options";
const _ = browser.i18n.getMessage; const _ = browser.i18n.getMessage;
/** Whitelist items to display. */ /** Whitelist items to display. */
export let items: WhitelistItemData[]; export let items: WhitelistItemData[];
export let opts: Options;
export let defaultUserAgent: Optional<string>;
let isEditing = false; let isEditing = false;
let isEditingValid = false; let isEditingValid = false;
@@ -16,6 +19,8 @@
let editingInput: HTMLInputElement; let editingInput: HTMLInputElement;
let editingValue: string; let editingValue: string;
let expandedItemIndices = new Set();
let knownAppToAdd: Nullable<KnownApp> = null; let knownAppToAdd: Nullable<KnownApp> = null;
$: filteredKnownApps = Object.values(knownApps).filter(app => { $: filteredKnownApps = Object.values(knownApps).filter(app => {
// If no pattern or name matches default media sender // If no pattern or name matches default media sender
@@ -115,13 +120,17 @@
<ul class="whitelist__items"> <ul class="whitelist__items">
{#each items as item, i} {#each items as item, i}
{@const isEditingItem = isEditing && editingIndex === i} {@const isEditingItem = isEditing && editingIndex === i}
{@const isItemExpanded = expandedItemIndices.has(i)}
<li <li
class="whitelist__item" class="whitelist__item"
class:whitelist__item--selected={isEditingItem} class:whitelist__item--selected={isEditingItem}
on:dblclick={() => beginEditing(i)} class:whitelist__item--expanded={isItemExpanded}
> >
<div class="whitelist__title"> <div
class="whitelist__title"
on:dblclick={() => beginEditing(i)}
>
{#if isEditingItem} {#if isEditingItem}
<input <input
type="text" type="text"
@@ -150,13 +159,6 @@
</div> </div>
{#if !isEditingItem} {#if !isEditingItem}
<label class="whitelist__user-agent">
<input
type="checkbox"
bind:checked={item.isUserAgentDisabled}
/>
{_("optionsSiteWhitelistUserAgent")}
</label>
<button <button
type="button" type="button"
class="whitelist__edit-button ghost" class="whitelist__edit-button ghost"
@@ -177,6 +179,80 @@
> >
<img src="assets/photon_delete.svg" alt="icon, remove" /> <img src="assets/photon_delete.svg" alt="icon, remove" />
</button> </button>
{#if !isEditingItem}
<button
type="button"
class="whitelist__expand-button ghost"
title={_("optionsSiteWhitelistRemoveItem")}
on:click={() => {
// Toggle expanded state
if (isItemExpanded) {
expandedItemIndices.delete(i);
} else {
expandedItemIndices.add(i);
}
expandedItemIndices = expandedItemIndices;
}}
>
<img
src="assets/{isItemExpanded
? 'photon_arrowhead_up.svg'
: 'photon_arrowhead_down.svg'}"
alt="icon, arrow down"
/>
</button>
{#if isItemExpanded}
<div class="whitelist__expanded">
<div class="option option--inline">
<div class="option__control">
<input
id="isUserAgentDisabled-{i}"
type="checkbox"
bind:checked={item.isUserAgentDisabled}
/>
</div>
<label
class="option__label"
for="isUserAgentDisabled-{i}"
>
{_("optionsSiteWhitelistUserAgentDisabled")}
</label>
<div class="option__description">
{_(
"optionsSiteWhitelistUserAgentDisabledDescription"
)}
</div>
</div>
<div class="option">
<label
class="option__label"
for="customUserAgentString-{i}"
>
{_(
"optionsSiteWhitelistSiteSpecificUserAgent"
)}
</label>
<div class="option__control">
<input
id="customUserAgentString-{i}"
type="text"
bind:value={item.customUserAgent}
placeholder={opts.siteWhitelistCustomUserAgent ||
defaultUserAgent}
/>
<div class="option__description">
{_(
"optionsSiteWhitelistSiteSpecificUserAgentDescription"
)}
</div>
</div>
</div>
</div>
{/if}
{/if}
</li> </li>
{/each} {/each}
</ul> </ul>

View File

@@ -0,0 +1,13 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
<style>
@media (prefers-color-scheme: dark) {
path {
fill: rgba(249, 249, 250, .8);
}
}
</style>
<path fill="rgba(12, 12, 13, .8)" d="M8 12a1 1 0 0 1-.707-.293l-5-5a1 1 0 0 1 1.414-1.414L8 9.586l4.293-4.293a1 1 0 0 1 1.414 1.414l-5 5A1 1 0 0 1 8 12z"></path>
</svg>

After

Width:  |  Height:  |  Size: 629 B

View File

@@ -0,0 +1,13 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
<style>
@media (prefers-color-scheme: dark) {
path {
fill: rgba(249, 249, 250, .8);
}
}
</style>
<path fill="rgba(12, 12, 13, .8)" d="M13 11a1 1 0 0 1-.707-.293L8 6.414l-4.293 4.293a1 1 0 0 1-1.414-1.414l5-5a1 1 0 0 1 1.414 0l5 5A1 1 0 0 1 13 11z"></path>
</svg>

After

Width:  |  Height:  |  Size: 625 B

View File

@@ -1,11 +1,11 @@
:root { :root {
--border-color: rgb(225, 225, 225); --border-color: var(--grey-90-a20);
--secondary-color: rgb(125, 125, 125); --secondary-color: rgb(125, 125, 125);
} }
@media (prefers-color-scheme: dark) { @media (prefers-color-scheme: dark) {
:root { :root {
--border-color: var(--grey-50); --border-color: var(--grey-10-a20);
--secondary-color: var(--grey-10-a60); --secondary-color: var(--grey-10-a60);
} }
} }
@@ -223,10 +223,6 @@ button.ghost:not(:hover) {
padding: 10px 0; padding: 10px 0;
} }
.category:disabled {
color: var(--secondary-color);
}
#form > .category { #form > .category {
border-bottom: 1px solid var(--border-color); border-bottom: 1px solid var(--border-color);
} }
@@ -350,13 +346,13 @@ button.ghost:not(:hover) {
.whitelist__item { .whitelist__item {
align-items: center; align-items: center;
display: flex; display: flex;
gap: 5px; flex-wrap: wrap;
height: 34px; column-gap: 5px;
padding: 0 10px; padding: 0 10px;
} }
.whitelist__item:nth-child(even) { .whitelist__item:nth-child(even) {
background-color: rgba(0, 0, 0, 0.05); background-color: rgba(0, 0, 0, 0.1);
} }
.whitelist__item--selected { .whitelist__item--selected {
@@ -365,7 +361,9 @@ button.ghost:not(:hover) {
.whitelist__title { .whitelist__title {
display: flex; display: flex;
align-items: center;
flex: 1; flex: 1;
min-height: 34px;
min-width: 0; min-width: 0;
padding: 4px; padding: 4px;
white-space: nowrap; white-space: nowrap;
@@ -389,6 +387,16 @@ button.ghost:not(:hover) {
margin-inline-end: auto; margin-inline-end: auto;
} }
.whitelist__expanded {
border-top: 1px solid var(--border-color);
display: grid;
grid-template-columns: 80px minmax(0, 1fr);
grid-column-gap: 10px;
grid-row-gap: 5px;
padding: 10px;
width: 100%;
}
.translator__tag { .translator__tag {
color: #0a84ff; color: #0a84ff;
display: inline-block; display: inline-block;
@@ -399,7 +407,9 @@ button.ghost:not(:hover) {
vertical-align: text-top; vertical-align: text-top;
} }
#siteWhitelistCustomUserAgent { /* Option specific styles */
#siteWhitelistCustomUserAgent,
input[id^="customUserAgentString-"] {
width: -moz-available; width: -moz-available;
} }