Bug 1901602 - Add Twitter SmartBlock Embeds shim. r=emz,webcompat-reviewers,twisniewski
Differential Revision: https://phabricator.services.mozilla.com/D239815
This commit is contained in:
@@ -1397,6 +1397,11 @@ var gProtectionsHandler = {
|
||||
shimId: "TiktokEmbed",
|
||||
displayName: "Tiktok",
|
||||
},
|
||||
{
|
||||
sites: ["https://platform.twitter.com"],
|
||||
shimId: "TwitterEmbed",
|
||||
displayName: "X",
|
||||
},
|
||||
],
|
||||
|
||||
/**
|
||||
|
||||
@@ -1025,6 +1025,33 @@ const AVAILABLE_SHIMS = [
|
||||
["*://steam.tv/*", "*://checkout.steampowered.com/*"],
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "TwitterEmbed",
|
||||
platform: "desktop",
|
||||
name: "Twitter embed placeholder",
|
||||
bug: "1901602",
|
||||
runFirst: "twitter-embed.js",
|
||||
// Blank stub file just so we run the script above when the matched script
|
||||
// files get blocked.
|
||||
file: "empty-script.js",
|
||||
matches: ["https://platform.twitter.com/widgets.js"],
|
||||
logos: ["x-logo.svg"],
|
||||
needsShimHelpers: [
|
||||
"embedClicked",
|
||||
"smartblockEmbedReplaced",
|
||||
"smartblockGetFluentString",
|
||||
],
|
||||
isSmartblockEmbedShim: true,
|
||||
onlyIfBlockedByETP: true,
|
||||
unblocksOnOptIn: [
|
||||
"*://platform.twitter.com/*",
|
||||
"*://syndication.twitter.com/*",
|
||||
"*://cdn.syndication.twimg.com/*",
|
||||
"*://pbs.twimg.com/*",
|
||||
"*://abs.twimg.com/*",
|
||||
"*://abs-0.twimg.com/*",
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
if (typeof module !== "undefined") {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"manifest_version": 2,
|
||||
"name": "Web Compatibility Interventions",
|
||||
"description": "Urgent post-release fixes for web compatibility.",
|
||||
"version": "138.2.0",
|
||||
"version": "138.3.0",
|
||||
"browser_specific_settings": {
|
||||
"gecko": {
|
||||
"id": "webcompat@mozilla.org",
|
||||
@@ -164,10 +164,12 @@
|
||||
"shims/tiktok.svg",
|
||||
"shims/tracking-pixel.png",
|
||||
"shims/tsn-ca.js",
|
||||
"shims/twitter-embed.js",
|
||||
"shims/vast2.xml",
|
||||
"shims/vast3.xml",
|
||||
"shims/vidible.js",
|
||||
"shims/vmad.xml",
|
||||
"shims/webtrends.js"
|
||||
"shims/webtrends.js",
|
||||
"shims/x-logo.svg"
|
||||
]
|
||||
}
|
||||
|
||||
237
browser/extensions/webcompat/shims/twitter-embed.js
Normal file
237
browser/extensions/webcompat/shims/twitter-embed.js
Normal file
@@ -0,0 +1,237 @@
|
||||
/* 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 https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
/* globals browser */
|
||||
|
||||
if (!window.smartblockTwitterShimInitialized) {
|
||||
// Guard against this script running multiple times
|
||||
window.smartblockTwitterShimInitialized = true;
|
||||
|
||||
const SHIM_ID = "TwitterEmbed";
|
||||
|
||||
const SHIM_EMBED_CLASSES = ["twitter-tweet", "twitter-timeline"];
|
||||
const SHIM_CLASS_SELECTORS = SHIM_EMBED_CLASSES.map(
|
||||
className => `.${className}`
|
||||
).join(",");
|
||||
|
||||
// Original URL of the embed script.
|
||||
const ORIGINAL_URL = "https://platform.twitter.com/widgets.js";
|
||||
const LOGO_URL = "https://smartblock.firefox.etp/x-logo.svg";
|
||||
|
||||
// Timeout for observing new changes to the page
|
||||
const OBSERVER_TIMEOUT_MS = 10000;
|
||||
let observerTimeout;
|
||||
let newEmbedObserver;
|
||||
|
||||
let originalEmbedContainers = [];
|
||||
let embedPlaceholders = [];
|
||||
|
||||
function sendMessageToAddon(message) {
|
||||
return browser.runtime.sendMessage({ message, shimId: SHIM_ID });
|
||||
}
|
||||
|
||||
function addonMessageHandler(message) {
|
||||
let { topic, shimId } = message;
|
||||
// Only react to messages which are targeting this shim.
|
||||
if (shimId != SHIM_ID) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (topic === "smartblock:unblock-embed") {
|
||||
if (newEmbedObserver) {
|
||||
newEmbedObserver.disconnect();
|
||||
newEmbedObserver = null;
|
||||
}
|
||||
|
||||
if (observerTimeout) {
|
||||
clearTimeout(observerTimeout);
|
||||
}
|
||||
|
||||
// remove embed placeholders
|
||||
embedPlaceholders.forEach((p, idx) => {
|
||||
p.replaceWith(originalEmbedContainers[idx]);
|
||||
});
|
||||
|
||||
// recreate scripts
|
||||
let scriptElement = document.createElement("script");
|
||||
|
||||
// Set the script element's src with the website's principal instead of
|
||||
// the content script principal to ensure the tracker script is not loaded
|
||||
// via the content script's expanded principal.
|
||||
scriptElement.wrappedJSObject.src = ORIGINAL_URL;
|
||||
document.body.appendChild(scriptElement);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces embeds with a SmartBlock Embed placeholder. Optionally takes a list
|
||||
* of embeds to replace, otherwise will search for all embeds on the page.
|
||||
*
|
||||
* @param {HTMLElement[]} embedContainers - Array of elements to replace with placeholders.
|
||||
* If the array is empty, this function will search
|
||||
* for and replace all embeds on the page.
|
||||
*/
|
||||
async function createShimPlaceholders(embedContainers = []) {
|
||||
const [titleString, descriptionString, buttonString] =
|
||||
await sendMessageToAddon("smartblockGetFluentString");
|
||||
|
||||
if (!embedContainers.length) {
|
||||
// No containers were passed in, do own search for containers
|
||||
embedContainers = document.querySelectorAll(SHIM_CLASS_SELECTORS);
|
||||
}
|
||||
|
||||
embedContainers.forEach(originalContainer => {
|
||||
// this string has to be defined within this function to avoid linting errors
|
||||
// see: https://github.com/mozilla/eslint-plugin-no-unsanitized/issues/259
|
||||
const SMARTBLOCK_PLACEHOLDER_HTML_STRING = `
|
||||
<style>
|
||||
#smartblock-placeholder-wrapper {
|
||||
min-height: 225px;
|
||||
width: 400px;
|
||||
padding: 32px 24px;
|
||||
|
||||
display: block;
|
||||
align-content: center;
|
||||
text-align: center;
|
||||
|
||||
background-color: light-dark(rgb(255, 255, 255), rgb(28, 27, 34));
|
||||
color: light-dark(rgb(43, 42, 51), rgb(251, 251, 254));
|
||||
|
||||
border-radius: 8px;
|
||||
border: 2px dashed #0250bb;
|
||||
|
||||
font-size: 14px;
|
||||
line-height: 1.2;
|
||||
font-family: system-ui;
|
||||
}
|
||||
|
||||
#smartblock-placeholder-button {
|
||||
min-height: 32px;
|
||||
padding: 8px 14px;
|
||||
|
||||
border-radius: 4px;
|
||||
font-weight: 600;
|
||||
border: 0;
|
||||
|
||||
/* Colours match light/dark theme from
|
||||
https://searchfox.org/mozilla-central/source/browser/themes/addons/light/manifest.json
|
||||
https://searchfox.org/mozilla-central/source/browser/themes/addons/dark/manifest.json */
|
||||
background-color: light-dark(rgb(0, 97, 224), rgb(0, 221, 255));
|
||||
color: light-dark(rgb(251, 251, 254), rgb(43, 42, 51));
|
||||
}
|
||||
|
||||
#smartblock-placeholder-button:hover {
|
||||
/* Colours match light/dark theme from
|
||||
https://searchfox.org/mozilla-central/source/browser/themes/addons/light/manifest.json
|
||||
https://searchfox.org/mozilla-central/source/browser/themes/addons/dark/manifest.json */
|
||||
background-color: light-dark(rgb(2, 80, 187), rgb(128, 235, 255));
|
||||
}
|
||||
|
||||
#smartblock-placeholder-button:hover:active {
|
||||
/* Colours match light/dark theme from
|
||||
https://searchfox.org/mozilla-central/source/browser/themes/addons/light/manifest.json
|
||||
https://searchfox.org/mozilla-central/source/browser/themes/addons/dark/manifest.json */
|
||||
background-color: light-dark(rgb(5, 62, 148), rgb(170, 242, 255));
|
||||
}
|
||||
|
||||
#smartblock-placeholder-title {
|
||||
margin-block: 14px;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#smartblock-placeholder-desc {
|
||||
margin-block: 14px;
|
||||
}
|
||||
</style>
|
||||
<div id="smartblock-placeholder-wrapper">
|
||||
<img id="smartblock-placeholder-image" width="24" height="24" />
|
||||
<p id="smartblock-placeholder-title"></p>
|
||||
<p id="smartblock-placeholder-desc"></p>
|
||||
<button id="smartblock-placeholder-button"></button>
|
||||
</div>`;
|
||||
|
||||
// Create the placeholder inside a shadow dom
|
||||
const placeholderDiv = document.createElement("div");
|
||||
|
||||
const shadowRoot = placeholderDiv.attachShadow({ mode: "closed" });
|
||||
|
||||
shadowRoot.innerHTML = SMARTBLOCK_PLACEHOLDER_HTML_STRING;
|
||||
shadowRoot.getElementById("smartblock-placeholder-image").src = LOGO_URL;
|
||||
shadowRoot.getElementById("smartblock-placeholder-title").textContent =
|
||||
titleString;
|
||||
shadowRoot.getElementById("smartblock-placeholder-desc").textContent =
|
||||
descriptionString;
|
||||
shadowRoot.getElementById("smartblock-placeholder-button").textContent =
|
||||
buttonString;
|
||||
|
||||
// Wait for user to opt-in.
|
||||
shadowRoot
|
||||
.getElementById("smartblock-placeholder-button")
|
||||
.addEventListener("click", ({ isTrusted }) => {
|
||||
if (!isTrusted) {
|
||||
return;
|
||||
}
|
||||
// Send a message to the addon to allow loading tracking resources
|
||||
// needed by the embed.
|
||||
sendMessageToAddon("embedClicked");
|
||||
});
|
||||
|
||||
// Save the original embed element and the newly created placeholder
|
||||
embedPlaceholders.push(placeholderDiv);
|
||||
originalEmbedContainers.push(originalContainer);
|
||||
|
||||
// Replace the embed with the placeholder
|
||||
originalContainer.replaceWith(placeholderDiv);
|
||||
|
||||
sendMessageToAddon("smartblockEmbedReplaced");
|
||||
});
|
||||
}
|
||||
|
||||
// Listen for messages from the background script.
|
||||
browser.runtime.onMessage.addListener(request => {
|
||||
addonMessageHandler(request);
|
||||
});
|
||||
|
||||
// Monitor for new embeds being added after page load so we can replace them
|
||||
// with placeholders.
|
||||
newEmbedObserver = new MutationObserver(mutations => {
|
||||
for (let { addedNodes, target, type } of mutations) {
|
||||
const nodes = type === "attributes" ? [target] : addedNodes;
|
||||
for (const node of nodes) {
|
||||
if (
|
||||
SHIM_EMBED_CLASSES.some(className =>
|
||||
node.classList?.contains(className)
|
||||
)
|
||||
) {
|
||||
// If node is an embed, replace with placeholder
|
||||
createShimPlaceholders([node]);
|
||||
} else {
|
||||
// If node is not an embed, check if any children are
|
||||
// and replace if needed
|
||||
let maybeEmbedNodeList =
|
||||
node.querySelectorAll?.(SHIM_CLASS_SELECTORS);
|
||||
if (maybeEmbedNodeList) {
|
||||
createShimPlaceholders(maybeEmbedNodeList);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
newEmbedObserver.observe(document.documentElement, {
|
||||
childList: true,
|
||||
subtree: true,
|
||||
attributes: true,
|
||||
attributeFilter: ["class"],
|
||||
});
|
||||
|
||||
// Disconnect the mutation observer after a fixed (long) timeout to conserve resources.
|
||||
observerTimeout = setTimeout(
|
||||
() => newEmbedObserver.disconnect(),
|
||||
OBSERVER_TIMEOUT_MS
|
||||
);
|
||||
|
||||
createShimPlaceholders();
|
||||
}
|
||||
5
browser/extensions/webcompat/shims/x-logo.svg
Normal file
5
browser/extensions/webcompat/shims/x-logo.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<!-- copyright is dedicated to the Public Domain.
|
||||
https://commons.wikimedia.org/wiki/File:X_logo_2023_original.svg -->
|
||||
<svg width="300" height="300.251" version="1.1" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M178.57 127.15 290.27 0h-26.46l-97.03 110.38L89.34 0H0l117.13 166.93L0 300.25h26.46l102.4-116.59 81.8 116.59h89.34M36.01 19.54H76.66l187.13 262.13h-40.66"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 381 B |
Reference in New Issue
Block a user