File indexing completed on 2024-04-28 05:32:49

0001 /*
0002     Copyright (C) 2019 Kai Uwe Broulik <kde@privat.broulik.de>
0003 
0004     This program is free software; you can redistribute it and/or
0005     modify it under the terms of the GNU General Public License as
0006     published by the Free Software Foundation; either version 3 of
0007     the License, or (at your option) any later version.
0008 
0009     This program is distributed in the hope that it will be useful,
0010     but WITHOUT ANY WARRANTY; without even the implied warranty of
0011     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0012     GNU General Public License for more details.
0013 
0014     You should have received a copy of the GNU General Public License
0015     along with this program.  If not, see <http://www.gnu.org/licenses/>.
0016  */
0017 
0018 class TabUtils {
0019     // Gets the currently viewed tab
0020     static getCurrentTab() {
0021         return new Promise((resolve, reject) => {
0022             chrome.tabs.query({
0023                 active: true,
0024                 currentWindow: true
0025             }, (tabs) => {
0026                 const error = chrome.runtime.lastError;
0027                 if (error) {
0028                     return reject(error.message);
0029                 }
0030 
0031                 const tab = tabs[0];
0032                 if (!tab) {
0033                     return reject("NO_TAB");
0034                 }
0035 
0036                 resolve(tab);
0037             });
0038         });
0039     }
0040 
0041     // Gets the URLs of the currently viewed tab including all of its iframes
0042     static getCurrentTabFramesUrls() {
0043         return new Promise((resolve, reject) => {
0044             TabUtils.getCurrentTab().then((tab) => {
0045                 chrome.tabs.executeScript({
0046                     allFrames: true, // so we also catch iframe videos
0047                     code: `window.location.href`,
0048                     runAt: "document_start"
0049                 }, (result) => {
0050                     const error = chrome.runtime.lastError;
0051                     if (error) {
0052                         return reject(error.message);
0053                     }
0054 
0055                     resolve(result);
0056                 });
0057             });
0058         });
0059     }
0060 };
0061 
0062 class MPrisBlocker {
0063     getAllowed() {
0064         return new Promise((resolve, reject) => {
0065             Promise.all([
0066                 SettingsUtils.get(),
0067                 TabUtils.getCurrentTabFramesUrls()
0068             ]).then((result) => {
0069 
0070                 const settings = result[0];
0071                 const currentUrls = result[1];
0072 
0073                 const mprisSettings = settings.mpris;
0074                 if (!mprisSettings.enabled) {
0075                     return reject("MPRIS_DISABLED");
0076                 }
0077 
0078                 if (!currentUrls) { // can this happen?
0079                     return reject("NO_URLS");
0080                 }
0081 
0082                 const origins = currentUrls.map((url) => {
0083                     try {
0084                         return new URL(url).origin;
0085                     } catch (e) {
0086                         console.warn("Invalid url", url);
0087                         return "";
0088                     }
0089                 }).filter((origin) => {
0090                     return !!origin;
0091                 });
0092 
0093                 if (origins.length === 0) {
0094                     return reject("NO_ORIGINS");
0095                 }
0096 
0097                 const uniqueOrigins = [...new Set(origins)];
0098 
0099                 const websiteSettings = mprisSettings.websiteSettings || {};
0100 
0101                 let response = {
0102                     origins: {},
0103                     mprisSettings
0104                 };
0105 
0106                 for (const origin of uniqueOrigins) {
0107                     let allowed = true;
0108                     if (typeof MPRIS_WEBSITE_SETTINGS[origin] === "boolean") {
0109                         allowed = MPRIS_WEBSITE_SETTINGS[origin];
0110                     }
0111                     if (typeof websiteSettings[origin] === "boolean") {
0112                         allowed = websiteSettings[origin];
0113                     }
0114 
0115                     response.origins[origin] = allowed;
0116                 }
0117 
0118                 resolve(response);
0119 
0120             }, reject);
0121         });
0122     }
0123 
0124     setAllowed(origin, allowed) {
0125         return SettingsUtils.get().then((settings) => {
0126             const mprisSettings = settings.mpris;
0127             if (!mprisSettings.enabled) {
0128                 return reject("MPRIS_DISABLED");
0129             }
0130 
0131             let websiteSettings = mprisSettings.websiteSettings || {};
0132 
0133             let implicitAllowed = true;
0134             if (typeof MPRIS_WEBSITE_SETTINGS[origin] === "boolean") {
0135                 implicitAllowed = MPRIS_WEBSITE_SETTINGS[origin];
0136             }
0137 
0138             if (allowed !== implicitAllowed) {
0139                 websiteSettings[origin] = allowed;
0140             } else {
0141                 delete websiteSettings[origin];
0142             }
0143 
0144             mprisSettings.websiteSettings = websiteSettings;
0145 
0146             return SettingsUtils.set({
0147                 mpris: mprisSettings
0148             });
0149         });
0150     }
0151 };
0152 
0153 document.addEventListener("DOMContentLoaded", () => {
0154 
0155     sendMessage("browserAction", "getStatus").then((status) => {
0156 
0157         switch (status.portStatus) {
0158         case "UNSUPPORTED_OS":
0159             document.getElementById("unsupported_os_error").classList.remove("hidden");
0160             break;
0161 
0162         case "STARTUP_FAILED": {
0163             document.getElementById("startup_error").classList.remove("hidden");
0164 
0165             const errorText = status.portLastErrorMessage;
0166             // Don't show generic error on startup failure. There's already an explanation.
0167             if (errorText && errorText !== "UNKNOWN") {
0168                 const errorTextItem = document.getElementById("startup_error_text");
0169                 errorTextItem.innerText = errorText;
0170                 errorTextItem.classList.remove("hidden");
0171             }
0172             break;
0173         }
0174 
0175         default: {
0176             document.getElementById("main").classList.remove("hidden");
0177 
0178             let errorText = status.portLastErrorMessage;
0179             if (errorText === "UNKNOWN") {
0180                 errorText = chrome.i18n.getMessage("general_error_unknown");
0181             }
0182 
0183             if (errorText) {
0184                 document.getElementById("runtime_error_text").innerText = errorText;
0185                 document.getElementById("runtime_error").classList.remove("hidden");
0186 
0187                 // There's some content, hide dummy placeholder
0188                 document.getElementById("dummy-main").classList.add("hidden");
0189             }
0190 
0191             break;
0192         }
0193         }
0194 
0195         // HACK so the extension can tell we closed, see "browserAction" "ready" callback in extension.js
0196         chrome.runtime.onConnect.addListener((port) => {
0197             if (port.name !== "browserActionPort") {
0198                 return;
0199             }
0200 
0201             // do we need to do something with the port here?
0202         });
0203         sendMessage("browserAction", "ready");
0204     });
0205 
0206     // MPris blocker checkboxes
0207     const blocker = new MPrisBlocker();
0208     blocker.getAllowed().then((result) => {
0209         const origins = result.origins;
0210 
0211         if (Object.entries(origins).length === 0) { // "isEmpty"
0212             return;
0213         }
0214 
0215         // To keep media controls setting from always showing up, only show them, if:
0216         // - There is actually a player anywhere on this tab
0217         // or, since when mpris is disabled, there are never any players
0218         // - when media controls are disabled for any origin on this tab
0219         new Promise((resolve, reject) => {
0220             for (let origin in origins) {
0221                 if (origins[origin] === false) {
0222                     return resolve("HAS_BLOCKED");
0223                 }
0224             }
0225 
0226             TabUtils.getCurrentTab().then((tab) => {
0227                 return sendMessage("mpris", "hasTabPlayer", {
0228                     tabId: tab.id
0229                 });
0230             }).then((playerIds) => {
0231                 if (playerIds.length > 0) {
0232                     return resolve("HAS_PLAYER");
0233                 }
0234 
0235                 reject("NO_PLAYER_NO_BLOCKED");
0236             });
0237         }).then(() => {
0238             // There's some content, hide dummy placeholder
0239             document.getElementById("dummy-main").classList.add("hidden");
0240 
0241             let blacklistInfoElement = document.querySelector(".mpris-blacklist-info");
0242             blacklistInfoElement.classList.remove("hidden");
0243 
0244             let originsListElement = blacklistInfoElement.querySelector("ul.mpris-blacklist-origins");
0245 
0246             for (const origin in origins) {
0247                 const originAllowed = origins[origin];
0248 
0249                 let blockListElement = document.createElement("li");
0250 
0251                 let labelElement = document.createElement("label");
0252                 labelElement.innerText = origin;
0253 
0254                 let checkboxElement = document.createElement("input");
0255                 checkboxElement.type = "checkbox";
0256                 checkboxElement.checked = (originAllowed === true);
0257                 checkboxElement.addEventListener("click", (e) => {
0258                     // Let us handle (un)checking the checkbox when setAllowed succeeds
0259                     e.preventDefault();
0260 
0261                     const allowed = checkboxElement.checked;
0262                     blocker.setAllowed(origin, allowed).then(() => {
0263                         checkboxElement.checked = allowed;
0264                     }, (err) => {
0265                         console.warn("Failed to change media controls settings:", err);
0266                     });
0267                 });
0268 
0269                 labelElement.insertBefore(checkboxElement, labelElement.firstChild);
0270 
0271                 blockListElement.appendChild(labelElement);
0272 
0273                 originsListElement.appendChild(blockListElement);
0274             }
0275         }, (err) => {
0276             console.log("Not showing media controls settings because", err);
0277         });
0278     }, (err) => {
0279         console.warn("Failed to check for whether media controls are blocked", err);
0280     });
0281 });