Merge pull request #1322 from NDevTK/master

Move popup to iframe and restrict embeds
This commit is contained in:
Ajay Ramachandran
2022-06-02 21:42:19 -04:00
committed by GitHub
10 changed files with 135 additions and 146 deletions

View File

@@ -8,9 +8,13 @@
<link id="sponsorBlockStyleSheet" href="popup.css" rel="stylesheet"> <link id="sponsorBlockStyleSheet" href="popup.css" rel="stylesheet">
</head> </head>
<body id="sponsorBlockPopupBody"> <body id="sponsorBlockPopupBody" style="visibility: hidden">
<div id="sponsorblockPopup" class="sponsorBlockPageBody sb-preload"> <div id="sponsorblockPopup" class="sponsorBlockPageBody sb-preload">
<button id="sbCloseButton" title="__MSG_closePopup__" class="sbCloseButton hidden">
<img src="icons/close.png" width="15" height="15">
</button>
<div id="sbBetaServerWarning" class="hidden" title="__MSG_openOptionsPage__"> <div id="sbBetaServerWarning" class="hidden" title="__MSG_openOptionsPage__">
__MSG_betaServerWarning__ __MSG_betaServerWarning__
</div> </div>

View File

@@ -84,7 +84,20 @@ chrome.runtime.onMessage.addListener(function (request, _, callback) {
case "unregisterContentScript": case "unregisterContentScript":
unregisterFirefoxContentScript(request.id) unregisterFirefoxContentScript(request.id)
return false; return false;
} case "tabs":
chrome.tabs.query({
active: true,
currentWindow: true
}, tabs => {
chrome.tabs.sendMessage(
tabs[0].id,
request.data,
(response) => callback(response)
);
}
);
return true;
}
}); });
//add help page on install //add help page on install

View File

