diff --git a/src/web_accessible_resources/google-ima.js b/src/web_accessible_resources/google-ima.js new file mode 100644 index 000000000..4005a8bf0 --- /dev/null +++ b/src/web_accessible_resources/google-ima.js @@ -0,0 +1,773 @@ +/* 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?.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 = undefined; + + 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(); + }; + })(); + + let ima = {}; + + class AdDisplayContainer { + destroy() {} + initialize() {} + } + + class ImaSdkSettings { + #c = true; + #f = {}; + #i = false; + #l = ""; + #p = ""; + #r = 0; + #t = ""; + #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) {} + } + ImaSdkSettings.CompanionBackfillMode = { + ALWAYS: "always", + ON_MASTER_AD: "on_master_ad", + }; + ImaSdkSettings.VpaidMode = { + DISABLED: 0, + ENABLED: 1, + INSECURE: 2, + }; + + class EventHandler { + #listeners = new Map(); + + _dispatch(e) { + const listeners = this.#listeners.get(e.type) || []; + for (const listener of Array.from(listeners)) { + try { + listener(e); + } catch (r) { + console.error(r); + } + } + } + + addEventListener(t, c) { + if (!this.#listeners.has(t)) { + this.#listeners.set(t, new Set()); + } + this.#listeners.get(t).add(c); + } + + removeEventListener(t, c) { + this.#listeners.get(t)?.delete(c); + } + } + + class AdsLoader extends EventHandler { + #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 { + #volume = 1; + 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) {} + 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.AD_BUFFERING, + AdEvent.Type.FIRST_QUARTILE, + AdEvent.Type.MIDPOINT, + AdEvent.Type.THIRD_QUARTILE, + AdEvent.Type.COMPLETE, + AdEvent.Type.ALL_ADS_COMPLETED, + ]) { + 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 { + _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 [""]; + } + 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 { + #errorCode = -1; + #message = ""; + #type = ""; + #vastErrorCode = -1; + constructor(type, code, vast, message) { + this.#errorCode = code; + this.#message = message; + this.#type = type; + this.#vastErrorCode = vast; + } + getErrorCode() { + return this.#errorCode; + } + getInnerError() {} + 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())) { + if (ctx.getPlayer()?.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 { + type = "adError"; + #error = ""; + constructor(error) { + 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() { + 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 ""; + } + getAdIsValue() { + 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", + }, + 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; +}