More receiver selector refactoring

This commit is contained in:
hensm
2022-04-29 01:14:06 +01:00
parent 70ac18511a
commit b1100bd258
15 changed files with 123 additions and 145 deletions

View File

@@ -1,35 +1,45 @@
"use strict"; "use strict";
import logger from "../../lib/logger"; import logger from "../lib/logger";
import messaging, { Port, Message } from "../../messaging"; import messaging, { Port, Message } from "../messaging";
import options from "../../lib/options"; import options from "../lib/options";
import { TypedEventTarget } from "../../lib/TypedEventTarget";
import { ReceiverDevice } from "../../types";
import { SessionRequest } from "../../cast/sdk/classes";
import { TypedEventTarget } from "../lib/TypedEventTarget";
import { SessionRequest } from "../cast/sdk/classes";
import { import {
ReceiverSelectionCast, ReceiverDevice,
ReceiverSelectionStop, ReceiverSelectionActionType,
ReceiverSelectorMediaType ReceiverSelectorMediaType
} from "./index"; } from "../types";
const POPUP_URL = browser.runtime.getURL("ui/popup/index.html"); const POPUP_URL = browser.runtime.getURL("ui/popup/index.html");
export interface PageInfo { /** Info about sender page context. */
export interface ReceiverSelectorPageInfo {
url: string; url: string;
tabId: number; tabId: number;
frameId: number; frameId: number;
sessionRequest?: SessionRequest; sessionRequest?: SessionRequest;
} }
export interface ReceiverSelectionCast {
actionType: ReceiverSelectionActionType.Cast;
receiverDevice: ReceiverDevice;
mediaType: ReceiverSelectorMediaType;
filePath?: string;
}
export interface ReceiverSelectionStop {
actionType: ReceiverSelectionActionType.Stop;
receiverDevice: ReceiverDevice;
}
export type ReceiverSelection = ReceiverSelectionCast | ReceiverSelectionStop;
interface ReceiverSelectorEvents { interface ReceiverSelectorEvents {
selected: ReceiverSelectionCast; selected: ReceiverSelectionCast;
error: string; error: string;
cancelled: void; cancelled: void;
stop: ReceiverSelectionStop; stop: ReceiverSelectionStop;
} }
/** /**
* Manages the receiver selector popup window and communication with the * Manages the receiver selector popup window and communication with the
* extension page hosted within. * extension page hosted within.
@@ -38,7 +48,7 @@ export default class ReceiverSelector extends TypedEventTarget<ReceiverSelectorE
/** Popup window ID. */ /** Popup window ID. */
private windowId?: number; private windowId?: number;
/** Message port to extension page within popup window. */ /** Message port to extension page. */
private messagePort?: Port; private messagePort?: Port;
private messagePortDisconnected?: boolean; private messagePortDisconnected?: boolean;
@@ -50,9 +60,7 @@ export default class ReceiverSelector extends TypedEventTarget<ReceiverSelectorE
private wasReceiverSelected = false; private wasReceiverSelected = false;
private appId?: string; private appId?: string;
private pageInfo?: PageInfo; private pageInfo?: ReceiverSelectorPageInfo;
#isOpen = false;
constructor() { constructor() {
super(); super();
@@ -63,6 +71,7 @@ export default class ReceiverSelector extends TypedEventTarget<ReceiverSelectorE
this.onWindowsFocusChanged = this.onWindowsFocusChanged.bind(this); this.onWindowsFocusChanged = this.onWindowsFocusChanged.bind(this);
browser.windows.onRemoved.addListener(this.onWindowsRemoved); browser.windows.onRemoved.addListener(this.onWindowsRemoved);
browser.windows.onFocusChanged.addListener(this.onWindowsFocusChanged);
/** /**
* Handle incoming message channel connection from popup * Handle incoming message channel connection from popup
@@ -73,7 +82,7 @@ export default class ReceiverSelector extends TypedEventTarget<ReceiverSelectorE
/** Is receiver selector window currently open. */ /** Is receiver selector window currently open. */
get isOpen() { get isOpen() {
return this.#isOpen; return this.windowId !== undefined;
} }
/** /**
@@ -84,7 +93,7 @@ export default class ReceiverSelector extends TypedEventTarget<ReceiverSelectorE
defaultMediaType: ReceiverSelectorMediaType; defaultMediaType: ReceiverSelectorMediaType;
availableMediaTypes: ReceiverSelectorMediaType; availableMediaTypes: ReceiverSelectorMediaType;
appId?: string; appId?: string;
pageInfo?: PageInfo; pageInfo?: ReceiverSelectorPageInfo;
}) { }) {
this.appId = opts.appId; this.appId = opts.appId;
this.pageInfo = opts.pageInfo; this.pageInfo = opts.pageInfo;
@@ -139,15 +148,7 @@ export default class ReceiverSelector extends TypedEventTarget<ReceiverSelectorE
...popupSizePosition ...popupSizePosition
}); });
this.#isOpen = true;
this.windowId = popup.id; this.windowId = popup.id;
// Add focus listener
if (await options.get("receiverSelectorCloseIfFocusLost")) {
browser.windows.onFocusChanged.addListener(
this.onWindowsFocusChanged
);
}
} }
/** Updates receiver devices displayed in the receiver selector. */ /** Updates receiver devices displayed in the receiver selector. */
@@ -258,8 +259,6 @@ export default class ReceiverSelector extends TypedEventTarget<ReceiverSelectorE
return; return;
} }
this.#isOpen = false;
browser.windows.onRemoved.removeListener(this.onWindowsRemoved); browser.windows.onRemoved.removeListener(this.onWindowsRemoved);
browser.windows.onFocusChanged.removeListener( browser.windows.onFocusChanged.removeListener(
this.onWindowsFocusChanged this.onWindowsFocusChanged
@@ -279,21 +278,18 @@ export default class ReceiverSelector extends TypedEventTarget<ReceiverSelectorE
} }
/** /**
* Closes popup window if another browser window is brought * Closes popup window if another browser window is brought into
* into focus. Doesn't apply if no window is focused * focus. Doesn't apply if no window is focused `WINDOW_ID_NONE`
* `WINDOW_ID_NONE` or if the popup window is re-focused. * or if the popup window is re-focused.
*/ */
private onWindowsFocusChanged(windowId: number) { private async onWindowsFocusChanged(windowId: number) {
if (!this.windowId) return;
if ( if (
windowId !== browser.windows.WINDOW_ID_NONE && windowId !== browser.windows.WINDOW_ID_NONE &&
windowId !== this.windowId windowId !== this.windowId
) { ) {
// Only run once if (await options.get("receiverSelectorCloseIfFocusLost")) {
browser.windows.onFocusChanged.removeListener(
this.onWindowsFocusChanged
);
if (this.windowId) {
browser.windows.remove(this.windowId); browser.windows.remove(this.windowId);
} }
} }

View File

@@ -5,9 +5,9 @@ import logger from "../lib/logger";
import options from "../lib/options"; import options from "../lib/options";
import bridge, { BridgeInfo } from "../lib/bridge"; import bridge, { BridgeInfo } from "../lib/bridge";
import CastManager from "../cast/CastManager"; import castManager from "./castManager";
import receiverDevices from "./receiverDevices"; import deviceManager from "./deviceManager";
import ReceiverSelectorManager from "./receiverSelector/ReceiverSelectorManager"; import selectorManager from "./selectorManager";
import { initMenus } from "./menus"; import { initMenus } from "./menus";
import { initWhitelist } from "./whitelist"; import { initWhitelist } from "./whitelist";
@@ -101,8 +101,8 @@ async function init() {
await notifyBridgeCompat(); await notifyBridgeCompat();
await receiverDevices.init(); await deviceManager.init();
await CastManager.init(); await castManager.init();
await initMenus(); await initMenus();
await initWhitelist(); await initWhitelist();
@@ -119,9 +119,9 @@ async function init() {
return; return;
} }
const selection = await ReceiverSelectorManager.getSelection(tab.id); const selection = await selectorManager.getSelection(tab.id);
if (selection) { if (selection) {
CastManager.loadSender({ castManager.loadSender({
tabId: tab.id, tabId: tab.id,
frameId: 0, frameId: 0,
selection selection

View File

@@ -7,13 +7,14 @@ import options from "../lib/options";
import { stringify } from "../lib/utils"; import { stringify } from "../lib/utils";
import { import {
ReceiverSelection,
ReceiverSelectionActionType, ReceiverSelectionActionType,
ReceiverSelectorMediaType ReceiverSelectorMediaType
} from "../background/receiverSelector"; } from "../types";
import ReceiverSelectorManager from "../background/receiverSelector/ReceiverSelectorManager"; import { ReceiverSelection } from "./ReceiverSelector";
import receiverDevices from "../background/receiverDevices";
import deviceManager from "./deviceManager";
import selectorManager from "./selectorManager";
type AnyPort = Port | MessagePort; type AnyPort = Port | MessagePort;
@@ -26,7 +27,7 @@ export interface CastInstance {
} }
/** Keeps track of cast API instances and provides bridge messaging. */ /** Keeps track of cast API instances and provides bridge messaging. */
export default new (class CastManager { export default new (class {
private activeInstances = new Set<CastInstance>(); private activeInstances = new Set<CastInstance>();
public async init() { public async init() {
@@ -38,7 +39,7 @@ export default new (class CastManager {
}); });
// Forward receiver eventes to cast instances // Forward receiver eventes to cast instances
receiverDevices.addEventListener("receiverDeviceUp", ev => { deviceManager.addEventListener("receiverDeviceUp", ev => {
for (const instance of this.activeInstances) { for (const instance of this.activeInstances) {
instance.contentPort.postMessage({ instance.contentPort.postMessage({
subject: "cast:receiverDeviceUp", subject: "cast:receiverDeviceUp",
@@ -46,7 +47,7 @@ export default new (class CastManager {
}); });
} }
}); });
receiverDevices.addEventListener("receiverDeviceDown", ev => { deviceManager.addEventListener("receiverDeviceDown", ev => {
for (const instance of this.activeInstances) { for (const instance of this.activeInstances) {
instance.contentPort.postMessage({ instance.contentPort.postMessage({
subject: "cast:receiverDeviceDown", subject: "cast:receiverDeviceDown",
@@ -202,7 +203,7 @@ export default new (class CastManager {
case "main:initializeCast": { case "main:initializeCast": {
instance.appId = message.data.appId; instance.appId = message.data.appId;
for (const receiverDevice of receiverDevices.getDevices()) { for (const receiverDevice of deviceManager.getDevices()) {
instance.contentPort.postMessage({ instance.contentPort.postMessage({
subject: "cast:receiverDeviceUp", subject: "cast:receiverDeviceUp",
data: { receiverDevice } data: { receiverDevice }
@@ -224,12 +225,11 @@ export default new (class CastManager {
} }
try { try {
const selection = const selection = await selectorManager.getSelection(
await ReceiverSelectorManager.getSelection( instance.contentTabId,
instance.contentTabId, instance.contentFrameId,
instance.contentFrameId, { sessionRequest: message.data.sessionRequest }
{ sessionRequest: message.data.sessionRequest } );
);
// Handle cancellation // Handle cancellation
if (!selection) { if (!selection) {
@@ -297,7 +297,7 @@ export default new (class CastManager {
* same one that caused the session creation. * same one that caused the session creation.
*/ */
case "main:closeReceiverSelector": { case "main:closeReceiverSelector": {
const selector = await ReceiverSelectorManager.getSelector(); const selector = await selectorManager.getSelector();
const shouldClose = await options.get( const shouldClose = await options.get(
"receiverSelectorWaitForConnection" "receiverSelectorWaitForConnection"
); );
@@ -366,7 +366,7 @@ export default new (class CastManager {
case ReceiverSelectorMediaType.File: { case ReceiverSelectorMediaType.File: {
const fileUrl = new URL(`file://${opts.selection.filePath}`); const fileUrl = new URL(`file://${opts.selection.filePath}`);
const { init } = await import("./senders/media"); const { init } = await import("../cast/senders/media");
init({ init({
mediaUrl: fileUrl.href, mediaUrl: fileUrl.href,

View File

@@ -2,17 +2,17 @@
import logger from "../lib/logger"; import logger from "../lib/logger";
import options from "../lib/options"; import options from "../lib/options";
import { stringify } from "../lib/utils"; import { stringify } from "../lib/utils";
import { import {
ReceiverSelection,
ReceiverSelectionActionType, ReceiverSelectionActionType,
ReceiverSelectorMediaType ReceiverSelectorMediaType
} from "./receiverSelector"; } from "../types";
import ReceiverSelectorManager from "./receiverSelector/ReceiverSelectorManager"; import { ReceiverSelection } from "./ReceiverSelector";
import CastManager from "../cast/CastManager";
import selectorManager from "./selectorManager";
import castManager from "./castManager";
const _ = browser.i18n.getMessage; const _ = browser.i18n.getMessage;
@@ -142,7 +142,7 @@ async function onMenuClicked(
let selection: Nullable<ReceiverSelection> = null; let selection: Nullable<ReceiverSelection> = null;
try { try {
selection = await ReceiverSelectorManager.getSelection( selection = await selectorManager.getSelection(
tab.id, tab.id,
info.frameId, info.frameId,
{ withMediaSender: castMediaMenuClicked } { withMediaSender: castMediaMenuClicked }
@@ -160,7 +160,7 @@ async function onMenuClicked(
} }
if (castMenuClicked) { if (castMenuClicked) {
CastManager.loadSender({ castManager.loadSender({
tabId: tab.id, tabId: tab.id,
frameId: info.frameId, frameId: info.frameId,
selection selection
@@ -187,7 +187,7 @@ async function onMenuClicked(
}); });
} else { } else {
// Handle other responses // Handle other responses
CastManager.loadSender({ castManager.loadSender({
tabId: tab.id, tabId: tab.id,
frameId: info.frameId, frameId: info.frameId,
selection selection

View File

@@ -1,29 +0,0 @@
"use strict";
import { ReceiverDevice } from "../../types";
export enum ReceiverSelectorMediaType {
None = 0,
App = 1,
Tab = 2,
Screen = 4,
File = 8
}
export enum ReceiverSelectionActionType {
Cast = 1,
Stop = 2
}
export interface ReceiverSelectionCast {
actionType: ReceiverSelectionActionType.Cast;
receiverDevice: ReceiverDevice;
mediaType: ReceiverSelectorMediaType;
filePath?: string;
}
export interface ReceiverSelectionStop {
actionType: ReceiverSelectionActionType.Stop;
receiverDevice: ReceiverDevice;
}
export type ReceiverSelection = ReceiverSelectionCast | ReceiverSelectionStop;

View File

@@ -1,23 +1,24 @@
"use strict"; "use strict";
import options from "../../lib/options"; import options from "../lib/options";
import logger from "../../lib/logger"; import logger from "../lib/logger";
import CastManager from "../../cast/CastManager"; import { getMediaTypesForPageUrl } from "../lib/utils";
import { SessionRequest } from "../../cast/sdk/classes"; import { SessionRequest } from "../cast/sdk/classes";
import receiverDevices from "../receiverDevices";
import { getMediaTypesForPageUrl } from "../../lib/utils"; import castManager from "./castManager";
import deviceManager from "./deviceManager";
import ReceiverSelector, {
ReceiverSelection,
ReceiverSelectionCast,
ReceiverSelectionStop
} from "./ReceiverSelector";
import { import {
ReceiverSelection,
ReceiverSelectionActionType, ReceiverSelectionActionType,
ReceiverSelectionCast,
ReceiverSelectionStop,
ReceiverSelectorMediaType ReceiverSelectorMediaType
} from "./index"; } from "../types";
import ReceiverSelector from "./ReceiverSelector";
let sharedSelector: ReceiverSelector; let sharedSelector: ReceiverSelector;
async function getSelector() { async function getSelector() {
@@ -46,12 +47,12 @@ async function getSelection(
contextTabId: number, contextTabId: number,
contextFrameId = 0, contextFrameId = 0,
selectionOpts?: { selectionOpts?: {
sessionRequest: SessionRequest; sessionRequest?: SessionRequest;
withMediaSender?: boolean; withMediaSender?: boolean;
} }
): Promise<ReceiverSelection | null> { ): Promise<ReceiverSelection | null> {
return new Promise(async (resolve, reject) => { return new Promise(async (resolve, reject) => {
let castInstance = CastManager.getInstance( let castInstance = castManager.getInstance(
contextTabId, contextTabId,
contextFrameId contextFrameId
); );
@@ -112,15 +113,12 @@ async function getSelection(
sharedSelector = new ReceiverSelector(); sharedSelector = new ReceiverSelector();
function onReceiverChange() { function onReceiverChange() {
sharedSelector.update(receiverDevices.getDevices()); sharedSelector.update(deviceManager.getDevices());
} }
receiverDevices.addEventListener("receiverDeviceUp", onReceiverChange); deviceManager.addEventListener("receiverDeviceUp", onReceiverChange);
receiverDevices.addEventListener( deviceManager.addEventListener("receiverDeviceDown", onReceiverChange);
"receiverDeviceDown", deviceManager.addEventListener(
onReceiverChange
);
receiverDevices.addEventListener(
"receiverDeviceUpdated", "receiverDeviceUpdated",
onReceiverChange onReceiverChange
); );
@@ -139,7 +137,7 @@ async function getSelection(
function onSelectorStop(ev: CustomEvent<ReceiverSelectionStop>) { function onSelectorStop(ev: CustomEvent<ReceiverSelectionStop>) {
logger.info("Stopping receiver app...", ev.detail); logger.info("Stopping receiver app...", ev.detail);
receiverDevices.stopReceiverApp(ev.detail.receiverDevice.id); deviceManager.stopReceiverApp(ev.detail.receiverDevice.id);
removeListeners(); removeListeners();
resolve({ resolve({
@@ -172,25 +170,25 @@ async function getSelection(
); );
sharedSelector.removeEventListener("error", onSelectorError); sharedSelector.removeEventListener("error", onSelectorError);
receiverDevices.removeEventListener( deviceManager.removeEventListener(
"receiverDeviceUp", "receiverDeviceUp",
onReceiverChange onReceiverChange
); );
receiverDevices.removeEventListener( deviceManager.removeEventListener(
"receiverDeviceDown", "receiverDeviceDown",
onReceiverChange onReceiverChange
); );
receiverDevices.removeEventListener( deviceManager.removeEventListener(
"receiverDeviceUpdated", "receiverDeviceUpdated",
onReceiverChange onReceiverChange
); );
} }
// Ensure status manager is initialized // Ensure status manager is initialized
await receiverDevices.init(); await deviceManager.init();
sharedSelector.open({ sharedSelector.open({
receiverDevices: receiverDevices.getDevices(), receiverDevices: deviceManager.getDevices(),
defaultMediaType, defaultMediaType,
availableMediaTypes, availableMediaTypes,
appId: castInstance?.appId, appId: castInstance?.appId,

View File

@@ -32,7 +32,7 @@ Reflect.defineProperty(HTMLScriptElement.prototype.wrappedJSObject, "src", {
enumerable: true, enumerable: true,
get: desc?.get, get: desc?.get,
set: exportFunction(function setFunc(this: HTMLScriptElement, value) { set: exportFunction(function (this: HTMLScriptElement, value: string) {
if (CAST_SCRIPT_URLS.includes(value)) { if (CAST_SCRIPT_URLS.includes(value)) {
return desc?.set?.call(this, CAST_LOADER_SCRIPT_URL); return desc?.set?.call(this, CAST_LOADER_SCRIPT_URL);
} }

View File

@@ -48,11 +48,13 @@ export function ensureInit(): Promise<TypedMessagePort<Message>> {
* URL. * URL.
*/ */
if (window.location.protocol === "moz-extension:") { if (window.location.protocol === "moz-extension:") {
const { default: CastManager } = await import("./CastManager"); const { default: castManager } = await import(
"../background/castManager"
);
// port2 will post bridge messages to port 1 // port2 will post bridge messages to port 1
await CastManager.init(); await castManager.init();
await CastManager.createInstance(channel.port2); await castManager.createInstance(channel.port2);
// bridge -> cast instance // bridge -> cast instance
channel.port1.onmessage = ev => { channel.port1.onmessage = ev => {

View File

@@ -2,10 +2,7 @@
import logger from "../../lib/logger"; import logger from "../../lib/logger";
import { import { ReceiverDevice, ReceiverDeviceCapabilities } from "../../types";
ReceiverDevice,
ReceiverDeviceCapabilities as ReceiverDeviceCapabilities
} from "../../types";
import { ErrorCallback, SuccessCallback } from "../types"; import { ErrorCallback, SuccessCallback } from "../types";
import eventMessaging from "../eventMessaging"; import eventMessaging from "../eventMessaging";

View File

@@ -3,8 +3,7 @@
import options from "../../lib/options"; import options from "../../lib/options";
import cast, { ensureInit } from "../export"; import cast, { ensureInit } from "../export";
import { ReceiverSelectorMediaType } from "../../background/receiverSelector"; import { ReceiverDevice, ReceiverSelectorMediaType } from "../../types";
import { ReceiverDevice } from "../../types";
import type Session from "../sdk/Session"; import type Session from "../sdk/Session";

View File

@@ -1,6 +1,6 @@
"use strict"; "use strict";
import { ReceiverSelectorMediaType } from "../background/receiverSelector"; import { ReceiverSelectorMediaType } from "../types";
export function getNextEllipsis(ellipsis: string): string { export function getNextEllipsis(ellipsis: string): string {
if (ellipsis === "") return "."; if (ellipsis === "") return ".";

View File

@@ -3,7 +3,6 @@
import { TypedPort } from "./lib/TypedPort"; import { TypedPort } from "./lib/TypedPort";
import { BridgeInfo } from "./lib/bridge"; import { BridgeInfo } from "./lib/bridge";
import { ReceiverSelectorMediaType } from "./background/receiverSelector";
import { import {
ReceiverSelection, ReceiverSelection,
ReceiverSelectionCast, ReceiverSelectionCast,
@@ -19,7 +18,7 @@ import {
} from "./cast/sdk/types"; } from "./cast/sdk/types";
import { SessionRequest } from "./cast/sdk/classes"; import { SessionRequest } from "./cast/sdk/classes";
import { ReceiverDevice } from "./types"; import { ReceiverDevice, ReceiverSelectorMediaType } from "./types";
/** /**
* Messages are JSON objects with a `subject` string key and a * Messages are JSON objects with a `subject` string key and a

View File

@@ -20,3 +20,15 @@ export interface ReceiverDevice {
port: number; port: number;
status?: ReceiverStatus; status?: ReceiverStatus;
} }
export enum ReceiverSelectorMediaType {
None = 0,
App = 1,
Tab = 2,
Screen = 4,
File = 8
}
export enum ReceiverSelectionActionType {
Cast = 1,
Stop = 2
}

View File

@@ -11,14 +11,15 @@ import messaging, { Message, Port } from "../../messaging";
import { getNextEllipsis } from "../../lib/utils"; import { getNextEllipsis } from "../../lib/utils";
import { RemoteMatchPattern } from "../../lib/matchPattern"; import { RemoteMatchPattern } from "../../lib/matchPattern";
import { ReceiverDevice, ReceiverDeviceCapabilities } from "../../types";
import { Capability } from "../../cast/sdk/enums";
import { import {
ReceiverDevice,
ReceiverDeviceCapabilities,
ReceiverSelectionActionType, ReceiverSelectionActionType,
ReceiverSelectorMediaType ReceiverSelectorMediaType
} from "../../background/receiverSelector"; } from "../../types";
import { PageInfo } from "../../background/receiverSelector/ReceiverSelector";
import { ReceiverSelectorPageInfo } from "../../background/ReceiverSelector";
import { Capability } from "../../cast/sdk/enums";
const _ = browser.i18n.getMessage; const _ = browser.i18n.getMessage;
@@ -73,7 +74,7 @@ interface PopupAppState {
filePath?: string; filePath?: string;
appId?: string; appId?: string;
pageInfo?: PageInfo; pageInfo?: ReceiverSelectorPageInfo;
mirroringEnabled: boolean; mirroringEnabled: boolean;
userAgentWhitelistEnabled: boolean; userAgentWhitelistEnabled: boolean;
@@ -405,7 +406,10 @@ class PopupApp extends Component<PopupAppProps, PopupAppState> {
); );
} }
private async onAddToWhitelist(app: KnownApp, pageInfo: PageInfo) { private async onAddToWhitelist(
app: KnownApp,
pageInfo: ReceiverSelectorPageInfo
) {
if (!app.matches) { if (!app.matches) {
return; return;
} }