@@ -5,8 +5,6 @@ import { ContentContainer, Keybind } from "./types";
import Utils from "./utils"; import Utils from "./utils";
const utils = new Utils(); const utils = new Utils();
import runThePopup from "./popup";
import PreviewBar, {PreviewBarSegment} from "./js-components/previewBar"; import PreviewBar, {PreviewBarSegment} from "./js-components/previewBar";
import SkipNotice from "./render/SkipNotice"; import SkipNotice from "./render/SkipNotice";
import SkipNoticeComponent from "./components/SkipNoticeComponent"; import SkipNoticeComponent from "./components/SkipNoticeComponent";
@@ -219,6 +217,9 @@ function messageListener(request: Message, sender: unknown, sendResponse: (respo
utils.addHiddenSegment(sponsorVideoID, request.UUID, request.type); utils.addHiddenSegment(sponsorVideoID, request.UUID, request.type);
updatePreviewBar(); updatePreviewBar();
break; break;
case "closePopup":
closeInfoMenu();
break;
} }
} }
@@ -1686,73 +1687,30 @@ function openInfoMenu() {
//hide info button //hide info button
if (playerButtons.info) playerButtons.info.button.style.display = "none"; if (playerButtons.info) playerButtons.info.button.style.display = "none";
sendRequestToCustomServer('GET', chrome.extension.getURL("popup.html"), function(xmlhttp) {
if (xmlhttp.readyState == 4 && xmlhttp.status == 200) { const popup = document.createElement("div");
const popup = document.createElement("div"); popup.id = "sponsorBlockPopupContainer";
popup.id = "sponsorBlockPopupContainer";
const frame = document.createElement("iframe");
frame.width = "374";
frame.height = "500";
frame.addEventListener("load", () => frame.contentWindow.postMessage("", "*"));
frame.src = chrome.extension.getURL("popup.html");
popup.appendChild(frame);
let htmlData = xmlhttp.responseText; const parentNodes = document.querySelectorAll("#secondary");
// Hack to replace head data (title, favicon) let parentNode = null;
htmlData = htmlData.replace(/<head>[\S\s]*<\/head>/gi, ""); for (let i = 0; i < parentNodes.length; i++) {
// Hack to replace body and html tag with div if (parentNodes[i].firstElementChild !== null) {
htmlData = htmlData.replace(/<body/gi, "<div"); parentNode = parentNodes[i];
htmlData = htmlData.replace(/<\/body/gi, "</div");
htmlData = htmlData.replace(/<html/gi, "<div");
htmlData = htmlData.replace(/<\/html/gi, "</div");
popup.innerHTML = htmlData;
//close button
const closeButton = document.createElement("button");
const closeButtonIcon = document.createElement("img");
closeButtonIcon.src = chrome.extension.getURL("icons/close.png");
closeButtonIcon.width = 15;
closeButtonIcon.height = 15;
closeButton.appendChild(closeButtonIcon);
closeButton.setAttribute("title", chrome.i18n.getMessage("closePopup"));
closeButton.classList.add("sbCloseButton");
closeButton.addEventListener("click", closeInfoMenu);
//add the close button
popup.prepend(closeButton);
const parentNodes = document.querySelectorAll("#secondary");
let parentNode = null;
for (let i = 0; i < parentNodes.length; i++) {
if (parentNodes[i].firstElementChild !== null) {
parentNode = parentNodes[i];
}
}
if (parentNode == null) {
//old youtube theme
parentNode = document.getElementById("watch7-sidebar-contents");
}
//make the logo source not 404
//query selector must be used since getElementByID doesn't work on a node and this isn't added to the document yet
const logo = <HTMLImageElement> popup.querySelector("#sponsorBlockPopupLogo");
const settings = <HTMLImageElement> popup.querySelector("#sbPopupIconSettings");
const edit = <HTMLImageElement> popup.querySelector("#sbPopupIconEdit");
const copy = <HTMLImageElement> popup.querySelector("#sbPopupIconCopyUserID");
const check = <HTMLImageElement> popup.querySelector("#sbPopupIconCheck");
const refreshSegments = <HTMLImageElement> popup.querySelector("#refreshSegments");
const heart = <HTMLImageElement> popup.querySelector(".sbHeart");
const close = <HTMLImageElement> popup.querySelector("#sbCloseDonate");
logo.src = chrome.extension.getURL("icons/IconSponsorBlocker256px.png");
settings.src = chrome.extension.getURL("icons/settings.svg");
edit.src = chrome.extension.getURL("icons/pencil.svg");
copy.src = chrome.extension.getURL("icons/clipboard.svg");
check.src = chrome.extension.getURL("icons/check.svg");
heart.src = chrome.extension.getURL("icons/heart.svg");
close.src = chrome.extension.getURL("icons/close.png");
refreshSegments.src = chrome.extension.getURL("icons/refresh.svg");
parentNode.insertBefore(popup, parentNode.firstChild);
//run the popup init script
runThePopup(messageListener);
} }
}); }
if (parentNode == null) {
//old youtube theme
parentNode = document.getElementById("watch7-sidebar-contents");
}
parentNode.insertBefore(popup, parentNode.firstChild);
} }
function closeInfoMenu() { function closeInfoMenu() {
@@ -2113,25 +2071,6 @@ function addCSS() {
} }
} }
function sendRequestToCustomServer(type, fullAddress, callback) {
const xmlhttp = new XMLHttpRequest();
xmlhttp.open(type, fullAddress, true);
if (callback != undefined) {
xmlhttp.onreadystatechange = function () {
callback(xmlhttp, false);
};
xmlhttp.onerror = function() {
callback(xmlhttp, true);
};
}
//submit this request
xmlhttp.send();
}
/** /**
* Update the isAdPlaying flag and hide preview bar/controls if ad is playing * Update the isAdPlaying flag and hide preview bar/controls if ad is playing
*/ */

View File

