/
githubmirror
/
amphtml
Обзор
Документация
Войти
/
githubmirror
/
amphtml
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
3p/environment.js
266 строк
6 KB
Shihua Zheng
Quick fix to the interval/timeout namespace conflict (#40238)
04 фев 2025, 02:53
Не верифицирован
04 фев 2025, 02:53
3e8d062
Код
Авторство
О чём код?
/* eslint-disable import/no-deprecated */ import {listenParent} from './messaging'; /** * Info about the current document/iframe. * @type {boolean} */ let inViewport = true; /** * @param {boolean} inV */ export function setInViewportForTesting(inV) { inViewport = inV; } // Active intervals. Must be global, because people clear intervals // with clearInterval from a different window. const intervals = {}; // TODO(40235): This is so we avoid the ID range of setTimeout. We // should also patch setTimeout and clearTimeout to avoid having to // use this. let intervalId = 10000000; /** * Add instrumentation to a window and all child iframes. * @param {!Window} win */ export function manageWin(win) { try { manageWin_(win); } catch (e) { // We use a try block, because the ad integrations often swallow errors. console./*OK*/ error(e.message, e.stack); } } /** * @param {!Window} win */ function manageWin_(win) { if (win.ampSeen) { return; } win.ampSeen = true; // Instrument window. instrumentEntryPoints(win); // Watch for new iframes. installObserver(win); // Existing iframes. maybeInstrumentsNodes(win, win.document.querySelectorAll('iframe')); blockSyncPopups(win); } /** * Add instrumentation code to doc.write. * @param {!Window} parent * @param {!Window} win */ function instrumentDocWrite(parent, win) { const doc = win.document; const {close} = doc; doc.close = function () { parent.ampManageWin = function (win) { manageWin(win); }; if (!parent.ampSeen) { // .call does not work in Safari with document.write. doc.write('<script>window.parent.ampManageWin(window)</script>'); } doc._close = close; return doc._close(); }; } /** * Add instrumentation code to iframe's srcdoc. * @param {!Window} parent * @param {!Element} iframe */ function instrumentSrcdoc(parent, iframe) { let srcdoc = iframe.getAttribute('srcdoc'); parent.ampManageWin = function (win) { manageWin(win); }; srcdoc += '<script>window.parent.ampManageWin(window)</script>'; iframe.setAttribute('srcdoc', srcdoc); } /** * Instrument added nodes if they are instrumentable iframes. * @param {!Window} win * @param {!Array<!Node>|NodeList<!Node>|NodeList<!Element>|null} addedNodes */ function maybeInstrumentsNodes(win, addedNodes) { for (let n = 0; n < addedNodes.length; n++) { const node = addedNodes[n]; try { if (node.tagName != 'IFRAME') { continue; } const src = node.getAttribute('src'); const srcdoc = node.getAttribute('srcdoc'); if (src == null || /^(about:|javascript:)/i.test(src.trim()) || srcdoc) { if (node.contentWindow) { instrumentIframeWindow(node, win, node.contentWindow); node.addEventListener('load', () => { try { instrumentIframeWindow(node, win, node.contentWindow); } catch (e) { console./*OK*/ error(e.message, e.stack); } }); } else if (srcdoc) { instrumentSrcdoc(parent, node); } } } catch (e) { console./*OK*/ error(e.message, e.stack); } } } /** * Installs a mutation observer in a window to look for iframes. * @param {!Element} node * @param {!Window} parent * @param {!Window} win */ function instrumentIframeWindow(node, parent, win) { if (win.ampSeen) { return; } const doc = win.document; instrumentDocWrite(parent, win); if (doc.body && doc.body.childNodes.length) { manageWin(win); } } /** * Installs a mutation observer in a window to look for iframes. * @param {!Window} win */ function installObserver(win) { if (!window.MutationObserver) { return; } const observer = new MutationObserver(function (mutations) { for (let i = 0; i < mutations.length; i++) { maybeInstrumentsNodes(win, mutations[i].addedNodes); } }); observer.observe(win.document.documentElement, { subtree: true, childList: true, }); } /** * Replace timers with variants that can be throttled. * @param {!Window} win */ function instrumentEntryPoints(win) { // Change setTimeout to respect a minimum timeout. const {setTimeout} = win; win.setTimeout = function (fn, time) { time = minTime(time); arguments[1] = time; return setTimeout.apply(this, arguments); }; // Implement setInterval in terms of setTimeout to make // it respect the same rules win.setInterval = function (fn) { const id = intervalId++; const args = Array.prototype.slice.call(arguments); /** * @return {*} * @suppress {uselessCode} */ function wrapper() { next(); if (typeof fn == 'string') { // Handle rare and dangerous string arg case. return (0, win.eval) /*NOT OK but whatcha gonna do.*/ .call(win, fn); // lgtm [js/useless-expression] } else { return fn.apply(this, arguments); } } args[0] = wrapper; /** * */ function next() { intervals[id] = win.setTimeout.apply(win, args); } next(); return id; }; const {clearInterval} = win; win.clearInterval = function (id) { clearInterval(id); win.clearTimeout(intervals[id]); delete intervals[id]; }; } /** * Blackhole the legacy popups since they should never be used for anything. * @param {!Window} win */ function blockSyncPopups(win) { let count = 0; /** * Checks for security error. */ function maybeThrow() { // Prevent deep recursion. if (count++ > 2) { throw new Error('security error'); } } try { win.alert = maybeThrow; win.prompt = function () { maybeThrow(); return ''; }; win.confirm = function () { maybeThrow(); return false; }; } catch (e) { console./*OK*/ error(e.message, e.stack); } } /** * Calculates the minimum time that a timeout should have right now. * @param {number|undefined} time * @return {number|undefined} */ function minTime(time) { if (!inViewport) { time += 1000; } // Eventually this should throttle like this: // - for timeouts in the order of a frame use requestAnimationFrame // instead. // - only allow about 2-4 short timeouts (< 16ms) in a 16ms time frame. // Throttle further timeouts to requestAnimationFrame. return time; } /** * Installs embed state listener. */ export function installEmbedStateListener() { listenParent(window, 'embed-state', function (data) { inViewport = data['inViewport']; }); }