зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1713710 - Add a SmartBlock shim for Vidible-served video playback on user opt-in; r=denschub,webcompat-reviewers
Differential Revision: https://phabricator.services.mozilla.com/D122756
This commit is contained in:
Родитель
d4f3c0c592
Коммит
e427ce2e32
|
@ -417,6 +417,32 @@ const AVAILABLE_SHIMS = [
|
|||
matches: ["*://media.richrelevance.com/rrserver/js/1.2/p13n.js"],
|
||||
onlyIfBlockedByETP: true,
|
||||
},
|
||||
{
|
||||
id: "Vidible",
|
||||
branch: ["nightly"],
|
||||
platform: "all",
|
||||
name: "Vidible",
|
||||
bug: "1713710",
|
||||
file: "vidible.js",
|
||||
logos: ["play.svg"],
|
||||
matches: [
|
||||
"*://*.vidible.tv/*/vidible-min.js*",
|
||||
"*://vdb-cdn-files.s3.amazonaws.com/*/vidible-min.js*",
|
||||
],
|
||||
needsShimHelpers: ["optIn"],
|
||||
onlyIfBlockedByETP: true,
|
||||
unblocksOnOptIn: [
|
||||
"*://delivery.vidible.tv/jsonp/pid=*/vid=*/*.js*",
|
||||
"*://delivery.vidible.tv/placement/*",
|
||||
"*://img.vidible.tv/prod/*",
|
||||
"*://cdn-ssl.vidible.tv/prod/player/js/*.js",
|
||||
"*://hlsrv.vidible.tv/prod/*.m3u8*",
|
||||
"*://videos.vidible.tv/prod/*.key*",
|
||||
"*://videos.vidible.tv/prod/*.mp4*",
|
||||
"*://videos.vidible.tv/prod/*.webm*",
|
||||
"*://videos.vidible.tv/prod/*.ts*",
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
module.exports = AVAILABLE_SHIMS;
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"manifest_version": 2,
|
||||
"name": "Web Compatibility Interventions",
|
||||
"description": "Urgent post-release fixes for web compatibility.",
|
||||
"version": "25.4.0",
|
||||
"version": "25.5.0",
|
||||
|
||||
"applications": {
|
||||
"gecko": {
|
||||
|
@ -124,6 +124,7 @@
|
|||
"shims/tracking-pixel.png",
|
||||
"shims/vast2.xml",
|
||||
"shims/vast3.xml",
|
||||
"shims/vidible.js",
|
||||
"shims/vmad.xml"
|
||||
]
|
||||
}
|
||||
|
|
|
@ -113,6 +113,7 @@ FINAL_TARGET_FILES.features["webcompat@mozilla.org"]["shims"] += [
|
|||
"shims/tracking-pixel.png",
|
||||
"shims/vast2.xml",
|
||||
"shims/vast3.xml",
|
||||
"shims/vidible.js",
|
||||
"shims/vmad.xml",
|
||||
]
|
||||
|
||||
|
|
|
@ -0,0 +1,424 @@
|
|||
/* 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/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Bug 1713710 - Shim Vidible video player
|
||||
*
|
||||
* Sites relying on Vidible's video player may experience broken videos if that
|
||||
* script is blocked. This shim allows users to opt into viewing those videos
|
||||
* regardless of any tracking consequences, by providing placeholders for each.
|
||||
*/
|
||||
|
||||
if (!window.vidible?.version) {
|
||||
const PlayIconURL = "https://smartblock.firefox.etp/play.svg";
|
||||
|
||||
const originalScript = document.currentScript.src;
|
||||
|
||||
const getGUID = () => {
|
||||
const v = crypto.getRandomValues(new Uint8Array(20));
|
||||
return Array.from(v, c => c.toString(16)).join("");
|
||||
};
|
||||
|
||||
const sendMessageToAddon = (function() {
|
||||
const shimId = "Vidible";
|
||||
const pendingMessages = new Map();
|
||||
const channel = new MessageChannel();
|
||||
channel.port1.onerror = console.error;
|
||||
channel.port1.onmessage = event => {
|
||||
const { messageId, response } = event.data;
|
||||
const resolve = pendingMessages.get(messageId);
|
||||
if (resolve) {
|
||||
pendingMessages.delete(messageId);
|
||||
resolve(response);
|
||||
}
|
||||
};
|
||||
function reconnect() {
|
||||
const detail = {
|
||||
pendingMessages: [...pendingMessages.values()],
|
||||
port: channel.port2,
|
||||
shimId,
|
||||
};
|
||||
window.dispatchEvent(new CustomEvent("ShimConnects", { detail }));
|
||||
}
|
||||
window.addEventListener("ShimHelperReady", reconnect);
|
||||
reconnect();
|
||||
return function(message) {
|
||||
const messageId = getGUID();
|
||||
return new Promise(resolve => {
|
||||
const payload = { message, messageId, shimId };
|
||||
pendingMessages.set(messageId, resolve);
|
||||
channel.port1.postMessage(payload);
|
||||
});
|
||||
};
|
||||
})();
|
||||
|
||||
const Shimmer = (function() {
|
||||
// If a page might store references to an object before we replace it,
|
||||
// ensure that it only receives proxies to that object created by
|
||||
// `Shimmer.proxy(obj)`. Later when the unshimmed object is created,
|
||||
// call `Shimmer.unshim(proxy, unshimmed)`. This way the references
|
||||
// will automatically "become" the unshimmed object when appropriate.
|
||||
|
||||
const shimmedObjects = new WeakMap();
|
||||
const unshimmedObjects = new Map();
|
||||
|
||||
function proxy(shim) {
|
||||
if (shimmedObjects.has(shim)) {
|
||||
return shimmedObjects.get(shim);
|
||||
}
|
||||
|
||||
const prox = new Proxy(shim, {
|
||||
get: (target, k) => {
|
||||
if (unshimmedObjects.has(prox)) {
|
||||
return unshimmedObjects.get(prox)[k];
|
||||
}
|
||||
return target[k];
|
||||
},
|
||||
apply: (target, thisArg, args) => {
|
||||
if (unshimmedObjects.has(prox)) {
|
||||
return unshimmedObjects.get(prox)(...args);
|
||||
}
|
||||
return target.apply(thisArg, args);
|
||||
},
|
||||
construct: (target, args) => {
|
||||
if (unshimmedObjects.has(prox)) {
|
||||
return new unshimmedObjects.get(prox)(...args);
|
||||
}
|
||||
return new target(...args);
|
||||
},
|
||||
});
|
||||
shimmedObjects.set(shim, prox);
|
||||
shimmedObjects.set(prox, prox);
|
||||
|
||||
for (const key in shim) {
|
||||
const value = shim[key];
|
||||
if (typeof value === "function") {
|
||||
shim[key] = function() {
|
||||
const unshimmed = unshimmedObjects.get(prox);
|
||||
if (unshimmed) {
|
||||
return unshimmed[key].apply(unshimmed, arguments);
|
||||
}
|
||||
return value.apply(this, arguments);
|
||||
};
|
||||
} else if (typeof value !== "object" || value === null) {
|
||||
shim[key] = value;
|
||||
} else {
|
||||
shim[key] = Shimmer.proxy(value);
|
||||
}
|
||||
}
|
||||
|
||||
return prox;
|
||||
}
|
||||
|
||||
function unshim(shim, unshimmed) {
|
||||
unshimmedObjects.set(shim, unshimmed);
|
||||
|
||||
for (const prop in shim) {
|
||||
if (prop in unshimmed) {
|
||||
const un = unshimmed[prop];
|
||||
if (typeof un === "object" && un !== null) {
|
||||
unshim(shim[prop], un);
|
||||
}
|
||||
} else {
|
||||
unshimmedObjects.set(shim[prop], undefined);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { proxy, unshim };
|
||||
})();
|
||||
|
||||
const extras = [];
|
||||
const playersByNode = new WeakMap();
|
||||
const playerData = new Map();
|
||||
|
||||
const getJSONPVideoPlacements = () => {
|
||||
return document.querySelectorAll(
|
||||
`script[src*="delivery.vidible.tv/jsonp"]`
|
||||
);
|
||||
};
|
||||
|
||||
const allowVidible = () => {
|
||||
if (allowVidible.promise) {
|
||||
return allowVidible.promise;
|
||||
}
|
||||
|
||||
const shim = window.vidible;
|
||||
window.vidible = undefined;
|
||||
|
||||
allowVidible.promise = sendMessageToAddon("optIn")
|
||||
.then(() => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const script = document.createElement("script");
|
||||
script.src = originalScript;
|
||||
script.addEventListener("load", () => {
|
||||
Shimmer.unshim(shim, window.vidible);
|
||||
|
||||
for (const args of extras) {
|
||||
window.visible.registerExtra(...args);
|
||||
}
|
||||
|
||||
for (const jsonp of getJSONPVideoPlacements()) {
|
||||
const { src } = jsonp;
|
||||
const jscript = document.createElement("script");
|
||||
jscript.onload = resolve;
|
||||
jscript.src = src;
|
||||
jsonp.replaceWith(jscript);
|
||||
}
|
||||
|
||||
for (const [playerShim, data] of playerData.entries()) {
|
||||
const { loadCalled, on, parent, placeholder, setup } = data;
|
||||
|
||||
placeholder?.remove();
|
||||
|
||||
const player = window.vidible.player(parent);
|
||||
Shimmer.unshim(playerShim, player);
|
||||
|
||||
for (const [type, fns] of on.entries()) {
|
||||
for (const fn of fns) {
|
||||
try {
|
||||
player.on(type, fn);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (setup) {
|
||||
player.setup(setup);
|
||||
}
|
||||
|
||||
if (loadCalled) {
|
||||
player.load();
|
||||
}
|
||||
}
|
||||
|
||||
resolve();
|
||||
});
|
||||
|
||||
script.addEventListener("error", () => {
|
||||
script.remove();
|
||||
reject();
|
||||
});
|
||||
|
||||
document.head.appendChild(script);
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
window.vidible = shim;
|
||||
delete allowVidible.promise;
|
||||
});
|
||||
|
||||
return allowVidible.promise;
|
||||
};
|
||||
|
||||
const createVideoPlaceholder = (service, callback) => {
|
||||
const placeholder = document.createElement("div");
|
||||
placeholder.style = `
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-width: 160px;
|
||||
min-height: 100px;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
background: #000;
|
||||
color: #fff;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-image: url(${PlayIconURL});
|
||||
background-position: 50% 47.5%;
|
||||
background-repeat: no-repeat;
|
||||
background-size: 25% 25%;
|
||||
-moz-text-size-adjust: none;
|
||||
-moz-user-select: none;
|
||||
color: #fff;
|
||||
align-items: center;
|
||||
padding-top: 200px;
|
||||
font-size: 14pt;
|
||||
`;
|
||||
placeholder.textContent = `Click to allow blocked ${service} content`;
|
||||
placeholder.addEventListener("click", evt => {
|
||||
evt.isTrusted && callback();
|
||||
});
|
||||
return placeholder;
|
||||
};
|
||||
|
||||
const Player = function(parent) {
|
||||
const existing = playersByNode.get(parent);
|
||||
if (existing) {
|
||||
return existing;
|
||||
}
|
||||
|
||||
const player = Shimmer.proxy(this);
|
||||
playersByNode.set(parent, player);
|
||||
|
||||
const placeholder = createVideoPlaceholder("Vidible", allowVidible);
|
||||
parent.parentNode.insertBefore(placeholder, parent);
|
||||
|
||||
playerData.set(player, {
|
||||
on: new Map(),
|
||||
parent,
|
||||
placeholder,
|
||||
});
|
||||
return player;
|
||||
};
|
||||
|
||||
const changeData = function(fn) {
|
||||
const data = playerData.get(this);
|
||||
if (data) {
|
||||
fn(data);
|
||||
playerData.set(this, data);
|
||||
}
|
||||
};
|
||||
|
||||
Player.prototype = {
|
||||
addEventListener() {},
|
||||
destroy() {
|
||||
const { placeholder } = playerData.get(this);
|
||||
placeholder?.remove();
|
||||
playerData.delete(this);
|
||||
},
|
||||
dispatchEvent() {},
|
||||
getAdsPassedTime() {},
|
||||
getAllMacros() {},
|
||||
getCurrentTime() {},
|
||||
getDuration() {},
|
||||
getHeight() {},
|
||||
getPixelsLog() {},
|
||||
getPlayerContainer() {},
|
||||
getPlayerInfo() {},
|
||||
getPlayerStatus() {},
|
||||
getRequestsLog() {},
|
||||
getStripUrl() {},
|
||||
getVolume() {},
|
||||
getWidth() {},
|
||||
hidePlayReplayControls() {},
|
||||
isMuted() {},
|
||||
isPlaying() {},
|
||||
load() {
|
||||
changeData(data => (data.loadCalled = true));
|
||||
},
|
||||
mute() {},
|
||||
on(type, fn) {
|
||||
changeData(({ on }) => {
|
||||
if (!on.has(type)) {
|
||||
on.set(type, new Set());
|
||||
}
|
||||
on.get(type).add(fn);
|
||||
});
|
||||
},
|
||||
off(type, fn) {
|
||||
changeData(({ on }) => {
|
||||
on.get(type)?.delete(fn);
|
||||
});
|
||||
},
|
||||
overrideMacro() {},
|
||||
pause() {},
|
||||
play() {},
|
||||
playVideoByIndex() {},
|
||||
removeEventListener() {},
|
||||
seekTo() {},
|
||||
sendBirthDate() {},
|
||||
sendKey() {},
|
||||
setup(s) {
|
||||
changeData(data => (data.setup = s));
|
||||
return this;
|
||||
},
|
||||
setVideosToPlay() {},
|
||||
setVolume() {},
|
||||
showPlayReplayControls() {},
|
||||
toggleFullscreen() {},
|
||||
toggleMute() {},
|
||||
togglePlay() {},
|
||||
updateBid() {},
|
||||
version() {},
|
||||
volume() {},
|
||||
};
|
||||
|
||||
const vidible = {
|
||||
ADVERT_CLOSED: "advertClosed",
|
||||
AD_END: "adend",
|
||||
AD_META: "admeta",
|
||||
AD_PAUSED: "adpaused",
|
||||
AD_PLAY: "adplay",
|
||||
AD_START: "adstart",
|
||||
AD_TIMEUPDATE: "adtimeupdate",
|
||||
AD_WAITING: "adwaiting",
|
||||
AGE_GATE_DISPLAYED: "agegatedisplayed",
|
||||
BID_UPDATED: "BidUpdated",
|
||||
CAROUSEL_CLICK: "CarouselClick",
|
||||
CONTEXT_ENDED: "contextended",
|
||||
CONTEXT_STARTED: "contextstarted",
|
||||
ENTER_FULLSCREEN: "playerenterfullscreen",
|
||||
EXIT_FULLSCREEN: "playerexitfullscreen",
|
||||
FALLBACK: "fallback",
|
||||
FLOAT_END_ACTION: "floatended",
|
||||
FLOAT_START_ACTION: "floatstarted",
|
||||
HIDE_PLAY_REPLAY_BUTTON: "hideplayreplaybutton",
|
||||
LIGHTBOX_ACTIVATED: "lightboxactivated",
|
||||
LIGHTBOX_DEACTIVATED: "lightboxdeactivated",
|
||||
MUTE: "Mute",
|
||||
PLAYER_CONTROLS_STATE_CHANGE: "playercontrolsstatechaned",
|
||||
PLAYER_DOCKED: "playerDocked",
|
||||
PLAYER_ERROR: "playererror",
|
||||
PLAYER_FLOATING: "playerFloating",
|
||||
PLAYER_READY: "playerready",
|
||||
PLAYER_RESIZE: "playerresize",
|
||||
PLAYLIST_END: "playlistend",
|
||||
SEEK_END: "SeekEnd",
|
||||
SEEK_START: "SeekStart",
|
||||
SHARE_SCREEN_CLOSED: "sharescreenclosed",
|
||||
SHARE_SCREEN_OPENED: "sharescreenopened",
|
||||
SHOW_PLAY_REPLAY_BUTTON: "showplayreplaybutton",
|
||||
SUBTITLES_DISABLED: "subtitlesdisabled",
|
||||
SUBTITLES_ENABLED: "subtitlesenabled",
|
||||
SUBTITLES_READY: "subtitlesready",
|
||||
UNMUTE: "Unmute",
|
||||
VIDEO_DATA_LOADED: "videodataloaded",
|
||||
VIDEO_END: "videoend",
|
||||
VIDEO_META: "videometadata",
|
||||
VIDEO_MODULE_CREATED: "videomodulecreated",
|
||||
VIDEO_PAUSE: "videopause",
|
||||
VIDEO_PLAY: "videoplay",
|
||||
VIDEO_SEEKEND: "videoseekend",
|
||||
VIDEO_SELECTED: "videoselected",
|
||||
VIDEO_START: "videostart",
|
||||
VIDEO_TIMEUPDATE: "videotimeupdate",
|
||||
VIDEO_VOLUME_CHANGED: "videovolumechanged",
|
||||
VOLUME: "Volume",
|
||||
_getContexts: () => [],
|
||||
"content.CLICK": "content.click",
|
||||
"content.IMPRESSION": "content.impression",
|
||||
"content.QUARTILE": "content.quartile",
|
||||
"content.VIEW": "content.view",
|
||||
createPlayer: parent => new Player(parent),
|
||||
createPlayerAsync: parent => new Player(parent),
|
||||
createVPAIDPlayer: parent => new Player(parent),
|
||||
destroyAll() {},
|
||||
extension() {},
|
||||
getContext() {},
|
||||
player: parent => new Player(parent),
|
||||
playerInceptionTime() {
|
||||
return { undefined: 1620149827713 };
|
||||
},
|
||||
registerExtra(a, b, c) {
|
||||
extras.push([a, b, c]);
|
||||
},
|
||||
version: () => "21.1.313",
|
||||
};
|
||||
|
||||
window.vidible = Shimmer.proxy(vidible);
|
||||
|
||||
for (const jsonp of getJSONPVideoPlacements()) {
|
||||
const player = new Player(jsonp);
|
||||
const { placeholder } = playerData.get(player);
|
||||
jsonp.parentNode.insertBefore(placeholder, jsonp);
|
||||
}
|
||||
}
|
Загрузка…
Ссылка в новой задаче