summaryrefslogtreecommitdiffstats
path: root/browser/extensions/webcompat/shims/vidible.js
diff options
context:
space:
mode:
Diffstat (limited to 'browser/extensions/webcompat/shims/vidible.js')
-rw-r--r--browser/extensions/webcompat/shims/vidible.js424
1 files changed, 424 insertions, 0 deletions
diff --git a/browser/extensions/webcompat/shims/vidible.js b/browser/extensions/webcompat/shims/vidible.js
new file mode 100644
index 0000000000..1d45bc0f7e
--- /dev/null
+++ b/browser/extensions/webcompat/shims/vidible.js
@@ -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);
+ }
+}