Implement additional options

This commit is contained in:
hensm
2018-10-24 00:29:40 +01:00
parent 3dc9f5a126
commit f4b81f3cda
6 changed files with 209 additions and 62 deletions

View File

@@ -17,6 +17,17 @@
"message": "Cast..." "message": "Cast..."
} }
, "options_category_media": {
"message": "Media casting"
}
, "options_category_media_description": {
"message": "HTML5 video/audio media casting."
}
, "options_option_mediaEnabled": {
"message": "Enabled"
}
, "options_category_localMedia": { , "options_category_localMedia": {
"message": "Local media casting" "message": "Local media casting"
} }
@@ -40,7 +51,7 @@
"message": "Enabled" "message": "Enabled"
} }
, "options_option_uaWhitelist": { , "options_option_uaWhitelist": {
"message": "Match matterns (newline-separated)" "message": "Match patterns (newline-separated)"
} }
, "options_option_uaWhitelistBasicView": { , "options_option_uaWhitelistBasicView": {
"message": "Basic View" "message": "Basic View"
@@ -64,6 +75,22 @@
"message": "Invalid match pattern $1" "message": "Invalid match pattern $1"
} }
, "options_category_mirroring": {
"message": "Screen mirroring"
}
, "options_category_mirroring_description": {
"message": "Screen/Tab mirroring to a Chromecast receiver app."
}
, "options_option_mirroringEnabled": {
"message": "Enabled"
}
, "options_option_mirroringAppId": {
"message": "Receiver app ID"
}
, "options_reset": {
"message": "Reset to defaults"
}
, "options_submit": { , "options_submit": {
"message": "Submit" "message": "Submit"
} }

View File

