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..."
}
, "options_category_media": {
"message": "Media casting"
}
, "options_category_media_description": {
"message": "HTML5 video/audio media casting."
}
, "options_option_mediaEnabled": {
"message": "Enabled"
}
, "options_category_localMedia": {
"message": "Local media casting"
}
@@ -40,7 +51,7 @@
"message": "Enabled"
}
, "options_option_uaWhitelist": {
"message": "Match matterns (newline-separated)"
"message": "Match patterns (newline-separated)"
}
, "options_option_uaWhitelistBasicView": {
"message": "Basic View"
@@ -64,6 +75,22 @@
"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": {
"message": "Submit"
}

View File

@@ -1,26 +1,18 @@
"use strict";
import messageRouter from "./messageRouter";
import defaultOptions from "./options/defaultOptions";
import messageRouter from "./messageRouter";
const _ = browser.i18n.getMessage;
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) {
// Set initial options
// Set default options
case "install":
browser.storage.sync.set({
options: initialOptions
await browser.storage.sync.set({
options: defaultOptions
});
break;
@@ -30,14 +22,14 @@ browser.runtime.onInstalled.addListener(async details => {
const newOptions = {};
// 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)) {
newOptions[key] = val;
}
}
// Update storage with default values of new options
browser.storage.sync.set({
await browser.storage.sync.set({
options: {
...options
, ...newOptions
@@ -46,9 +38,55 @@ browser.runtime.onInstalled.addListener(async details => {
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
const SENDER_SCRIPT_URL =
"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");
// If options aren't set yet, return
@@ -159,34 +197,52 @@ async function registerWebRequestListeners (alteredOptions) {
}
};
if (!alteredOptions) {
// If no altered properties specified, register all listeners
for (const func of Object.values(registerFunctions)) {
func();
}
} else {
if ( alteredOptions.includes("option_uaWhitelist")
if (alteredOptions.includes("option_uaWhitelist")
|| alteredOptions.includes("option_uaWhitelistEnabled")) {
browser.webRequest.onBeforeSendHeaders.removeListener(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 => {
switch (message.subject) {
case "optionsUpdated":
const { alteredOptions } = message.data;
registerWebRequestListeners(alteredOptions);
onOptionsUpdated(message.data.alteredOptions);
break;
}
});
// Defines window.chrome for site compatibility
browser.contentScripts.register({
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 mediaCastFrameId;
@@ -225,6 +261,7 @@ let mirrorCastFrameId;
browser.menus.onClicked.addListener(async (info, tab) => {
const { frameId } = info;
const { options } = await browser.storage.sync.get("options");
// Load cast setup script
await browser.tabs.executeScript(tab.id, {
@@ -238,7 +275,8 @@ browser.menus.onClicked.addListener(async (info, tab) => {
mirrorCastFrameId = frameId;
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
});
@@ -422,3 +460,8 @@ messageRouter.register("mediaCast", message => {
browser.runtime.onMessage.addListener((message, sender) => {
messageRouter.handleMessage(message, sender);
});
// Misc init
createMenus();
onOptionsUpdated();

View File

@@ -3,9 +3,6 @@
let chrome;
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";
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;
}
.category-name {}
.category-description {
.category-description,
.category .category {
color: graytext;
grid-column: span 2;
}
@@ -48,6 +49,7 @@
.editable-list__items {
display: flex;
flex-direction: column;
padding: initial;
}
.editable-list__item {
align-items: center;

View File

@@ -3,6 +3,8 @@
import React, { Component } from "react";
import ReactDOM from "react-dom";
import defaultOptions from "./defaultOptions";
const _ = browser.i18n.getMessage;
@@ -272,6 +274,7 @@ class OptionsApp extends Component {
, isFormValid: true
};
this.handleReset = this.handleReset.bind(this);
this.handleFormSubmit = this.handleFormSubmit.bind(this);
this.handleFormChange = this.handleFormChange.bind(this);
this.handleInputChange = this.handleInputChange.bind(this);
@@ -285,14 +288,26 @@ class OptionsApp extends Component {
setStorage () {
return browser.storage.sync.set({
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_uaWhitelistEnabled: this.state.option_uaWhitelistEnabled
, 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) {
ev.preventDefault();
@@ -360,32 +375,81 @@ class OptionsApp extends Component {
onChange={ this.handleFormChange }>
<fieldset className="category">
<legend className="category-name">
{ _("options_category_localMedia") }
{ _("options_category_media") }
</legend>
<p className="category-description">
{ _("options_category_localMedia_description") }
{ _("options_category_media_description") }
</p>
<label className="option">
<div className="option-label">
{ _("options_option_localMediaEnabled") }
{ _("options_option_mediaEnabled") }
</div>
<input name="option_localMediaEnabled"
<input name="option_mediaEnabled"
type="checkbox"
checked={ this.state.option_localMediaEnabled }
checked={ this.state.option_mediaEnabled }
onChange={ this.handleInputChange } />
</label>
<fieldset className="category">
<legend className="category-name">
{ _("options_category_localMedia") }
</legend>
<p className="category-description">
{ _("options_category_localMedia_description") }
</p>
<label className="option">
<div className="option-label">
{ _("options_option_localMediaEnabled") }
</div>
<input name="option_localMediaEnabled"
type="checkbox"
checked={ this.state.option_localMediaEnabled }
onChange={ this.handleInputChange } />
</label>
<label className="option">
<div className="option-label">
{ _("options_option_localMediaServerPort") }
</div>
<input name="option_localMediaServerPort"
type="number"
required
min="1025"
max="65535"
value={ this.state.option_localMediaServerPort }
onChange={ this.handleInputChange } />
</label>
</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_localMediaServerPort") }
{ _("options_option_mirroringAppId") }
</div>
<input name="option_localMediaServerPort"
type="number"
<input name="option_mirroringAppId"
type="text"
required
min="1025"
max="65535"
value={ this.state.option_localMediaServerPort }
value={ this.state.option_mirroringAppId }
onChange={ this.handleInputChange } />
</label>
</fieldset>
@@ -420,6 +484,9 @@ class OptionsApp extends Component {
</fieldset>
<div id="buttons">
<button onClick={ this.handleReset }>
{ _("options_reset") }
</button>
<button type="submit"
disabled={ !this.state.isFormValid }>
{ _("options_submit") }