/** * * Source below is based on Mozilla source code: * https://searchfox.org/mozilla-central/rev/d317e93d9a59c9e4c06ada85fbff9f6a1ceaaad1/browser/extensions/webcompat/shims/google-ima.js * * Modifications to the original code below this comment: * - Avoid JS syntax not supported by older browser versions * - Add missing shim event * - Modified to avoid jshint warnings as per uBO's config * - Added `OmidVerificationVendor` to `ima` * - Have `AdError.getInnerError()` return `null` * - Have `AdDisplayContainer` constructor add DIV element to container * - Added missing event dispatcher functionality * - Corrected return type of `Ad.getUniversalAdIds()` * - Corrected typo in `UniversalAdIdInfo.getAdIdValue()` method name * - Corrected dispatch of LOAD event when preloading is enabled * - Corrected dispatch of CONTENT_PAUSE/RESUME_REQUESTED events * * Related issue: * - https://github.com/uBlockOrigin/uBlock-issues/issues/2158 * **/ 'use strict'; /* 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/. */ /** * Bug 1713690 - Shim Google Interactive Media Ads ima3.js * * Many sites use ima3.js for ad bidding and placement, often in conjunction * with Google Publisher Tags, Prebid.js and/or other scripts. This shim * provides a stubbed-out version of the API which helps work around related * site breakage, such as black bxoes where videos ought to be placed. */ if (!window.google || !window.google.ima || !window.google.ima.VERSION) { const VERSION = "3.517.2"; const CheckCanAutoplay = (function() { // Sourced from: https://searchfox.org/mozilla-central/source/dom/media/gtest/negative_duration.mp4 const TEST_VIDEO = new Blob( [ new Uint32Array([ 469762048, 1887007846, 1752392036, 0, 913273705, 1717987696, 828601953, -1878917120, 1987014509, 1811939328, 1684567661, 0, 0, 0, -402456576, 0, 256, 1, 0, 0, 256, 0, 0, 0, 256, 0, 0, 0, 64, 0, 0, 0, 0, 0, 0, 33554432, -201261056, 1801548404, 1744830464, 1684564852, 251658241, 0, 0, 0, 0, 16777216, 0, -1, -1, 0, 0, 0, 0, 256, 0, 0, 0, 256, 0, 0, 0, 64, 5, 53250, -2080309248, 1634296941, 738197504, 1684563053, 1, 0, 0, 0, 0, -2137614336, -1, -1, 50261, 754974720, 1919706216, 0, 0, 1701079414, 0, 0, 0, 1701079382, 1851869295, 1919249508, 16777216, 1852402979, 102, 1752004116, 100, 1, 0, 0, 1852400676, 102, 1701995548, 102, 0, 1, 1819440396, 32, 1, 1651799011, 108, 1937011607, 100, 0, 1, 1668702599, 49, 0, 1, 0, 0, 0, 33555712, 4718800, 4718592, 0, 65536, 0, 0, 0, 0, 0, 0, 0, 0, 16776984, 1630601216, 21193590, -14745500, 1729626337, -1407254428, 89161945, 1049019, 9453056, -251611125, 27269507, -379058688, -1329024392, 268435456, 1937011827, 0, 0, 268435456, 1668510835, 0, 0, 335544320, 2054386803, 0, 0, 0, 268435456, 1868788851, 0, 0, 671088640, 2019915373, 536870912, 2019914356, 0, 16777216, 16777216, 0, 0, 0, ]), ], { type: "video/mp4" } ); let testVideo; return function() { if (!testVideo) { testVideo = document.createElement("video"); testVideo.style = "position:absolute; width:0; height:0; left:0; right:0; z-index:-1; border:0"; testVideo.setAttribute("muted", "muted"); testVideo.setAttribute("playsinline", "playsinline"); testVideo.src = URL.createObjectURL(TEST_VIDEO); document.body.appendChild(testVideo); } return testVideo.play(); }; })(); const ima = {}; class AdDisplayContainer { constructor(containerElement) { const divElement = document.createElement("div"); divElement.style.setProperty("display", "none", "important"); divElement.style.setProperty("visibility", "collapse", "important"); containerElement.appendChild(divElement); } destroy() {} initialize() {} } class ImaSdkSettings { constructor() { this.c = true; this.f = {}; this.i = false; this.l = ""; this.p = ""; this.r = 0; this.t = ""; this.v = ""; } getCompanionBackfill() {} getDisableCustomPlaybackForIOS10Plus() { return this.i; } getFeatureFlags() { return this.f; } getLocale() { return this.l; } getNumRedirects() { return this.r; } getPlayerType() { return this.t; } getPlayerVersion() { return this.v; } getPpid() { return this.p; } isCookiesEnabled() { return this.c; } setAutoPlayAdBreaks() {} setCompanionBackfill() {} setCookiesEnabled(c) { this.c = !!c; } setDisableCustomPlaybackForIOS10Plus(i) { this.i = !!i; } setFeatureFlags(f) { this.f = f; } setLocale(l) { this.l = l; } setNumRedirects(r) { this.r = r; } setPlayerType(t) { this.t = t; } setPlayerVersion(v) { this.v = v; } setPpid(p) { this.p = p; } setSessionId(/*s*/) {} setVpaidAllowed(/*a*/) {} setVpaidMode(/*m*/) {} // https://github.com/uBlockOrigin/uBlock-issues/issues/2265#issuecomment-1637094149 getDisableFlashAds() { } setDisableFlashAds() { } } ImaSdkSettings.CompanionBackfillMode = { ALWAYS: "always", ON_MASTER_AD: "on_master_ad", }; ImaSdkSettings.VpaidMode = { DISABLED: 0, ENABLED: 1, INSECURE: 2, }; class EventHandler { constructor() { this.listeners = new Map(); } _dispatch(e) { let listeners = this.listeners.get(e.type); listeners = listeners ? Array.from(listeners.values()) : []; for (const listener of listeners) { try { listener(e); } catch (r) { console.error(r); } } } addEventListener(types, c, options, context) { if (!Array.isArray(types)) { types = [types]; } for (const t of types) { if (!this.listeners.has(t)) { this.listeners.set(t, new Map()); } this.listeners.get(t).set(c, c.bind(context || this)); } } removeEventListener(types, c) { if (!Array.isArray(types)) { types = [types]; } for (const t of types) { const typeSet = this.listeners.get(t); if (typeSet) { typeSet.delete(c); } } } } class AdsLoader extends EventHandler { constructor() { super(); this.settings = new ImaSdkSettings(); } contentComplete() {} destroy() {} getSettings() { return this.settings; } getVersion() { return VERSION; } requestAds(/*r, c*/) { // If autoplay is disabled and the page is trying to autoplay a tracking // ad, then IMA fails with an error, and the page is expected to request // ads again later when the user clicks to play. CheckCanAutoplay().then( () => { const { ADS_MANAGER_LOADED } = AdsManagerLoadedEvent.Type; this._dispatch(new ima.AdsManagerLoadedEvent(ADS_MANAGER_LOADED)); }, () => { const e = new ima.AdError( "adPlayError", 1205, 1205, "The browser prevented playback initiated without user interaction." ); this._dispatch(new ima.AdErrorEvent(e)); } ); } } class AdsManager extends EventHandler { constructor() { super(); this.volume = 1; this._enablePreloading = false; } collapse() {} configureAdsManager() {} destroy() {} discardAdBreak() {} expand() {} focus() {} getAdSkippableState() { return false; } getCuePoints() { return [0]; } getCurrentAd() { return currentAd; } getCurrentAdCuePoints() { return []; } getRemainingTime() { return 0; } getVolume() { return this.volume; } init(/*w, h, m, e*/) { if (this._enablePreloading) { this._dispatch(new ima.AdEvent(AdEvent.Type.LOADED)); } } isCustomClickTrackingUsed() { return false; } isCustomPlaybackUsed() { return false; } pause() {} requestNextAdBreak() {} resize(/*w, h, m*/) {} resume() {} setVolume(v) { this.volume = v; } skip() {} start() { requestAnimationFrame(() => { for (const type of [ AdEvent.Type.LOADED, AdEvent.Type.STARTED, AdEvent.Type.CONTENT_PAUSE_REQUESTED, AdEvent.Type.AD_BUFFERING, AdEvent.Type.FIRST_QUARTILE, AdEvent.Type.MIDPOINT, AdEvent.Type.THIRD_QUARTILE, AdEvent.Type.COMPLETE, AdEvent.Type.ALL_ADS_COMPLETED, AdEvent.Type.CONTENT_RESUME_REQUESTED, ]) { try { this._dispatch(new ima.AdEvent(type)); } catch (e) { console.error(e); } } }); } stop() {} updateAdsRenderingSettings(/*s*/) {} } class AdsRenderingSettings {} class AdsRequest { setAdWillAutoPlay() {} setAdWillPlayMuted() {} setContinuousPlayback() {} } class AdPodInfo { getAdPosition() { return 1; } getIsBumper() { return false; } getMaxDuration() { return -1; } getPodIndex() { return 1; } getTimeOffset() { return 0; } getTotalAds() { return 1; } } class Ad { constructor() { this._pi = new AdPodInfo(); } getAdId() { return ""; } getAdPodInfo() { return this._pi; } getAdSystem() { return ""; } getAdvertiserName() { return ""; } getApiFramework() { return null; } getCompanionAds() { return []; } getContentType() { return ""; } getCreativeAdId() { return ""; } getCreativeId() { return ""; } getDealId() { return ""; } getDescription() { return ""; } getDuration() { return 8.5; } getHeight() { return 0; } getMediaUrl() { return null; } getMinSuggestedDuration() { return -2; } getSkipTimeOffset() { return -1; } getSurveyUrl() { return null; } getTitle() { return ""; } getTraffickingParameters() { return {}; } getTraffickingParametersString() { return ""; } getUiElements() { return [""]; } getUniversalAdIdRegistry() { return "unknown"; } getUniversalAdIds() { return [new UniversalAdIdInfo()]; } getUniversalAdIdValue() { return "unknown"; } getVastMediaBitrate() { return 0; } getVastMediaHeight() { return 0; } getVastMediaWidth() { return 0; } getWidth() { return 0; } getWrapperAdIds() { return [""]; } getWrapperAdSystems() { return [""]; } getWrapperCreativeIds() { return [""]; } isLinear() { return true; } isSkippable() { return true; } } class CompanionAd { getAdSlotId() { return ""; } getContent() { return ""; } getContentType() { return ""; } getHeight() { return 1; } getWidth() { return 1; } } class AdError { constructor(type, code, vast, message) { this.errorCode = code; this.message = message; this.type = type; this.vastErrorCode = vast; } getErrorCode() { return this.errorCode; } getInnerError() { return null; } getMessage() { return this.message; } getType() { return this.type; } getVastErrorCode() { return this.vastErrorCode; } toString() { return `AdError ${this.errorCode}: ${this.message}`; } } AdError.ErrorCode = {}; AdError.Type = {}; const isEngadget = () => { try { for (const ctx of Object.values(window.vidible._getContexts())) { const player = ctx.getPlayer(); if (!player) { continue;} const div = player.div; if (!div) { continue; } if (div.innerHTML.includes("www.engadget.com")) { return true; } } } catch (_) {} return false; }; const currentAd = isEngadget() ? undefined : new Ad(); class AdEvent { constructor(type) { this.type = type; } getAd() { return currentAd; } getAdData() { return {}; } } AdEvent.Type = { AD_BREAK_READY: "adBreakReady", AD_BUFFERING: "adBuffering", AD_CAN_PLAY: "adCanPlay", AD_METADATA: "adMetadata", AD_PROGRESS: "adProgress", ALL_ADS_COMPLETED: "allAdsCompleted", CLICK: "click", COMPLETE: "complete", CONTENT_PAUSE_REQUESTED: "contentPauseRequested", CONTENT_RESUME_REQUESTED: "contentResumeRequested", DURATION_CHANGE: "durationChange", EXPANDED_CHANGED: "expandedChanged", FIRST_QUARTILE: "firstQuartile", IMPRESSION: "impression", INTERACTION: "interaction", LINEAR_CHANGE: "linearChange", LINEAR_CHANGED: "linearChanged", LOADED: "loaded", LOG: "log", MIDPOINT: "midpoint", PAUSED: "pause", RESUMED: "resume", SKIPPABLE_STATE_CHANGED: "skippableStateChanged", SKIPPED: "skip", STARTED: "start", THIRD_QUARTILE: "thirdQuartile", USER_CLOSE: "userClose", VIDEO_CLICKED: "videoClicked", VIDEO_ICON_CLICKED: "videoIconClicked", VIEWABLE_IMPRESSION: "viewable_impression", VOLUME_CHANGED: "volumeChange", VOLUME_MUTED: "mute", }; class AdErrorEvent { constructor(error) { this.type = "adError"; this.error = error; } getError() { return this.error; } getUserRequestContext() { return {}; } } AdErrorEvent.Type = { AD_ERROR: "adError", }; const manager = new AdsManager(); class AdsManagerLoadedEvent { constructor(type) { this.type = type; } getAdsManager(c, settings) { if (settings && settings.enablePreloading) { manager._enablePreloading = true; } return manager; } getUserRequestContext() { return {}; } } AdsManagerLoadedEvent.Type = { ADS_MANAGER_LOADED: "adsManagerLoaded", }; class CustomContentLoadedEvent {} CustomContentLoadedEvent.Type = { CUSTOM_CONTENT_LOADED: "deprecated-event", }; class CompanionAdSelectionSettings {} CompanionAdSelectionSettings.CreativeType = { ALL: "All", FLASH: "Flash", IMAGE: "Image", }; CompanionAdSelectionSettings.ResourceType = { ALL: "All", HTML: "Html", IFRAME: "IFrame", STATIC: "Static", }; CompanionAdSelectionSettings.SizeCriteria = { IGNORE: "IgnoreSize", SELECT_EXACT_MATCH: "SelectExactMatch", SELECT_NEAR_MATCH: "SelectNearMatch", }; class AdCuePoints { getCuePoints() { return []; } } class AdProgressData {} class UniversalAdIdInfo { getAdIdRegistry() { return ""; } getAdIdValue() { return ""; } } Object.assign(ima, { AdCuePoints, AdDisplayContainer, AdError, AdErrorEvent, AdEvent, AdPodInfo, AdProgressData, AdsLoader, AdsManager: manager, AdsManagerLoadedEvent, AdsRenderingSettings, AdsRequest, CompanionAd, CompanionAdSelectionSettings, CustomContentLoadedEvent, gptProxyInstance: {}, ImaSdkSettings, OmidAccessMode: { DOMAIN: "domain", FULL: "full", LIMITED: "limited", }, OmidVerificationVendor: { 1: "OTHER", 2: "GOOGLE", GOOGLE: 2, OTHER: 1 }, settings: new ImaSdkSettings(), UiElements: { AD_ATTRIBUTION: "adAttribution", COUNTDOWN: "countdown", }, UniversalAdIdInfo, VERSION, ViewMode: { FULLSCREEN: "fullscreen", NORMAL: "normal", }, }); if (!window.google) { window.google = {}; } window.google.ima = ima; }