diff --git a/extension/action_popup.html b/extension/action_popup.html --- a/extension/action_popup.html +++ b/extension/action_popup.html @@ -7,6 +7,8 @@ + + @@ -41,7 +43,13 @@ diff --git a/extension/action_popup.css b/extension/action_popup.css --- a/extension/action_popup.css +++ b/extension/action_popup.css @@ -16,10 +16,14 @@ text-align: center; } +section { + margin: 0 0.5em; +} + section > header { background: #F0F0F0; color: #757777; - margin: 0 -1em .5em -1em; + margin: 0 -1em -0.5em -1em; } .message { @@ -44,3 +48,19 @@ filter: invert(1); } } + +/* Media controls blacklist */ +.mpris-blacklist-info { + padding: 0.5em 0; +} +.mpris-blacklist-info p { + padding: 0 0.5em; +} +.mpris-blacklist-info ul { + display: block; + padding: 0 0.5em; +} +.mpris-blacklist-info ul > li { + display: block; + margin-bottom: 0.5em; +} diff --git a/extension/action_popup.js b/extension/action_popup.js --- a/extension/action_popup.js +++ b/extension/action_popup.js @@ -15,6 +15,149 @@ along with this program. If not, see . */ +class TabUtils { + // Gets the URL of the currently viewed tab + static getCurrentUrl() { + return new Promise((resolve, reject) => { + chrome.tabs.query({ + active: true, + currentWindow: true + }, (tabs) => { + const error = chrome.runtime.lastError; + if (error) { + return reject(error.message); + } + + const tab = tabs[0]; + if (!tab) { // can this happen? + return reject("NO_TAB"); + } + + resolve(tab.url); + }); + }); + } + + // Gets the URLs of the currently viewed tab including all of its iframes + static getCurrentUrls() { + return new Promise((resolve, reject) => { + chrome.tabs.executeScript({ + allFrames: true, // so we also catch iframe videos + code: `window.location.href` + }, (result) => { + const error = chrome.runtime.lastError; + if (error) { + return reject(error.message); + } + + resolve(result); + }); + }); + } +}; + +class MPrisBlocker { + get() { + return new Promise((resolve, reject) => { + + Promise.all([ + SettingsUtils.get(), + TabUtils.getCurrentUrls() + ]).then((result) => { + + const settings = result[0]; + const currentUrls = result[1]; + + const mprisSettings = settings.mpris; + if (!mprisSettings.enabled) { + return reject("MPRIS_DISABLED"); + } + + if (!currentUrls) { // can this happen? + return reject("NO_URLS"); + } + + const domains = currentUrls.map((url) => { + try { + return new URL(url).hostname; + } catch (e) { + console.warn("Invalid url", url); + return ""; + } + }).filter((domain) => { + return !!domain; + }); + + if (domains.length === 0) { + return reject("NO_DOMAINS"); + } + + const uniqueDomains = [...new Set(domains)]; + + const whitelist = mprisSettings.whitelistedDomains || []; + const blacklist = mprisSettings.blacklistedDomains || []; + + let response = { + domains: {}, + mprisSettings + }; + + for (const domain of uniqueDomains) { + const blocked = (blacklist.includes(domain) || (BLACKLISTED_MPRIS_DOMAINS.includes(domain) && !whitelist.includes(domain))); + + response.domains[domain] = {blocked}; + } + + resolve(response); + + }, reject); + + }); + } + + set(domain, block) { + + return this.get().then((blockInfo) => { + + let whitelist = blockInfo.mprisSettings.whitelistedDomains; + let blacklist = blockInfo.mprisSettings.blacklistedDomains; + + if (block) { + const whitelistIdx = whitelist.indexOf(domain); + if (whitelistIdx > -1) { + whitelist.splice(whitelistIdx, 1); + } else if (!blacklist.includes(domain)) { + blacklist.push(domain); + } + } else { + if (BLACKLISTED_MPRIS_DOMAINS.includes(domain)) { + whitelist.push(domain); + } else { + const blacklistIdx = blacklist.indexOf(domain); + if (blacklistIdx > -1) { + blacklist.splice(blacklistIdx, 1); + } + } + } + + blockInfo.mprisSettings.whitelistedDomains = whitelist; + blockInfo.mprisSettings.blacklistedDomains = blacklist; + + return { + mprisSettings: blockInfo.mprisSettings + }; + + }).then((result) => { + + return SettingsUtils.set({ + mpris: result.mprisSettings + }); + + }); + + } +}; + document.addEventListener("DOMContentLoaded", () => { sendMessage("browserAction", "getStatus").then((status) => { @@ -55,4 +198,45 @@ sendMessage("browserAction", "ready"); }); + // MPris blocker checkboxes + const blocker = new MPrisBlocker(); + blocker.get().then((result) => { + + const domains = result.domains; + + if (Object.entries(domains).length === 0) { // "isEmpty" + return; + } + + let blacklistInfoElement = document.querySelector(".mpris-blacklist-info"); + blacklistInfoElement.classList.remove("hidden"); + + let domainsListElement = blacklistInfoElement.querySelector("ul.mpris-blacklist-domains"); + + for (const domain in domains) { + const domainSettings = domains[domain]; + + let blockListElement = document.createElement("li"); + + let labelElement = document.createElement("label"); + labelElement.innerText = domain; + + let checkboxElement = document.createElement("input"); + checkboxElement.type = "checkbox"; + checkboxElement.checked = !domainSettings.blocked; + checkboxElement.addEventListener("click", (e) => { + blocker.set(domain, !checkboxElement.checked); + }); + + labelElement.insertBefore(checkboxElement, labelElement.firstChild); + + blockListElement.appendChild(labelElement); + + domainsListElement.appendChild(blockListElement); + } + + }, (err) => { + console.warn("Failed to check for whether MPRIS is blocked", err); + }); + }); diff --git a/extension/constants.js b/extension/constants.js --- a/extension/constants.js +++ b/extension/constants.js @@ -18,7 +18,10 @@ DEFAULT_EXTENSION_SETTINGS = { mpris: { - enabled: true + enabled: true, + // Domains that are explicitly whitelisted despite being in the global blacklist + whitelistedDomains: [], + blacklistedDomains: [] }, mprisMediaSessions: { enabled: true @@ -46,3 +49,8 @@ // NOTE if you change this, make sure to adjust the error message shown in action_popup.html SUPPORTED_PLATFORMS = ["linux", "openbsd", "freebsd"]; + +// Websites known to break with our media controls +const BLACKLISTED_MPRIS_DOMAINS = [ + +]; diff --git a/extension/content-script.js b/extension/content-script.js --- a/extension/content-script.js +++ b/extension/content-script.js @@ -65,9 +65,18 @@ const mpris = items.mpris; if (mpris.enabled) { - loadMpris(); - if (items.mprisMediaSessions.enabled) { - loadMediaSessionsShim(); + const domain = window.location.hostname; + + const whitelist = items.mpris.whitelistedDomains || []; + const blacklist = items.mpris.blacklistedDomains || []; + + const mprisBlocked = (blacklist.includes(domain) || (BLACKLISTED_MPRIS_DOMAINS.includes(domain) && !whitelist.includes(domain))); + + if (!mprisBlocked) { + loadMpris(); + if (items.mprisMediaSessions.enabled) { + loadMediaSessionsShim(); + } } } diff --git a/extension/extension.js b/extension/extension.js --- a/extension/extension.js +++ b/extension/extension.js @@ -86,7 +86,6 @@ var portLastErrorMessage = undefined; function updateBrowserAction() { - let enableAction = false; if (portStatus === "UNSUPPORTED_OS" || portStatus === "STARTUP_FAILED") { chrome.browserAction.setIcon({ path: { @@ -96,22 +95,14 @@ "128": "icons/plasma-disabled-128.png" } }); - enableAction = true; } if (portLastErrorMessage) { chrome.browserAction.setBadgeText({ text: "!" }); chrome.browserAction.setBadgeBackgroundColor({ color: "#da4453" }); // breeze "negative" color - enableAction = true; } else { chrome.browserAction.setBadgeText({ text: "" }); } - - if (enableAction) { - chrome.browserAction.enable(); - } else { - chrome.browserAction.disable(); - } } updateBrowserAction();