Clean up shim initialization

This commit is contained in:
hensm
2019-06-19 01:39:19 +01:00
parent 290a1facdb
commit 0a9af09419
13 changed files with 66 additions and 88 deletions

View File

@@ -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 Googles Cast API library. The background script registers a `webRequest.onBeforeRequest` listener that intercepts requests to Googles 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.

View File

@@ -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"
}); });

View File

@@ -18,7 +18,7 @@ import { ErrorCode
import { ListenerObject import { ListenerObject
, onMessage , onMessage
, sendMessageResponse } from "../../messageBridge"; , sendMessageResponse } from "../../eventMessageChannel";
import { Callbacks import { Callbacks
, CallbacksMap , CallbacksMap

View File

@@ -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 = (

View File

@@ -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

View File

@@ -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)
}); });

View 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);
});

View File

@@ -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)
});

View File

@@ -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) {

View File

@@ -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 {

View File

@@ -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();

View File

@@ -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"