Skip to content

Commit

Permalink
Merge pull request #2994
Browse files Browse the repository at this point in the history
Improved widget replacement for Spotify, SoundCloud, Twitch and YouTube.
  • Loading branch information
ghostwords committed Jul 17, 2024
2 parents 6e1cff2 + dd1903b commit c4fedb3
Show file tree
Hide file tree
Showing 4 changed files with 98 additions and 34 deletions.
45 changes: 30 additions & 15 deletions src/data/socialwidgets.json
Original file line number Diff line number Diff line change
Expand Up @@ -342,8 +342,13 @@
},
"SoundCloud": {
"domain": "w.soundcloud.com",
"buttonSelectors": [
"iframe[src^='https://w.soundcloud.com/player']"
"selectors": [
{
"elm": "iframe",
"urls": [
"https://w.soundcloud.com/player"
]
}
],
"replacementButton": {
"unblockDomains": [
Expand All @@ -358,10 +363,15 @@
"open.spotify.com",
"podcasters.spotify.com"
],
"buttonSelectors": [
"iframe[src^='https://embed.spotify.com/']",
"iframe[src^='https://open.spotify.com/embed']",
"iframe[src^='https://podcasters.spotify.com/pod/']"
"selectors": [
{
"elm": "iframe",
"urls": [
"https://embed.spotify.com/",
"https://open.spotify.com/embed",
"https://podcasters.spotify.com/pod/"
]
}
],
"replacementButton": {
"unblockDomains": [
Expand Down Expand Up @@ -472,15 +482,20 @@
"www.youtube.com",
"www.youtube-nocookie.com"
],
"buttonSelectors": [
"iframe[src^='//youtube.com/embed']",
"iframe[src^='//www.youtube.com/embed']",
"iframe[src^='http://www.youtube.com/embed']",
"iframe[src^='https://www.youtube.com/embed']",
"iframe[src^='https://youtube.com/embed']",
"iframe[src^='https://www.youtube.com/v/']",
"iframe[src^='//www.youtube-nocookie.com/embed']",
"iframe[src^='https://www.youtube-nocookie.com/embed']"
"selectors": [
{
"elm": "iframe",
"urls": [
"//youtube.com/embed",
"//www.youtube.com/embed",
"http://www.youtube.com/embed",
"https://www.youtube.com/embed",
"https://youtube.com/embed",
"https://www.youtube.com/v/",
"//www.youtube-nocookie.com/embed",
"https://www.youtube-nocookie.com/embed"
]
}
],
"replacementButton": {
"unblockDomains": [
Expand Down
56 changes: 47 additions & 9 deletions src/js/contentscripts/socialwidgets.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,13 @@ const WIDGET_ELS = {};

let doNotReplace = new WeakSet();

// if the widget element lacks a src property,
// try to use the following dataset properties instead
const lazyLoadDatasetSrcProps = [
"src",
"ezsrc"
];

/**
* @param {Object} response response to checkWidgetReplacementEnabled
*/
Expand Down Expand Up @@ -274,7 +281,15 @@ function restoreWidget(widget) {
unblockTracker(name, function () {
// restore all widgets of this type
WIDGET_ELS[name].forEach(data => {
data.parent.replaceChild(data.widget, data.replacement);
if (!data.origWidgetElem.src) {
for (let prop of lazyLoadDatasetSrcProps) {
if (data.origWidgetElem.dataset[prop]) {
data.origWidgetElem.src = data.origWidgetElem.dataset[prop];
break;
}
}
}
data.parentNode.replaceChild(data.origWidgetElem, data.replacement);
if (data.scriptSelectors) {
// This is part of "click-to-play" for third-party page widgets:
// https://privacybadger.org/#How-does-Privacy-Badger-handle-social-media-widgets
Expand Down Expand Up @@ -472,9 +487,18 @@ function createReplacementWidget(widget, elToReplace) {
let widget_url;
if (widget.directLinkUrl) {
widget_url = widget.directLinkUrl;
} else if (elToReplace.nodeName.toLowerCase() == 'iframe' && elToReplace.src && !widget.noDirectLink) {
} else if (elToReplace.nodeName.toLowerCase() == 'iframe' && !widget.noDirectLink) {
// use the frame URL for framed widgets
widget_url = elToReplace.src;
if (elToReplace.src) {
widget_url = elToReplace.src;
} else {
for (let prop of lazyLoadDatasetSrcProps) {
if (elToReplace.dataset[prop]) {
widget_url = elToReplace.dataset[prop];
break;
}
}
}
} else if (elToReplace.nodeName.toLowerCase() == 'blockquote') {
if (elToReplace.cite && elToReplace.cite.startsWith('https://')) {
// special case for TikTok
Expand Down Expand Up @@ -582,9 +606,9 @@ function createReplacementWidget(widget, elToReplace) {
WIDGET_ELS[name] = [];
}
let data = {
parent: elToReplace.parentNode,
widget: elToReplace,
replacement: widgetFrame
parentNode: elToReplace.parentNode,
replacement: widgetFrame,
origWidgetElem: elToReplace
};
if (widget.scriptSelectors) {
data.scriptSelectors = widget.scriptSelectors;
Expand Down Expand Up @@ -737,8 +761,22 @@ a:hover {
* Replaces buttons/widgets in the DOM.
*/
function replaceIndividualButton(widget) {
let selector = widget.buttonSelectors.join(','),
elsToReplace = document.querySelectorAll(selector);
let elsToReplace = [];

if (widget.buttonSelectors) {
elsToReplace = document.querySelectorAll(widget.buttonSelectors.join(','));
} else if (widget.selectors) {
let selectors = [];
for (let item of widget.selectors) {
for (let url of item.urls) {
selectors.push(`${item.elm}[src^='${url}']`);
for (let prop of lazyLoadDatasetSrcProps) {
selectors.push(`${item.elm}[data-${prop}^='${url}']`);
}
}
}
elsToReplace = document.querySelectorAll(selectors.join(','));
}

for (let el of elsToReplace) {
if (doNotReplace.has(el)) {
Expand All @@ -747,7 +785,7 @@ function replaceIndividualButton(widget) {
// also don't replace if we think we currently have a placeholder
// for this widget type attached to the same parent element
if (hasOwn(WIDGET_ELS, widget.name)) {
if (WIDGET_ELS[widget.name].some(d => d.parent == el.parentNode)) {
if (WIDGET_ELS[widget.name].some(d => d.parentNode == el.parentNode)) {
// something went wrong, give up
continue;
}
Expand Down
8 changes: 0 additions & 8 deletions src/js/contentscripts/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,14 +61,6 @@ window.FRAME_URL = getFrameUrl();

// END FUNCTION DEFINITIONS ///////////////////////////////////////////////////

// register listener in top-level frames only for now
// NOTE: before removing this restriction,
// investigate implications of third-party scripts in nested frames
// generating pbSurrogateMessage events
if (window.top != window) {
return;
}

document.addEventListener("pbSurrogateMessage", function (e) {
if (e.detail && e.detail.type == "widgetFromSurrogate") {
chrome.runtime.sendMessage({
Expand Down
23 changes: 21 additions & 2 deletions src/js/webrequest.js
Original file line number Diff line number Diff line change
Expand Up @@ -1763,11 +1763,30 @@ function dispatcher(request, sender, sendResponse) {
// proxies surrogate script-initiated widget replacement messages
// from one content script to another
case "widgetFromSurrogate": {
let tab_host = extractHostFromURL(sender.tab.url);
let tab_url = sender.tab.url,
tab_host = extractHostFromURL(tab_url);
if (!badger.isPrivacyBadgerEnabled(tab_host)) {
break;
}

// accept widget surrogate messages only from top-level,
// first-party, and Embedly frames
//
// NOTE: before removing this restriction, investigate
// implications of accepting pbSurrogateMessage events
// from third-party scripts in nested frames
if (sender.frameId > 0) {
if (!request.frameUrl.startsWith('https://cdn.embedly.com/')) {
let tab_scheme = tab_url.slice(0, tab_url.indexOf(tab_host));
if (!request.frameUrl.startsWith(tab_scheme + tab_host)) {
let frame_host = extractHostFromURL(request.frameUrl);
if (!frame_host || utils.isThirdPartyDomain(frame_host, tab_host)) {
break;
}
}
}
}

if (request.name == "X (Twitter)") {
// no need to build a custom widget,
// merely rescan the page for Twitter embeds
Expand All @@ -1776,7 +1795,7 @@ function dispatcher(request, sender, sendResponse) {
trackerDomain: "platform.twitter.com",
frameId: sender.frameId
});
return;
break;
}

// NOTE: request.name and request.data are not to be trusted
Expand Down

0 comments on commit c4fedb3

Please sign in to comment.