@@ -1,26 +1,18 @@
"use strict"; "use strict";
import defaultOptions from "./options/defaultOptions";
import messageRouter from "./messageRouter"; import messageRouter from "./messageRouter";
const _ = browser.i18n.getMessage; const _ = browser.i18n.getMessage;
browser.runtime.onInstalled.addListener(async details => { browser.runtime.onInstalled.addListener(async details => {
const initialOptions = {
option_localMediaEnabled: true
, option_localMediaServerPort: 9555
, option_uaWhitelistEnabled: true
, option_uaWhitelist: [
"https://www.netflix.com/*"
]
};
switch (details.reason) { switch (details.reason) {
// Set initial options // Set default options
case "install": case "install":
browser.storage.sync.set({ await browser.storage.sync.set({
options: initialOptions options: defaultOptions
}); });
break; break;
@@ -30,14 +22,14 @@ browser.runtime.onInstalled.addListener(async details => {
const newOptions = {}; const newOptions = {};
// Find options not already in storage // Find options not already in storage
for (const [ key, val ] of Object.entries(initialOptions)) { for (const [ key, val ] of Object.entries(defaultOptions)) {
if (!options.hasOwnProperty(key)) { if (!options.hasOwnProperty(key)) {
newOptions[key] = val; newOptions[key] = val;
} }
} }
// Update storage with default values of new options // Update storage with default values of new options
browser.storage.sync.set({ await browser.storage.sync.set({
options: { options: {
...options ...options
, ...newOptions , ...newOptions
@@ -46,9 +38,55 @@ browser.runtime.onInstalled.addListener(async details => {
break; break;
} }
// Call after default options have been set
createMenus();
}); });
// Menu IDs
let mirrorCastMenuId;
let mediaCastMenuId;
const mediaCastTargetUrlPatterns = new Set([
"http://*/*"
, "https://*/*"
]);
const LOCAL_MEDIA_URL_PATTERN = "file://*/*";
async function createMenus () {
const { options } = await browser.storage.sync.get("options");
/**
* If options aren't set or menus have already been
* created, return.
*/
if (!options || mirrorCastMenuId || mediaCastMenuId) return;
if (options.option_localMediaEnabled) {
mediaCastTargetUrlPatterns.add(LOCAL_MEDIA_URL_PATTERN);
}
// <video>/<audio> "Cast..." context menu item
mediaCastMenuId = await browser.menus.create({
contexts: [ "audio", "video" ]
, id: "contextCastMedia"
, targetUrlPatterns: Array.from(mediaCastTargetUrlPatterns)
, title: _("context_media_cast")
, visible: options.option_mediaEnabled
});
// Screen/Tab mirroring "Cast..." context menu item
mirrorCastMenuId = await browser.menus.create({
contexts: [ "browser_action", "page" ]
, id: "contextCast"
, title: _("context_media_cast")
, visible: options.option_mirroringEnabled
});
}
// Google-hosted API loader script // Google-hosted API loader script
const SENDER_SCRIPT_URL = const SENDER_SCRIPT_URL =
"https://www.gstatic.com/cv/js/sender/v1/cast_sender.js"; "https://www.gstatic.com/cv/js/sender/v1/cast_sender.js";
@@ -142,7 +180,7 @@ async function onBeforeSendHeaders (details) {
}; };
} }
async function registerWebRequestListeners (alteredOptions) { async function onOptionsUpdated (alteredOptions) {
const { options } = await browser.storage.sync.get("options"); const { options } = await browser.storage.sync.get("options");
// If options aren't set yet, return // If options aren't set yet, return
@@ -159,34 +197,52 @@ async function registerWebRequestListeners (alteredOptions) {
} }
}; };
if (!alteredOptions) { if (!alteredOptions) {
// If no altered properties specified, register all listeners // If no altered properties specified, register all listeners
for (const func of Object.values(registerFunctions)) { for (const func of Object.values(registerFunctions)) {
func(); func();
} }
} else { } else {
if ( alteredOptions.includes("option_uaWhitelist") if (alteredOptions.includes("option_uaWhitelist")
|| alteredOptions.includes("option_uaWhitelistEnabled")) { || alteredOptions.includes("option_uaWhitelistEnabled")) {
browser.webRequest.onBeforeSendHeaders.removeListener(onBeforeSendHeaders); browser.webRequest.onBeforeSendHeaders.removeListener(onBeforeSendHeaders);
registerFunctions.onBeforeSendHeaders(); registerFunctions.onBeforeSendHeaders();
} }
if (alteredOptions.includes("option_mirroringEnabled")) {
browser.menus.update(mirrorCastMenuId, {
visible: options.option_mirroringEnabled
});
}
if (alteredOptions.includes("option_mediaEnabled")) {
browser.menus.update(mediaCastMenuId, {
visible: options.option_mediaEnabled
})
}
if (alteredOptions.includes("option_localMediaEnabled")) {
if (options.option_localMediaEnabled) {
mediaCastTargetUrlPatterns.add(LOCAL_MEDIA_URL_PATTERN);
} else {
mediaCastTargetUrlPatterns.delete(LOCAL_MEDIA_URL_PATTERN);
}
browser.menus.update(mediaCastMenuId, {
targetUrlPatterns: Array.from(mediaCastTargetUrlPatterns)
});
}
} }
} }
registerWebRequestListeners();
browser.runtime.onMessage.addListener(message => { browser.runtime.onMessage.addListener(message => {
switch (message.subject) { switch (message.subject) {
case "optionsUpdated": case "optionsUpdated":
const { alteredOptions } = message.data; onOptionsUpdated(message.data.alteredOptions);
registerWebRequestListeners(alteredOptions);
break; break;
} }
}); });
// Defines window.chrome for site compatibility // Defines window.chrome for site compatibility
browser.contentScripts.register({ browser.contentScripts.register({
allFrames: true allFrames: true
@@ -196,26 +252,6 @@ browser.contentScripts.register({
}); });
// Screen/Tab mirroring "Cast..." context menu item
browser.menus.create({
contexts: [ "browser_action", "page" ]
, id: "contextCast"
, title: _("context_media_cast")
});
// <video>/<audio> "Cast..." context menu item
browser.menus.create({
contexts: [ "audio", "video" ]
, id: "contextCastMedia"
, targetUrlPatterns: [
"http://*/*"
, "https://*/*"
, "file://*/*"
]
, title: _("context_media_cast")
});
let mediaCastTabId; let mediaCastTabId;
let mediaCastFrameId; let mediaCastFrameId;
@@ -225,6 +261,7 @@ let mirrorCastFrameId;
browser.menus.onClicked.addListener(async (info, tab) => { browser.menus.onClicked.addListener(async (info, tab) => {
const { frameId } = info; const { frameId } = info;
const { options } = await browser.storage.sync.get("options");
// Load cast setup script // Load cast setup script
await browser.tabs.executeScript(tab.id, { await browser.tabs.executeScript(tab.id, {
@@ -238,7 +275,8 @@ browser.menus.onClicked.addListener(async (info, tab) => {
mirrorCastFrameId = frameId; mirrorCastFrameId = frameId;
await browser.tabs.executeScript(tab.id, { await browser.tabs.executeScript(tab.id, {
code: `let selectedMedia = "${info.pageUrl ? "tab" : "screen"}";` code: `let selectedMedia = "${info.pageUrl ? "tab" : "screen"}";
let FX_CAST_RECEIVER_APP_ID = ${options.option_mirroringAppId};`
, frameId , frameId
}); });
@@ -422,3 +460,8 @@ messageRouter.register("mediaCast", message => {
browser.runtime.onMessage.addListener((message, sender) => { browser.runtime.onMessage.addListener((message, sender) => {
messageRouter.handleMessage(message, sender); messageRouter.handleMessage(message, sender);
}); });
// Misc init
createMenus();
onOptionsUpdated();

View File

@@ -3,9 +3,6 @@
let chrome; let chrome;
let logMessage; let logMessage;
const FX_CAST_RECEIVER_APP_ID = typeof MIRROR_CAST_APP_ID !== "undefined"
? MIRROR_CAST_APP_ID
: "19A6F4AE";
const FX_CAST_NAMESPACE = "urn:x-cast:fx_cast"; const FX_CAST_NAMESPACE = "urn:x-cast:fx_cast";
let session; let session;

View File

@@ -0,0 +1,11 @@
export default {
option_mediaEnabled: true
, option_localMediaEnabled: true
, option_localMediaServerPort: 9555
, option_uaWhitelistEnabled: true
, option_uaWhitelist: [
"https://www.netflix.com/*"
]
, option_mirroringEnabled: false
, option_mirroringAppId: MIRROR_CAST_APP_ID || "19A6F4AE"
}

View File

@@ -5,7 +5,8 @@
grid-row-gap: 5px; grid-row-gap: 5px;
} }
.category-name {} .category-name {}
.category-description { .category-description,
.category .category {
color: graytext; color: graytext;
grid-column: span 2; grid-column: span 2;
} }
@@ -48,6 +49,7 @@
.editable-list__items { .editable-list__items {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
padding: initial;
} }
.editable-list__item { .editable-list__item {
align-items: center; align-items: center;

View File

@@ -3,6 +3,8 @@
import React, { Component } from "react"; import React, { Component } from "react";
import ReactDOM from "react-dom"; import ReactDOM from "react-dom";
import defaultOptions from "./defaultOptions";
const _ = browser.i18n.getMessage; const _ = browser.i18n.getMessage;
@@ -272,6 +274,7 @@ class OptionsApp extends Component {
, isFormValid: true , isFormValid: true
}; };
this.handleReset = this.handleReset.bind(this);
this.handleFormSubmit = this.handleFormSubmit.bind(this); this.handleFormSubmit = this.handleFormSubmit.bind(this);
this.handleFormChange = this.handleFormChange.bind(this); this.handleFormChange = this.handleFormChange.bind(this);
this.handleInputChange = this.handleInputChange.bind(this); this.handleInputChange = this.handleInputChange.bind(this);
@@ -285,14 +288,26 @@ class OptionsApp extends Component {
setStorage () { setStorage () {
return browser.storage.sync.set({ return browser.storage.sync.set({
options: { options: {
option_localMediaEnabled: this.state.option_localMediaEnabled option_mediaEnabled: this.state.option_mediaEnabled
, option_localMediaEnabled: this.state.option_localMediaEnabled
, option_localMediaServerPort: this.state.option_localMediaServerPort , option_localMediaServerPort: this.state.option_localMediaServerPort
, option_uaWhitelistEnabled: this.state.option_uaWhitelistEnabled , option_uaWhitelistEnabled: this.state.option_uaWhitelistEnabled
, option_uaWhitelist: this.state.option_uaWhitelist , option_uaWhitelist: this.state.option_uaWhitelist
, option_mirroringEnabled: this.state.option_mirroringEnabled
, option_mirroringAppId: this.state.option_mirroringAppId
} }
}); });
} }
handleReset () {
this.setState({
...defaultOptions
});
// TODO: Propagate state properly
this.form.submit();
}
async handleFormSubmit (ev) { async handleFormSubmit (ev) {
ev.preventDefault(); ev.preventDefault();
@@ -358,6 +373,24 @@ class OptionsApp extends Component {
<form id="form" ref={ form => { this.form = form; }} <form id="form" ref={ form => { this.form = form; }}
onSubmit={ this.handleFormSubmit } onSubmit={ this.handleFormSubmit }
onChange={ this.handleFormChange }> onChange={ this.handleFormChange }>
<fieldset className="category">
<legend className="category-name">
{ _("options_category_media") }
</legend>
<p className="category-description">
{ _("options_category_media_description") }
</p>
<label className="option">
<div className="option-label">
{ _("options_option_mediaEnabled") }
</div>
<input name="option_mediaEnabled"
type="checkbox"
checked={ this.state.option_mediaEnabled }
onChange={ this.handleInputChange } />
</label>
<fieldset className="category"> <fieldset className="category">
<legend className="category-name"> <legend className="category-name">
{ _("options_category_localMedia") } { _("options_category_localMedia") }
@@ -389,6 +422,37 @@ class OptionsApp extends Component {
onChange={ this.handleInputChange } /> onChange={ this.handleInputChange } />
</label> </label>
</fieldset> </fieldset>
</fieldset>
<fieldset className="category">
<legend className="category-name">
{ _("options_category_mirroring") }
</legend>
<p className="category-description">
{ _("options_category_mirroring_description") }
</p>
<label className="option">
<div className="option-label">
{ _("options_option_mirroringEnabled") }
</div>
<input name="option_mirroringEnabled"
type="checkbox"
checked={ this.state.option_mirroringEnabled }
onChange={ this.handleInputChange } />
</label>
<label className="option">
<div className="option-label">
{ _("options_option_mirroringAppId") }
</div>
<input name="option_mirroringAppId"
type="text"
required
value={ this.state.option_mirroringAppId }
onChange={ this.handleInputChange } />
</label>
</fieldset>
<fieldset className="category"> <fieldset className="category">
<legend className="category-name"> <legend className="category-name">
@@ -420,6 +484,9 @@ class OptionsApp extends Component {
</fieldset> </fieldset>
<div id="buttons"> <div id="buttons">
<button onClick={ this.handleReset }>
{ _("options_reset") }
</button>
<button type="submit" <button type="submit"
disabled={ !this.state.isFormValid }> disabled={ !this.state.isFormValid }>
{ _("options_submit") } { _("options_submit") }