/
githubmirror
/
amphtml
Обзор
Документация
Войти
/
githubmirror
/
amphtml
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/polyfills/stubs/intersection-observer-stub.js
269 строк
7 KB
Kristofer Baxter
♻️ Remove Notice from JS and Markdown (#35717)
19 авг 2021, 21:21
Не верифицирован
19 авг 2021, 21:21
f564b7e
Код
Авторство
О чём код?
/** * @fileoverview * See https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserver. * * This is the stub to support IntersectionObserver. It's installed from * polyfills/intersection-observer.js and upgraded from the * amp-intersection-observer-polyfill extension. */ /** @typedef {function(!typeof IntersectionObserver)} */ let InObUpgraderDef; const UPGRADERS = '_upgraders'; const NATIVE = '_native'; const STUB = '_stub'; /** * @param {!Window} win * @return {boolean} * @visibleForTesting */ export function shouldLoadPolyfill(win) { return ( !win.IntersectionObserver || !win.IntersectionObserverEntry || !!win.IntersectionObserver[STUB] || !supportsDocumentRoot(win) || isWebkit(win) ); } /** * All current WebKit (as of Safari 14.x) {root:document} IntersectionObservers * will report incorrect rootBounds, intersectionRect, and intersectionRatios * and therefore we force the polyfill in this case. * See: https://bugs.webkit.org/show_bug.cgi?id=219495. * * @param {!Window} win * @return {boolean} */ function isWebkit(win) { // navigator.vendor is always "Apple Computer, Inc." for all iOS browsers and // Mac OS Safari. return /apple/i.test(win.navigator.vendor); } /** * @param {!typeof IntersectionObserver} Native * @param {!typeof IntersectionObserver} Polyfill * @return {!typeof IntersectionObserver} */ function getIntersectionObserverDispatcher(Native, Polyfill) { /** * @param {!IntersectionObserverCallback} ioCallback * @param {IntersectionObserverInit=} opts * @return {!IntersectionObserver} */ function Ctor(ioCallback, opts) { if (opts?.root?.nodeType === /* Node.DOCUMENT_NODE */ 9) { return new Polyfill(ioCallback, opts); } else { return new Native(ioCallback, opts); } } return Ctor; } /** * Installs the InOb stubs. This should only be called in two cases: * 1. No native InOb exists. * 2. Native InOb is present, but lacks document root support. * * @param {!Window} win */ export function installStub(win) { if (!win.IntersectionObserver) { win.IntersectionObserver = IntersectionObserverStub; win.IntersectionObserver[STUB] = IntersectionObserverStub; return; } const Native = win.IntersectionObserver; win.IntersectionObserver = getIntersectionObserverDispatcher( win.IntersectionObserver, IntersectionObserverStub ); win.IntersectionObserver[STUB] = IntersectionObserverStub; win.IntersectionObserver[NATIVE] = Native; } /** * Returns true if IntersectionObserver supports a document root. * @param {!Window} win * @return {boolean} */ export function supportsDocumentRoot(win) { try { new win.IntersectionObserver(() => {}, { // TODO(rcebulko): Update when CC updates their externs // See https://github.com/google/closure-compiler/pull/3804 root: /** @type {?} */ (win.document), }); return true; } catch { return false; } } /** * @param {!Window} win * @param {function()} installer */ export function upgradePolyfill(win, installer) { // Can't use the IntersectionObserverStub here directly since it's a separate // instance deployed in v0.js vs the polyfill extension. const Stub = win.IntersectionObserver[STUB]; if (Stub) { const Native = win.IntersectionObserver[NATIVE]; delete win.IntersectionObserver; delete win.IntersectionObserverEntry; installer(); const Polyfill = win.IntersectionObserver; if (Native) { win.IntersectionObserver = getIntersectionObserverDispatcher( Native, Polyfill ); } /** @type {!Array<!InObUpgraderDef>} */ const upgraders = Stub[UPGRADERS].slice(0); const microtask = Promise.resolve(); const upgrade = (upgrader) => { microtask.then(() => upgrader(Polyfill)); }; upgraders.forEach(upgrade); Stub[UPGRADERS] = {'push': upgrade}; } else { // Even if this is not the stub, we still may need to polyfill // `isIntersecting`. See `shouldLoadPolyfill` for more info. installer(); } } /** * The stub for `IntersectionObserver`. Implements the same interface, but * keeps the tracked elements in memory until the actual polyfill arives. * This stub is necessary because the polyfill itself is significantly bigger. * * It doesn't technically extend IntersectionObserver, but this allows the stub * to be seen as equivalent when typechecking calls expecting an * IntersectionObserver. * @extends IntersectionObserver */ export class IntersectionObserverStub { /** * @param {!IntersectionObserverCallback} callback * @param {!IntersectionObserverInit=} options */ constructor(callback, options) { /** @private @const */ this.callback_ = callback; /** @private @const {!IntersectionObserverInit} */ this.options_ = { root: null, rootMargin: '0px 0px 0px 0px', ...options, }; /** @private {!Array<!Element>} */ this.elements_ = []; /** @private {?IntersectionObserver} */ this.inst_ = null; // Wait for the upgrade. IntersectionObserverStub[UPGRADERS].push(this.upgrade_.bind(this)); } /** @return {?Element} */ get root() { if (this.inst_) { return this.inst_.root; } // eslint-disable-next-line local/no-forbidden-terms return /** @type {!Element} */ (this.options_.root) || null; } /** @return {string} */ get rootMargin() { if (this.inst_) { return this.inst_.rootMargin; } // The CC-provided IntersectionObserverInit type allows for rootMargin to be // undefined, but we provide a default, so it's guaranteed to be a string // here. return /** @type {string} */ (this.options_.rootMargin); } /** @return {!Array<number>} */ get thresholds() { if (this.inst_) { return this.inst_.thresholds; } return [].concat(this.options_.threshold || 0); } /** @return {undefined} */ disconnect() { if (this.inst_) { this.inst_.disconnect(); } else { this.elements_.length = 0; } } /** @return {!Array<!IntersectionObserverEntry>} */ takeRecords() { if (this.inst_) { return this.inst_.takeRecords(); } return []; } /** @param {!Element} target */ observe(target) { if (this.inst_) { this.inst_.observe(target); } else { if (this.elements_.indexOf(target) == -1) { this.elements_.push(target); } } } /** @param {!Element} target */ unobserve(target) { if (this.inst_) { this.inst_.unobserve(target); } else { const index = this.elements_.indexOf(target); if (index != -1) { this.elements_.splice(index, 1); } } } /** * @param {!typeof IntersectionObserver} Ctor * @private */ upgrade_(Ctor) { const inst = new Ctor(this.callback_, this.options_); this.inst_ = inst; for (const e of this.elements_) { inst.observe(e); } this.elements_.length = 0; } } /** @type {!Array<!InObUpgraderDef>} */ IntersectionObserverStub[UPGRADERS] = []; /** @visibleForTesting */ export function resetStubsForTesting() { IntersectionObserverStub[UPGRADERS] = []; }