mirror of
https://github.com/hensm/fx_cast.git
synced 2026-06-08 08:39:59 +00:00
Clean up shim initialization
This commit is contained in:
@@ -2,13 +2,13 @@
|
|||||||
|
|
||||||
A bridge application instance (`statusBridge`) is created to keep track of receivers’ statuses. This is expected to exist throughout the lifetime of the extension and will automatically reconnect if unexpectedly disconnected.
|
A bridge application instance (`statusBridge`) is created to keep track of receivers’ statuses. This is expected to exist throughout the lifetime of the extension and will automatically reconnect if unexpectedly disconnected.
|
||||||
|
|
||||||
The `shim/contentSetup.ts` content script is registered for all pages. It creates an empty `window.chrome` object in the page context since some sites may expect it to exist. It also intercepts any `src` attribute changes on `<script>` elements where the cast API may be loaded directly from a `chrome-extension://` URL, then sets them to the regular cast API script URL.
|
The `shim/content.ts` content script is registered for all pages. It creates an empty `window.chrome` object in the page context since some sites may expect it to exist. It also intercepts any `src` attribute changes on `<script>` elements where the cast API may be loaded directly from a `chrome-extension://` URL, then sets them to the regular cast API script URL.
|
||||||
|
|
||||||
## Shim Initialization
|
## Shim Initialization
|
||||||
|
|
||||||
The background script registers a `webRequest.onBeforeRequest` listener that intercepts requests to Google’s Cast API library.
|
The background script registers a `webRequest.onBeforeRequest` listener that intercepts requests to Google’s Cast API library.
|
||||||
|
|
||||||
When a request is intercepted, the `shim/content.ts` script is executed in the content script context. This facilitates any message passing across content/page script isolation (the shim itself is executed in the page context, both for convenience — since it interacts substantially with page scripts — and security reasons).
|
When a request is intercepted, the `shim/contentBridge.ts` script is executed in the content script context. This facilitates any message passing across content/page script isolation (the shim itself is executed in the page context, both for convenience — since it interacts substantially with page scripts — and security reasons).
|
||||||
|
|
||||||
Messages passed to the shim are custom events of type `__castMessage`. Messages passed back from the shim are custom events of type `__castMessageResponse`. Event listening and creation is handled by the `shim/messageBridge.ts` module.
|
Messages passed to the shim are custom events of type `__castMessage`. Messages passed back from the shim are custom events of type `__castMessageResponse`. Event listening and creation is handled by the `shim/messageBridge.ts` module.
|
||||||
|
|
||||||
@@ -23,6 +23,12 @@ The cast API is now available to the web app.
|
|||||||
|
|
||||||
The web app calls `chrome.cast.initialize` with an `ApiConfig` object containing the Chromecast receiver app ID to use. The shim sends a `main:/shimInitialized` message to the background script. The bridge sends `shim:/serviceUp` messages for any discovered devices with device info (address, port, label, etc…).
|
The web app calls `chrome.cast.initialize` with an `ApiConfig` object containing the Chromecast receiver app ID to use. The shim sends a `main:/shimInitialized` message to the background script. The bridge sends `shim:/serviceUp` messages for any discovered devices with device info (address, port, label, etc…).
|
||||||
|
|
||||||
|
### Extension Sender Apps
|
||||||
|
|
||||||
|
The initialization for built-in sender apps (media/mirroring) works slightly differently. They run in the content script context and import the shim, rather than running in the page script context.
|
||||||
|
|
||||||
|
The exported shim comes from `shim/export.ts`. Instead of setting the `window.__onGCastApiAvailable` callback, the module provides an init function which returns a promise. The `shim/contentBridge.ts` script is executed from that module and the initialization process is identical until the `shim:/initialized` message is received, at which point the promise is resolved.
|
||||||
|
|
||||||
## User Interaction
|
## User Interaction
|
||||||
|
|
||||||
A user will trigger casting through the web app interface and the app calls `chrome.cast.requestSession`. The shim sends a `main:/selectReceiverBegin` message to the background script to open the receiver selector.
|
A user will trigger casting through the web app interface and the app calls `chrome.cast.requestSession`. The shim sends a `main:/selectReceiverBegin` message to the background script to open the receiver selector.
|
||||||
|
|||||||
@@ -258,14 +258,7 @@ browser.menus.onShown.addListener(info => {
|
|||||||
browser.webRequest.onBeforeRequest.addListener(
|
browser.webRequest.onBeforeRequest.addListener(
|
||||||
async details => {
|
async details => {
|
||||||
await browser.tabs.executeScript(details.tabId, {
|
await browser.tabs.executeScript(details.tabId, {
|
||||||
code: `window._isFramework = ${
|
file: "shim/contentBridge.js"
|
||||||
details.url === CAST_FRAMEWORK_LOADER_SCRIPT_URL}`
|
|
||||||
, frameId: details.frameId
|
|
||||||
, runAt: "document_start"
|
|
||||||
});
|
|
||||||
|
|
||||||
await browser.tabs.executeScript(details.tabId, {
|
|
||||||
file: "shim/content.js"
|
|
||||||
, frameId: details.frameId
|
, frameId: details.frameId
|
||||||
, runAt: "document_start"
|
, runAt: "document_start"
|
||||||
});
|
});
|
||||||
@@ -402,7 +395,7 @@ browser.runtime.onMessage.addListener(async message => {
|
|||||||
// Defines window.chrome for site compatibility
|
// Defines window.chrome for site compatibility
|
||||||
browser.contentScripts.register({
|
browser.contentScripts.register({
|
||||||
allFrames: true
|
allFrames: true
|
||||||
, js: [{ file: "shim/contentSetup.js" }]
|
, js: [{ file: "shim/content.js" }]
|
||||||
, matches: [ "<all_urls>" ]
|
, matches: [ "<all_urls>" ]
|
||||||
, runAt: "document_start"
|
, runAt: "document_start"
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import { ErrorCode
|
|||||||
|
|
||||||
import { ListenerObject
|
import { ListenerObject
|
||||||
, onMessage
|
, onMessage
|
||||||
, sendMessageResponse } from "../../messageBridge";
|
, sendMessageResponse } from "../../eventMessageChannel";
|
||||||
|
|
||||||
import { Callbacks
|
import { Callbacks
|
||||||
, CallbacksMap
|
, CallbacksMap
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ import * as media from "./media";
|
|||||||
|
|
||||||
import { ReceiverSelectorMediaType }
|
import { ReceiverSelectorMediaType }
|
||||||
from "../../receiver_selectors/ReceiverSelector";
|
from "../../receiver_selectors/ReceiverSelector";
|
||||||
import { onMessage, sendMessageResponse } from "../messageBridge";
|
import { onMessage, sendMessageResponse } from "../eventMessageChannel";
|
||||||
|
|
||||||
|
|
||||||
type ReceiverActionListener = (
|
type ReceiverActionListener = (
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ import { PlayerState
|
|||||||
import _Error from "../../classes/Error";
|
import _Error from "../../classes/Error";
|
||||||
import { ErrorCode } from "../../enums";
|
import { ErrorCode } from "../../enums";
|
||||||
|
|
||||||
import { onMessage, sendMessageResponse } from "../../../messageBridge";
|
import { onMessage, sendMessageResponse } from "../../../eventMessageChannel";
|
||||||
|
|
||||||
import { Callbacks
|
import { Callbacks
|
||||||
, CallbacksMap
|
, CallbacksMap
|
||||||
|
|||||||
@@ -1,42 +1,38 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
import { Message } from "../types";
|
import { CAST_LOADER_SCRIPT_URL
|
||||||
import { onMessageResponse, sendMessage } from "./messageBridge";
|
, CAST_SCRIPT_URLS } from "../endpoints";
|
||||||
|
|
||||||
import { loadScript } from "../lib/utils";
|
|
||||||
|
|
||||||
|
|
||||||
if ((window as any)._isFramework) {
|
const _window = (window.wrappedJSObject as any);
|
||||||
loadScript(browser.runtime.getURL("vendor/webcomponents-lite.js"));
|
|
||||||
}
|
_window.chrome = cloneInto({}, window);
|
||||||
|
_window.navigator.presentation = cloneInto({}, window);
|
||||||
|
|
||||||
|
|
||||||
// Message ports
|
/**
|
||||||
const backgroundPort = browser.runtime.connect({ name: "shim" });
|
* Replace the src property setter on <script> elements to
|
||||||
let popupPort: browser.runtime.Port;
|
* intercept the new value.
|
||||||
|
*
|
||||||
|
* If it matches one of Chrome's cast extension sender script
|
||||||
|
* URLs, replace it with the standard API URL, the request for
|
||||||
|
* which is handled in the main script.
|
||||||
|
*/
|
||||||
|
const { get, set } = Reflect.getOwnPropertyDescriptor(
|
||||||
|
HTMLScriptElement.prototype.wrappedJSObject, "src");
|
||||||
|
|
||||||
|
Reflect.defineProperty(
|
||||||
|
HTMLScriptElement.prototype.wrappedJSObject, "src", {
|
||||||
|
|
||||||
// Set popupPort once it connects
|
configurable: true
|
||||||
browser.runtime.onConnect.addListener(port => {
|
, enumerable: true
|
||||||
if (port.name === "popup") {
|
, get
|
||||||
popupPort = port;
|
|
||||||
}
|
|
||||||
|
|
||||||
port.onMessage.addListener(sendMessage);
|
, set: exportFunction(function (value) {
|
||||||
});
|
if (CAST_SCRIPT_URLS.includes(value)) {
|
||||||
|
return set.call(this, CAST_LOADER_SCRIPT_URL);
|
||||||
// Forward background messages to shim
|
|
||||||
backgroundPort.onMessage.addListener(sendMessage);
|
|
||||||
|
|
||||||
// Forward shim messages to popup and background script
|
|
||||||
onMessageResponse((message: Message) => {
|
|
||||||
const [ destination ] = message.subject.split(":/");
|
|
||||||
|
|
||||||
if (destination === "popup") {
|
|
||||||
if (popupPort) {
|
|
||||||
popupPort.postMessage(message);
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
backgroundPort.postMessage(message);
|
return set.call(this, value);
|
||||||
}
|
}, window)
|
||||||
});
|
});
|
||||||
|
|||||||
15
ext/src/shim/contentBridge.ts
Normal file
15
ext/src/shim/contentBridge.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
import { onMessageResponse, sendMessage } from "./eventMessageChannel";
|
||||||
|
|
||||||
|
|
||||||
|
// Message port to background script
|
||||||
|
const backgroundPort = browser.runtime.connect({ name: "shim" });
|
||||||
|
|
||||||
|
// Forward background messages to shim
|
||||||
|
backgroundPort.onMessage.addListener(sendMessage);
|
||||||
|
|
||||||
|
// Forward shim messages to background
|
||||||
|
onMessageResponse(message => {
|
||||||
|
backgroundPort.postMessage(message);
|
||||||
|
});
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
"use strict";
|
|
||||||
|
|
||||||
import { CAST_LOADER_SCRIPT_URL
|
|
||||||
, CAST_SCRIPT_URLS } from "../endpoints";
|
|
||||||
|
|
||||||
|
|
||||||
const _window = (window.wrappedJSObject as any);
|
|
||||||
|
|
||||||
_window.chrome = cloneInto({}, window);
|
|
||||||
_window.navigator.presentation = cloneInto({}, window);
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Replace the src property setter on <script> elements to
|
|
||||||
* intercept the new value.
|
|
||||||
*
|
|
||||||
* If it matches one of Chrome's cast extension sender script
|
|
||||||
* URLs, replace it with the standard API URL, the request for
|
|
||||||
* which is handled in the main script.
|
|
||||||
*/
|
|
||||||
const { get, set } = Reflect.getOwnPropertyDescriptor(
|
|
||||||
HTMLScriptElement.prototype.wrappedJSObject, "src");
|
|
||||||
|
|
||||||
Reflect.defineProperty(
|
|
||||||
HTMLScriptElement.prototype.wrappedJSObject, "src", {
|
|
||||||
|
|
||||||
configurable: true
|
|
||||||
, enumerable: true
|
|
||||||
, get
|
|
||||||
|
|
||||||
, set: exportFunction(function (value) {
|
|
||||||
if (CAST_SCRIPT_URLS.includes(value)) {
|
|
||||||
return set.call(this, CAST_LOADER_SCRIPT_URL);
|
|
||||||
}
|
|
||||||
|
|
||||||
return set.call(this, value);
|
|
||||||
}, window)
|
|
||||||
});
|
|
||||||
@@ -4,7 +4,7 @@ import * as cast from "./cast";
|
|||||||
|
|
||||||
import { BridgeInfo } from "../lib/getBridgeInfo";
|
import { BridgeInfo } from "../lib/getBridgeInfo";
|
||||||
import { Message } from "../types";
|
import { Message } from "../types";
|
||||||
import { onMessage } from "./messageBridge";
|
import { onMessage } from "./eventMessageChannel";
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -18,7 +18,7 @@ export function init (): Promise<BridgeInfo> {
|
|||||||
return new Promise(async (resolve, reject) => {
|
return new Promise(async (resolve, reject) => {
|
||||||
|
|
||||||
// Trigger message port setup side-effects
|
// Trigger message port setup side-effects
|
||||||
import("./content");
|
import("./contentBridge");
|
||||||
|
|
||||||
onMessage(message => {
|
onMessage(message => {
|
||||||
switch (message.subject) {
|
switch (message.subject) {
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ import { ActiveInputState
|
|||||||
|
|
||||||
import GoogleCastLauncher from "./GoogleCastLauncher";
|
import GoogleCastLauncher from "./GoogleCastLauncher";
|
||||||
|
|
||||||
import { onMessage } from "../messageBridge";
|
import { onMessage } from "../eventMessageChannel";
|
||||||
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import * as cast from "./cast";
|
|||||||
|
|
||||||
import { CAST_FRAMEWORK_SCRIPT_URL } from "../endpoints";
|
import { CAST_FRAMEWORK_SCRIPT_URL } from "../endpoints";
|
||||||
import { loadScript } from "../lib/utils";
|
import { loadScript } from "../lib/utils";
|
||||||
import { onMessage } from "./messageBridge";
|
import { onMessage } from "./eventMessageChannel";
|
||||||
|
|
||||||
|
|
||||||
const _window = (window as any);
|
const _window = (window as any);
|
||||||
@@ -47,6 +47,12 @@ if (document.currentScript) {
|
|||||||
|
|
||||||
isFramework = true;
|
isFramework = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Framework API library requires webcomponents for the cast
|
||||||
|
* button custom element (<google-cast-launcher>).
|
||||||
|
*/
|
||||||
|
loadScript(browser.runtime.getURL("vendor/webcomponents-lite.js"));
|
||||||
|
|
||||||
const script = loadScript(CAST_FRAMEWORK_SCRIPT_URL);
|
const script = loadScript(CAST_FRAMEWORK_SCRIPT_URL);
|
||||||
script.addEventListener("load", ev => {
|
script.addEventListener("load", ev => {
|
||||||
callPageReadyFunction();
|
callPageReadyFunction();
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ module.exports = (env) => ({
|
|||||||
// Shim entries
|
// Shim entries
|
||||||
, "shim/bundle": `${env.includePath}/shim/index.ts`
|
, "shim/bundle": `${env.includePath}/shim/index.ts`
|
||||||
, "shim/content": `${env.includePath}/shim/content.ts`
|
, "shim/content": `${env.includePath}/shim/content.ts`
|
||||||
, "shim/contentSetup": `${env.includePath}/shim/contentSetup.ts`
|
, "shim/contentBridge": `${env.includePath}/shim/contentBridge.ts`
|
||||||
}
|
}
|
||||||
, output: {
|
, output: {
|
||||||
filename: "[name].js"
|
filename: "[name].js"
|
||||||
|
|||||||
Reference in New Issue
Block a user