@@ -1,15 +1,15 @@
import Config from "./config"; import Config from "./config";
import { showDonationLink } from "./utils/configUtils"; import { showDonationLink } from "./utils/configUtils";
import Utils from "./utils"; import { localizeHtmlPage } from "./utils/pageUtils";
const utils = new Utils(); import { GenericUtils } from "./utils/genericUtils";
window.addEventListener('DOMContentLoaded', init); window.addEventListener('DOMContentLoaded', init);
async function init() { async function init() {
utils.localizeHtmlPage(); localizeHtmlPage();
await utils.wait(() => Config.config !== null); await GenericUtils.wait(() => Config.config !== null);
if (!Config.config.darkMode) { if (!Config.config.darkMode) {
document.documentElement.setAttribute("data-theme", "light"); document.documentElement.setAttribute("data-theme", "light");

View File

@@ -16,7 +16,8 @@ interface DefaultMessage {
| "getChannelID" | "getChannelID"
| "isChannelWhitelisted" | "isChannelWhitelisted"
| "submitTimes" | "submitTimes"
| "refreshSegments"; | "refreshSegments"
| "closePopup";
} }
interface BoolValueMessage { interface BoolValueMessage {

View File

@@ -12,13 +12,14 @@ import Utils from "./utils";
import CategoryChooser from "./render/CategoryChooser"; import CategoryChooser from "./render/CategoryChooser";
import KeybindComponent from "./components/KeybindComponent"; import KeybindComponent from "./components/KeybindComponent";
import { showDonationLink } from "./utils/configUtils"; import { showDonationLink } from "./utils/configUtils";
import { localizeHtmlPage } from "./utils/pageUtils";
const utils = new Utils(); const utils = new Utils();
let embed = false; let embed = false;
window.addEventListener('DOMContentLoaded', init); window.addEventListener('DOMContentLoaded', init);
async function init() { async function init() {
utils.localizeHtmlPage(); localizeHtmlPage();
// selected tab // selected tab
if (location.hash != "") { if (location.hash != "") {

View File

@@ -1,5 +1,6 @@
import Config from "./config"; import Config from "./config";
import Utils from "./utils"; import Utils from "./utils";
import { localizeHtmlPage } from "./utils/pageUtils";
const utils = new Utils(); const utils = new Utils();
// This is needed, if Config is not imported before Utils, things break. // This is needed, if Config is not imported before Utils, things break.
@@ -9,7 +10,7 @@ Config.config;
window.addEventListener('DOMContentLoaded', init); window.addEventListener('DOMContentLoaded', init);
async function init() { async function init() {
utils.localizeHtmlPage(); localizeHtmlPage();
const domains = document.location.hash.replace("#", "").split(","); const domains = document.location.hash.replace("#", "").split(",");

View File

@@ -6,6 +6,7 @@ import { Message, MessageResponse, IsInfoFoundMessageResponse } from "./messageT
import { showDonationLink } from "./utils/configUtils"; import { showDonationLink } from "./utils/configUtils";
import { AnimationUtils } from "./utils/animationUtils"; import { AnimationUtils } from "./utils/animationUtils";
import { GenericUtils } from "./utils/genericUtils"; import { GenericUtils } from "./utils/genericUtils";
import { localizeHtmlPage } from "./utils/pageUtils";
const utils = new Utils(); const utils = new Utils();
interface MessageListener { interface MessageListener {
@@ -22,13 +23,15 @@ class MessageHandler {
sendMessage(id: number, request: Message, callback?) { sendMessage(id: number, request: Message, callback?) {
if (this.messageListener) { if (this.messageListener) {
this.messageListener(request, null, callback); this.messageListener(request, null, callback);
} else { } else if (chrome.tabs) {
chrome.tabs.sendMessage(id, request, callback); chrome.tabs.sendMessage(id, request, callback);
} else {
chrome.runtime.sendMessage({ message: "tabs", data: request }, callback);
} }
} }
query(config, callback) { query(config, callback) {
if (this.messageListener) { if (this.messageListener || !chrome.tabs) {
// Send back dummy info // Send back dummy info
callback([{ callback([{
url: document.URL, url: document.URL,
@@ -41,15 +44,17 @@ class MessageHandler {
} }
} }
// To prevent clickjacking
let allowPopup = window === window.top;
window.addEventListener("message", async (e) => {
if (e.source !== window.parent) return;
if (e.origin.endsWith('.youtube.com')) return allowPopup = true;
});
//make this a function to allow this to run on the content page //make this a function to allow this to run on the content page
async function runThePopup(messageListener?: MessageListener): Promise<void> { async function runThePopup(messageListener?: MessageListener): Promise<void> {
const messageHandler = new MessageHandler(messageListener); const messageHandler = new MessageHandler(messageListener);
localizeHtmlPage();
utils.localizeHtmlPage();
await utils.wait(() => Config.config !== null);
type InputPageElements = { type InputPageElements = {
whitelistToggle?: HTMLInputElement, whitelistToggle?: HTMLInputElement,
@@ -58,6 +63,15 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
}; };
type PageElements = { [key: string]: HTMLElement } & InputPageElements type PageElements = { [key: string]: HTMLElement } & InputPageElements
/** If true, the content script is in the process of creating a new segment. */
let creatingSegment = false;
//the start and end time pairs (2d)
let sponsorTimes: SponsorTime[] = [];
//current video ID of this tab
let currentVideoID = null;
const PageElements: PageElements = {}; const PageElements: PageElements = {};
[ [
@@ -112,9 +126,24 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
"sponsorTimesDonateContainer", "sponsorTimesDonateContainer",
"sbConsiderDonateLink", "sbConsiderDonateLink",
"sbCloseDonate", "sbCloseDonate",
"sbBetaServerWarning" "sbBetaServerWarning",
"sbCloseButton"
].forEach(id => PageElements[id] = document.getElementById(id)); ].forEach(id => PageElements[id] = document.getElementById(id));
getSegmentsFromContentScript(false);
await utils.wait(() => Config.config !== null && allowPopup, 5000, 5);
document.querySelector("body").style.removeProperty("visibility");
PageElements.sbCloseButton.addEventListener("click", () => {
sendTabMessage({
message: "closePopup"
});
});
if (window !== window.top) {
PageElements.sbCloseButton.classList.remove("hidden");
}
// Hide donate button if wanted (Safari, or user choice) // Hide donate button if wanted (Safari, or user choice)
if (!showDonationLink()) { if (!showDonationLink()) {
PageElements.sbDonate.style.display = "none"; PageElements.sbDonate.style.display = "none";
@@ -151,15 +180,6 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
PageElements.refreshSegmentsButton.addEventListener("click", refreshSegments); PageElements.refreshSegmentsButton.addEventListener("click", refreshSegments);
PageElements.sbPopupIconCopyUserID.addEventListener("click", async () => navigator.clipboard.writeText(await utils.getHash(Config.config.userID))); PageElements.sbPopupIconCopyUserID.addEventListener("click", async () => navigator.clipboard.writeText(await utils.getHash(Config.config.userID)));
/** If true, the content script is in the process of creating a new segment. */
let creatingSegment = false;
//the start and end time pairs (2d)
let sponsorTimes: SponsorTime[] = [];
//current video ID of this tab
let currentVideoID = null;
//show proper disable skipping button //show proper disable skipping button
const disableSkipping = Config.config.disableSkipping; const disableSkipping = Config.config.disableSkipping;
if (disableSkipping != undefined && disableSkipping) { if (disableSkipping != undefined && disableSkipping) {
@@ -239,8 +259,6 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
// Must be delayed so it only happens once loaded // Must be delayed so it only happens once loaded
setTimeout(() => PageElements.sponsorblockPopup.classList.remove("preload"), 250); setTimeout(() => PageElements.sponsorblockPopup.classList.remove("preload"), 250);
getSegmentsFromContentScript(false);
function showDonateWidget(viewCount: number) { function showDonateWidget(viewCount: number) {
if (Config.config.showDonationLink && Config.config.donateClicked <= 0 && Config.config.showPopupDonationCount < 5 if (Config.config.showDonationLink && Config.config.donateClicked <= 0 && Config.config.showPopupDonationCount < 5
&& viewCount < 50000 && !Config.config.isVip && Config.config.skipCount > 10) { && viewCount < 50000 && !Config.config.isVip && Config.config.skipCount > 10) {
@@ -272,13 +290,14 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
}); });
} }
function loadTabData(tabs, updating: boolean): void { async function loadTabData(tabs, updating: boolean): Promise<void> {
if (!currentVideoID) { if (!currentVideoID) {
//this isn't a YouTube video then //this isn't a YouTube video then
displayNoVideo(); displayNoVideo();
return; return;
} }
await utils.wait(() => Config.config !== null, 5000, 10);
sponsorTimes = Config.config.unsubmittedSegments[currentVideoID] ?? []; sponsorTimes = Config.config.unsubmittedSegments[currentVideoID] ?? [];
updateSegmentEditingUI(); updateSegmentEditingUI();
@@ -578,6 +597,22 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
chrome.runtime.sendMessage({ "message": "openHelp" }); chrome.runtime.sendMessage({ "message": "openHelp" });
} }
function sendTabMessage(data: Message): Promise<unknown> {
return new Promise((resolve) => {
messageHandler.query({
active: true,
currentWindow: true
}, tabs => {
messageHandler.sendMessage(
tabs[0].id,
data,
(response) => resolve(response)
);
}
);
});
}
//make the options username setting option visible //make the options username setting option visible
function setUsernameButton() { function setUsernameButton() {
PageElements.usernameInput.value = PageElements.usernameValue.innerText; PageElements.usernameInput.value = PageElements.usernameValue.innerText;
@@ -822,9 +857,4 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
//end of function //end of function
} }
if (chrome.tabs != undefined) { runThePopup();
//this means it is actually opened in the popup
runThePopup();
}
export default runThePopup;

View File

@@ -256,31 +256,6 @@ export default class Utils {
} }
} }
localizeHtmlPage(): void {
//Localize by replacing __MSG_***__ meta tags
const localizedMessage = this.getLocalizedMessage(document.title);
if (localizedMessage) document.title = localizedMessage;
const objects = document.getElementsByClassName("sponsorBlockPageBody")[0].children;
for (let j = 0; j < objects.length; j++) {
const obj = objects[j];
const localizedMessage = this.getLocalizedMessage(obj.innerHTML.toString());
if (localizedMessage) obj.innerHTML = localizedMessage;
}
}
getLocalizedMessage(text: string): string | false {
const valNewH = text.replace(/__MSG_(\w+)__/g, function(match, v1) {
return v1 ? chrome.i18n.getMessage(v1).replace(/</g, "&#60;")
.replace(/"/g, "&quot;").replace(/\n/g, "<br/>") : "";
});
if(valNewH != text) {
return valNewH;
} else {
return false;
}
}
/** /**
* @returns {String[]} Domains in regex form * @returns {String[]} Domains in regex form
*/ */

View File

@@ -61,4 +61,29 @@ export function getHashParams(): Record<string, unknown> {
} }
return {}; return {};
}
export function localizeHtmlPage(): void {
//Localize by replacing __MSG_***__ meta tags
const localizedMessage = getLocalizedMessage(document.title);
if (localizedMessage) document.title = localizedMessage;
const objects = document.getElementsByClassName("sponsorBlockPageBody")[0].children;
for (let j = 0; j < objects.length; j++) {
const obj = objects[j];
const localizedMessage = getLocalizedMessage(obj.innerHTML.toString());
if (localizedMessage) obj.innerHTML = localizedMessage;
}
}
export function getLocalizedMessage(text: string): string | false {
const valNewH = text.replace(/__MSG_(\w+)__/g, function(match, v1) {
return v1 ? chrome.i18n.getMessage(v1).replace(/</g, "&#60;")
.replace(/"/g, "&quot;").replace(/\n/g, "<br/>") : "";
});
if (valNewH != text) {
return valNewH;
} else {
return false;
}
} }