From cdd9eca37e8b332fabc385c5d347e239970dcd64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Luba=C5=84ski?= Date: Fri, 17 Nov 2023 16:07:22 +0100 Subject: [PATCH 1/3] Add Youtube wall popup to v10 --- .../app/content-scripts/youtube.js | 32 +++--- extension-manifest-v2/app/youtube/index.js | 9 +- .../src/background/helpers.js | 6 + .../src/content_scripts/youtube.js | 47 ++++++++ .../src/manifest.chromium.json | 10 +- .../src/manifest.firefox.json | 10 +- .../src/manifest.safari.json | 10 +- .../src/pages/youtube/index.html | 10 ++ .../src/pages/youtube/index.js | 53 +++++++++ packages/ui/package.json | 2 + .../ui/src/modules/youtube/components/wall.js | 107 ++++++++++++++++++ packages/ui/src/modules/youtube/index.js | 23 ++++ packages/ui/src/modules/youtube/wall.js | 39 +++++++ packages/ui/src/utils/iframe.js | 5 + 14 files changed, 339 insertions(+), 24 deletions(-) create mode 100644 extension-manifest-v3/src/content_scripts/youtube.js create mode 100644 extension-manifest-v3/src/pages/youtube/index.html create mode 100644 extension-manifest-v3/src/pages/youtube/index.js create mode 100644 packages/ui/src/modules/youtube/components/wall.js create mode 100644 packages/ui/src/modules/youtube/index.js create mode 100644 packages/ui/src/modules/youtube/wall.js diff --git a/extension-manifest-v2/app/content-scripts/youtube.js b/extension-manifest-v2/app/content-scripts/youtube.js index 93c461110..a3807de6e 100644 --- a/extension-manifest-v2/app/content-scripts/youtube.js +++ b/extension-manifest-v2/app/content-scripts/youtube.js @@ -1,11 +1,16 @@ -import { showIframe, closeIframe } from '@ghostery/ui/iframe'; - -// Based on https://github.com/AdguardTeam/AdguardFilters/blob/e5ae8e3194f8d18bdcc660d4c42282e4a96ca5b9/AnnoyancesFilter/Popups/sections/antiadblock.txt#L2044 -const ADBLOCKER_WALL_SELECTORS = [ - 'ytd-watch-flexy:not([hidden]) ytd-enforcement-message-view-model > div.ytd-enforcement-message-view-model', -]; +/** + * Ghostery Browser Extension + * https://www.ghostery.com/ + * + * Copyright 2017-present Ghostery GmbH. All rights reserved. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0 + */ -let isShown = false; +import detectWall from '@ghostery/ui/youtube/wall'; +import { showIframe, closeIframe } from '@ghostery/ui/iframe'; chrome.storage.local.get(['youtube_dont_show_again'], (storage) => { if (storage.youtube_dont_show_again || chrome.extension.inIncognitoContext) { @@ -13,14 +18,13 @@ chrome.storage.local.get(['youtube_dont_show_again'], (storage) => { } window.addEventListener('yt-navigate-start', () => { - isShown = false; closeIframe(); }, true); - setInterval(() => { - if (!isShown && document.querySelectorAll(ADBLOCKER_WALL_SELECTORS).length > 0) { - showIframe(chrome.runtime.getURL(`app/templates/youtube.html?url=${encodeURIComponent(window.location.href)}`), '460px'); - isShown = true; - } - }, 2000); + detectWall(() => { + showIframe( + chrome.runtime.getURL(`app/templates/youtube.html?url=${encodeURIComponent(window.location.href)}`), + '460px', + ); + }); }); diff --git a/extension-manifest-v2/app/youtube/index.js b/extension-manifest-v2/app/youtube/index.js index f77b2b1dd..6790179af 100644 --- a/extension-manifest-v2/app/youtube/index.js +++ b/extension-manifest-v2/app/youtube/index.js @@ -8,10 +8,10 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0 */ + import { mount, html } from 'hybrids'; -import '@ghostery/ui/onboarding'; -import './components/message'; +import '@ghostery/ui/youtube'; import { setupIframe, closeIframe } from '@ghostery/ui/iframe'; setupIframe(); @@ -25,6 +25,7 @@ function openPrivateWindow() { url, }, }); + closeIframe(); } function openBlog(slug) { @@ -46,12 +47,12 @@ function dontAsk() { mount(document.body, { content: () => html` - + > `, }); diff --git a/extension-manifest-v3/src/background/helpers.js b/extension-manifest-v3/src/background/helpers.js index b6d3a8dce..6d85911c0 100644 --- a/extension-manifest-v3/src/background/helpers.js +++ b/extension-manifest-v3/src/background/helpers.js @@ -16,6 +16,12 @@ chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => { sendResponse(tab); }); return true; + case 'openTabWithUrl': + chrome.tabs.create({ url: msg.url }); + break; + case 'openPrivateWindowWithUrl': + chrome.windows.create({ url: msg.url, incognito: true }); + break; } return false; diff --git a/extension-manifest-v3/src/content_scripts/youtube.js b/extension-manifest-v3/src/content_scripts/youtube.js new file mode 100644 index 000000000..a5acb443b --- /dev/null +++ b/extension-manifest-v3/src/content_scripts/youtube.js @@ -0,0 +1,47 @@ +/** + * Ghostery Browser Extension + * https://www.ghostery.com/ + * + * Copyright 2017-present Ghostery GmbH. All rights reserved. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0 + */ + +import { showIframe, closeIframe } from '@ghostery/ui/iframe'; +import detectWall from '@ghostery/ui/youtube/wall'; + +(async () => { + // INFO: Safari always returns false for `inIncognitoContext` + if (chrome.extension.inIncognitoContext) return; + + const { options, youtubeDontAsk } = await chrome.storage.local.get([ + 'options', + 'youtubeDontAsk', + ]); + + if ( + // User's choice to not show the wall + youtubeDontAsk || + // Terms not accepted or paused domain + !options || + !options.terms || + options.paused.some(({ id }) => id.includes('youtube.com')) + ) { + return; + } + + window.addEventListener('yt-navigate-start', () => closeIframe(), true); + + detectWall(() => { + showIframe( + chrome.runtime.getURL( + `/pages/youtube/index.html?url=${encodeURIComponent( + window.location.href, + )}`, + ), + '460px', + ); + }); +})(); diff --git a/extension-manifest-v3/src/manifest.chromium.json b/extension-manifest-v3/src/manifest.chromium.json index 9a52f4ab8..05f239e16 100644 --- a/extension-manifest-v3/src/manifest.chromium.json +++ b/extension-manifest-v3/src/manifest.chromium.json @@ -91,7 +91,12 @@ "js": [ "content_scripts/whotracksme/reporting.js" ] - } + }, + { + "matches": ["*://www.youtube.com/*"], + "run_at": "document_start", + "js": ["content_scripts/youtube.js"] + } ], "web_accessible_resources": [ { @@ -103,7 +108,8 @@ "pages/autoconsent/index.html", "pages/onboarding/index.html", "pages/onboarding/iframe.html", - "pages/onboarding/opera-serp.html" + "pages/onboarding/opera-serp.html", + "pages/youtube/index.html" ], "all_frames": true, "matches": [ diff --git a/extension-manifest-v3/src/manifest.firefox.json b/extension-manifest-v3/src/manifest.firefox.json index e5f902a74..8c7cd37c4 100644 --- a/extension-manifest-v3/src/manifest.firefox.json +++ b/extension-manifest-v3/src/manifest.firefox.json @@ -73,7 +73,12 @@ "js": [ "content_scripts/whotracksme/reporting.js" ] - } + }, + { + "matches": ["*://www.youtube.com/*"], + "run_at": "document_start", + "js": ["content_scripts/youtube.js"] + } ], "web_accessible_resources": [ "content_scripts/trackers-preview.js", @@ -81,7 +86,8 @@ "pages/trackers-preview/index.html", "pages/autoconsent/index.html", "pages/onboarding/index.html", - "pages/onboarding/iframe.html" + "pages/onboarding/iframe.html", + "pages/youtube/index.html" ], "browser_specific_settings": { "gecko": { diff --git a/extension-manifest-v3/src/manifest.safari.json b/extension-manifest-v3/src/manifest.safari.json index 3f7306561..1fd62689c 100644 --- a/extension-manifest-v3/src/manifest.safari.json +++ b/extension-manifest-v3/src/manifest.safari.json @@ -94,7 +94,12 @@ "js": [ "content_scripts/whotracksme/reporting.js" ] - } + }, + { + "matches": ["*://www.youtube.com/*"], + "run_at": "document_start", + "js": ["content_scripts/youtube.js"] + } ], "web_accessible_resources": [ "content_scripts/whotracksme/ghostery-whotracksme.js", @@ -103,7 +108,8 @@ "pages/trackers-preview/index.html", "pages/autoconsent/index.html", "pages/onboarding/index.html", - "pages/onboarding/iframe.html" + "pages/onboarding/iframe.html", + "pages/youtube/index.html" ], "content_security_policy" : {}, "browser_specific_settings": { diff --git a/extension-manifest-v3/src/pages/youtube/index.html b/extension-manifest-v3/src/pages/youtube/index.html new file mode 100644 index 000000000..60fa161ec --- /dev/null +++ b/extension-manifest-v3/src/pages/youtube/index.html @@ -0,0 +1,10 @@ + + + + + + Youtube Wall + + + + diff --git a/extension-manifest-v3/src/pages/youtube/index.js b/extension-manifest-v3/src/pages/youtube/index.js new file mode 100644 index 000000000..83c1722bb --- /dev/null +++ b/extension-manifest-v3/src/pages/youtube/index.js @@ -0,0 +1,53 @@ +/** + * Ghostery Browser Extension + * https://www.ghostery.com/ + * + * Copyright 2017-present Ghostery GmbH. All rights reserved. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0 + */ + +import { mount, html } from 'hybrids'; + +import '@ghostery/ui/youtube'; +import { setupIframe, closeIframe } from '@ghostery/ui/iframe'; + +function dontAsk() { + chrome.storage.local.set({ + youtubeDontAsk: true, + }); + closeIframe(); +} + +function openBlog(slug) { + chrome.runtime.sendMessage({ + action: 'openTabWithUrl', + url: `https://www.ghostery.com/blog/${slug}?utm_source=gbe&utm_campaign=youtube`, + }); +} + +function openPrivateWindow() { + chrome.runtime.sendMessage({ + action: 'openPrivateWindowWithUrl', + url: new URLSearchParams(window.location.search).get('url'), + }); + closeIframe(); +} + +setupIframe(); + +mount(document.body, { + content: () => html` + + `, +}); diff --git a/packages/ui/package.json b/packages/ui/package.json index 7d7fc6e54..7c0c0d63f 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -11,6 +11,8 @@ "./panel": "./src/modules/panel/index.js", "./settings": "./src/modules/settings/index.js", "./trackers-preview": "./src/modules/trackers-preview/index.js", + "./youtube": "./src/modules/youtube/index.js", + "./youtube/wall": "./src/modules/youtube/wall.js", "./categories": "./src/utils/categories.js", "./labels": "./src/utils/labels.js", "./wheel": "./src/utils/wheel.js", diff --git a/packages/ui/src/modules/youtube/components/wall.js b/packages/ui/src/modules/youtube/components/wall.js new file mode 100644 index 000000000..97cf5ab35 --- /dev/null +++ b/packages/ui/src/modules/youtube/components/wall.js @@ -0,0 +1,107 @@ +/** + * Ghostery Browser Extension + * https://www.ghostery.com/ + * + * Copyright 2017-present Ghostery GmbH. All rights reserved. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0 + */ + +import { html, dispatch } from 'hybrids'; + +export default { + content: () => html` + + `, +}; diff --git a/packages/ui/src/modules/youtube/index.js b/packages/ui/src/modules/youtube/index.js new file mode 100644 index 000000000..6f3ddb986 --- /dev/null +++ b/packages/ui/src/modules/youtube/index.js @@ -0,0 +1,23 @@ +/** + * Ghostery Browser Extension + * https://www.ghostery.com/ + * + * Copyright 2017-present Ghostery GmbH. All rights reserved. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0 + */ + +import { define } from 'hybrids'; + +import '../onboarding/index.js'; + +// Components +define.from( + import.meta.glob('./components/*.js', { eager: true, import: 'default' }), + { + prefix: 'ui-youtube', + root: 'components', + }, +); diff --git a/packages/ui/src/modules/youtube/wall.js b/packages/ui/src/modules/youtube/wall.js new file mode 100644 index 000000000..5e796ab7f --- /dev/null +++ b/packages/ui/src/modules/youtube/wall.js @@ -0,0 +1,39 @@ +/** + * Ghostery Browser Extension + * https://www.ghostery.com/ + * + * Copyright 2017-present Ghostery GmbH. All rights reserved. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0 + */ + +const SELECTORS = [ + // Based on https://github.com/AdguardTeam/AdguardFilters/blob/e5ae8e3194f8d18bdcc660d4c42282e4a96ca5b9/AnnoyancesFilter/Popups/sections/antiadblock.txt#L2044 + 'ytd-watch-flexy:not([hidden]) ytd-enforcement-message-view-model > div.ytd-enforcement-message-view-model', + + 'yt-playability-error-supported-renderers#error-screen ytd-enforcement-message-view-model', + 'tp-yt-paper-dialog .ytd-enforcement-message-view-model', +]; + +export default function detectWall(cb) { + let currentHref = ''; + + const observer = new MutationObserver(() => { + if (currentHref === location.href) return; + currentHref = location.href; + + if (document.querySelector(SELECTORS)?.clientHeight > 0) { + cb(); + } + }); + + document.addEventListener('DOMContentLoaded', () => { + observer.observe(document.body, { + childList: true, + subtree: true, + attributeFilter: ['src', 'style'], + }); + }); +} diff --git a/packages/ui/src/utils/iframe.js b/packages/ui/src/utils/iframe.js index a00ed6c39..dde1d0014 100644 --- a/packages/ui/src/utils/iframe.js +++ b/packages/ui/src/utils/iframe.js @@ -10,6 +10,11 @@ */ export function showIframe(url, width = '440px') { + // Prevent multiple iframes be shown at the same time + if (document.querySelector('ghostery-iframe-wrapper')) { + return; + } + const wrapper = document.createElement('ghostery-iframe-wrapper'); const shadowRoot = wrapper.attachShadow({ mode: 'closed' }); From 5829c3144f3903b64c04564cfa335301c8fd366e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Luba=C5=84ski?= Date: Mon, 20 Nov 2023 13:40:57 +0100 Subject: [PATCH 2/3] Use debounce callback --- packages/ui/src/modules/youtube/wall.js | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/packages/ui/src/modules/youtube/wall.js b/packages/ui/src/modules/youtube/wall.js index 5e796ab7f..4a838f85e 100644 --- a/packages/ui/src/modules/youtube/wall.js +++ b/packages/ui/src/modules/youtube/wall.js @@ -16,17 +16,24 @@ const SELECTORS = [ 'yt-playability-error-supported-renderers#error-screen ytd-enforcement-message-view-model', 'tp-yt-paper-dialog .ytd-enforcement-message-view-model', ]; +const DELAY = 2000; export default function detectWall(cb) { - let currentHref = ''; + let timeout = null; const observer = new MutationObserver(() => { - if (currentHref === location.href) return; - currentHref = location.href; + if (timeout) return; - if (document.querySelector(SELECTORS)?.clientHeight > 0) { - cb(); - } + timeout = setTimeout(() => { + timeout = null; + if (document.querySelector(SELECTORS)?.clientHeight > 0) { + try { + cb(); + } catch (e) { + /* ignore */ + } + } + }, DELAY); }); document.addEventListener('DOMContentLoaded', () => { From ec77e51b08d0922d5638578edbba290d3a54307b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Luba=C5=84ski?= Date: Mon, 20 Nov 2023 14:45:20 +0100 Subject: [PATCH 3/3] Fix check condition & prevent re-open popup when closed --- .../app/content-scripts/youtube.js | 38 +++--- .../app/youtube/components/message.js | 112 ------------------ .../src/content_scripts/youtube.js | 44 ++++--- packages/ui/src/modules/youtube/wall.js | 10 +- packages/ui/src/utils/iframe.js | 8 +- 5 files changed, 64 insertions(+), 148 deletions(-) delete mode 100644 extension-manifest-v2/app/youtube/components/message.js diff --git a/extension-manifest-v2/app/content-scripts/youtube.js b/extension-manifest-v2/app/content-scripts/youtube.js index a3807de6e..3b79583b8 100644 --- a/extension-manifest-v2/app/content-scripts/youtube.js +++ b/extension-manifest-v2/app/content-scripts/youtube.js @@ -12,19 +12,29 @@ import detectWall from '@ghostery/ui/youtube/wall'; import { showIframe, closeIframe } from '@ghostery/ui/iframe'; -chrome.storage.local.get(['youtube_dont_show_again'], (storage) => { - if (storage.youtube_dont_show_again || chrome.extension.inIncognitoContext) { - return; - } +function isFeatureDisabled() { + return new Promise((resolve) => { + chrome.storage.local.get(['youtube_dont_show_again'], (storage) => { + resolve(storage.youtube_dont_show_again); + }); + }); +} - window.addEventListener('yt-navigate-start', () => { - closeIframe(); - }, true); +if (!chrome.extension.inIncognitoContext) { + (async () => { + if (await isFeatureDisabled()) return; - detectWall(() => { - showIframe( - chrome.runtime.getURL(`app/templates/youtube.html?url=${encodeURIComponent(window.location.href)}`), - '460px', - ); - }); -}); + window.addEventListener('yt-navigate-start', () => { + closeIframe(); + }, true); + + detectWall(async () => { + if (await isFeatureDisabled()) return; + + showIframe( + chrome.runtime.getURL(`app/templates/youtube.html?url=${encodeURIComponent(window.location.href)}`), + '460px', + ); + }); + })(); +} diff --git a/extension-manifest-v2/app/youtube/components/message.js b/extension-manifest-v2/app/youtube/components/message.js deleted file mode 100644 index b727576f4..000000000 --- a/extension-manifest-v2/app/youtube/components/message.js +++ /dev/null @@ -1,112 +0,0 @@ -/** - * Ghostery Browser Extension - * https://www.ghostery.com/ - * - * Copyright 2017-present Ghostery GmbH. All rights reserved. - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0 - */ - -import { define, html, dispatch } from 'hybrids'; - -export default define({ - tag: 'youtube-message', - content: () => html` - - `.css` - .hr { - background: #D4D6D9; - height: 1px; - align-self: stretch; - } - `, -}); diff --git a/extension-manifest-v3/src/content_scripts/youtube.js b/extension-manifest-v3/src/content_scripts/youtube.js index a5acb443b..df666f3ea 100644 --- a/extension-manifest-v3/src/content_scripts/youtube.js +++ b/extension-manifest-v3/src/content_scripts/youtube.js @@ -12,10 +12,7 @@ import { showIframe, closeIframe } from '@ghostery/ui/iframe'; import detectWall from '@ghostery/ui/youtube/wall'; -(async () => { - // INFO: Safari always returns false for `inIncognitoContext` - if (chrome.extension.inIncognitoContext) return; - +async function isFeatureDisabled() { const { options, youtubeDontAsk } = await chrome.storage.local.get([ 'options', 'youtubeDontAsk', @@ -29,19 +26,30 @@ import detectWall from '@ghostery/ui/youtube/wall'; !options.terms || options.paused.some(({ id }) => id.includes('youtube.com')) ) { - return; + return true; } - window.addEventListener('yt-navigate-start', () => closeIframe(), true); - - detectWall(() => { - showIframe( - chrome.runtime.getURL( - `/pages/youtube/index.html?url=${encodeURIComponent( - window.location.href, - )}`, - ), - '460px', - ); - }); -})(); + return false; +} + +// INFO: Safari always returns false for `inIncognitoContext` +if (!chrome.extension.inIncognitoContext) { + (async () => { + if (await isFeatureDisabled()) return; + + window.addEventListener('yt-navigate-start', () => closeIframe(), true); + + detectWall(async () => { + if (await isFeatureDisabled()) return; + + showIframe( + chrome.runtime.getURL( + `/pages/youtube/index.html?url=${encodeURIComponent( + window.location.href, + )}`, + ), + '460px', + ); + }); + })(); +} diff --git a/packages/ui/src/modules/youtube/wall.js b/packages/ui/src/modules/youtube/wall.js index 4a838f85e..891b071b1 100644 --- a/packages/ui/src/modules/youtube/wall.js +++ b/packages/ui/src/modules/youtube/wall.js @@ -16,7 +16,7 @@ const SELECTORS = [ 'yt-playability-error-supported-renderers#error-screen ytd-enforcement-message-view-model', 'tp-yt-paper-dialog .ytd-enforcement-message-view-model', ]; -const DELAY = 2000; +const DELAY = 1000; // 1 second export default function detectWall(cb) { let timeout = null; @@ -25,17 +25,23 @@ export default function detectWall(cb) { if (timeout) return; timeout = setTimeout(() => { - timeout = null; if (document.querySelector(SELECTORS)?.clientHeight > 0) { try { cb(); } catch (e) { /* ignore */ } + } else { + timeout = null; } }, DELAY); }); + document.addEventListener('yt-navigate-start', () => { + clearTimeout(timeout); + timeout = null; + }); + document.addEventListener('DOMContentLoaded', () => { observer.observe(document.body, { childList: true, diff --git a/packages/ui/src/utils/iframe.js b/packages/ui/src/utils/iframe.js index dde1d0014..77562ec63 100644 --- a/packages/ui/src/utils/iframe.js +++ b/packages/ui/src/utils/iframe.js @@ -98,12 +98,16 @@ export function showIframe(url, width = '440px') { if (e.data.reload) { window.location.reload(); } else { - setTimeout(() => wrapper.parentElement.removeChild(wrapper), 0); + if (wrapper.parentElement) { + setTimeout(() => wrapper.parentElement.removeChild(wrapper), 0); + } } break; case 'ghostery-clear-iframe': if (iframe.src === e.data.url) { - setTimeout(() => wrapper.parentElement.removeChild(wrapper), 0); + if (wrapper.parentElement) { + setTimeout(() => wrapper.parentElement.removeChild(wrapper), 0); + } } break; default: