/
githubmirror
/
amphtml
Обзор
Документация
Войти
/
githubmirror
/
amphtml
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/utils/event-helper.js
256 строк
7 KB
Ryan Cebulko
♻️ Move files into #core, #utils to enable tsc typechecking (#36277)
07 окт 2021, 23:26
Не верифицирован
07 окт 2021, 23:26
b6d1550
Код
Авторство
О чём код?
import {internalListenImplementation} from '#core/dom/event-helper-listen'; import {lastChildElement} from '#core/dom/query'; import * as mode from '#core/mode'; import {user} from '#utils/log'; /** @const {string} */ const LOAD_FAILURE_PREFIX = 'Failed to load:'; /** @const {string} */ export const MEDIA_LOAD_FAILURE_SRC_PROPERTY = '__AMP_MEDIA_LOAD_FAILURE_SRC'; /** * Returns a CustomEvent with a given type and detail; supports fallback for IE. * @param {!Window} win * @param {string} type * @param {!JsonObject|string|undefined|null} detail * @param {EventInit=} opt_eventInit * @return {!Event} */ export function createCustomEvent(win, type, detail, opt_eventInit) { const eventInit = /** @type {!CustomEventInit} */ ({detail}); Object.assign(eventInit, opt_eventInit); // win.CustomEvent is a function on Edge, Chrome, FF, Safari but // is an object on IE 11. if (mode.isEsm() || typeof win.CustomEvent == 'function') { return new win.CustomEvent(type, eventInit); } else { // Deprecated fallback for IE. const e = win.document.createEvent('CustomEvent'); e.initCustomEvent( type, !!eventInit.bubbles, !!eventInit.cancelable, detail ); return e; } } /** * Listens for the specified event on the element. * @param {!EventTarget} element * @param {string} eventType * @param {function(!Event)} listener * @param {Object=} opt_evtListenerOpts * @return {!UnlistenDef} */ export function listen(element, eventType, listener, opt_evtListenerOpts) { return internalListenImplementation( element, eventType, listener, opt_evtListenerOpts ); } /** * Returns the data property of an event with the correct type. * @param {!Event|{data: !JsonObject}} event * @return {?JsonObject|string|undefined} */ export function getData(event) { return /** @type {?JsonObject|string|undefined} */ (event.data); } /** * Returns the detail property of an event with the correct type. * @param {!Event|{detail: !JsonObject}} event * @return {?JsonObject|string|undefined} */ export function getDetail(event) { return /** @type {?JsonObject|string|undefined} */ (event.detail); } /** * Listens for the specified event on the element and removes the listener * as soon as event has been received. * @param {!EventTarget} element * @param {string} eventType * @param {function(!Event)} listener * @param {Object=} opt_evtListenerOpts * @return {!UnlistenDef} */ export function listenOnce(element, eventType, listener, opt_evtListenerOpts) { let localListener = listener; const unlisten = internalListenImplementation( element, eventType, (event) => { try { localListener(event); } finally { // Ensure listener is GC'd localListener = null; unlisten(); } }, opt_evtListenerOpts ); return unlisten; } /** * Returns a promise that will resolve as soon as the specified event has * fired on the element. * @param {!EventTarget} element * @param {string} eventType * @param {Object=} opt_evtListenerOpts * @param {function(!UnlistenDef)=} opt_cancel An optional function that, when * provided, will be called with the unlistener. This gives the caller * access to the unlistener, so it may be called manually when necessary. * @return {!Promise<!Event>} */ export function listenOncePromise( element, eventType, opt_evtListenerOpts, opt_cancel ) { let unlisten; const eventPromise = new Promise((resolve) => { unlisten = listenOnce(element, eventType, resolve, opt_evtListenerOpts); }); eventPromise.then(unlisten, unlisten); if (opt_cancel) { opt_cancel(unlisten); } return eventPromise; } /** * Whether the specified element/window has been loaded already. * @param {!Element|!Window} eleOrWindow * @return {boolean} */ export function isLoaded(eleOrWindow) { return !!( eleOrWindow.complete || eleOrWindow.readyState == 'complete' || (isHTMLMediaElement(eleOrWindow) && eleOrWindow.readyState > 0) || // If the passed in thing is a Window, infer loaded state from // (eleOrWindow.document && eleOrWindow.document.readyState == 'complete') ); } /** * Returns a promise that will resolve or fail based on the eleOrWindow's 'load' * and 'error' events. Optionally this method takes a timeout, which will reject * the promise if the resource has not loaded by then. * @param {T} eleOrWindow Supports both Elements and as a special case Windows. * @return {!Promise<T>} * @template T */ export function loadPromise(eleOrWindow) { let unlistenLoad; let unlistenError; if (isLoaded(eleOrWindow)) { return Promise.resolve(eleOrWindow); } const isMediaElement = isHTMLMediaElement(eleOrWindow); if ( isMediaElement && eleOrWindow[MEDIA_LOAD_FAILURE_SRC_PROPERTY] === eleOrWindow.currentSrc ) { return Promise.reject(eleOrWindow); } const loadingPromise = new Promise((resolve, reject) => { // Listen once since IE 5/6/7 fire the onload event continuously for // animated GIFs. if (isMediaElement) { // The following event can be triggered by the media or one of its // sources. Using capture is required as the media events do not bubble. unlistenLoad = listenOnce(eleOrWindow, 'loadedmetadata', resolve, { capture: true, }); } else { unlistenLoad = listenOnce(eleOrWindow, 'load', resolve); } // Don't unlisten on error for Windows. if (!eleOrWindow.tagName) { return; } let errorTarget = eleOrWindow; // If the media element has no `src`, it will try to load the sources in // document order. If the last source errors, then the media element // loading errored. if (isMediaElement && !eleOrWindow.hasAttribute('src')) { errorTarget = lastChildElement( eleOrWindow, (child) => child.tagName === 'SOURCE' ); if (!errorTarget) { return reject(new Error('Media has no source.')); } } unlistenError = listenOnce(errorTarget, 'error', reject); }); return loadingPromise.then( () => { if (unlistenError) { unlistenError(); } return eleOrWindow; }, () => { if (unlistenLoad) { unlistenLoad(); } failedToLoad(eleOrWindow); } ); } /** * Emit error on load failure. * @param {!Element|!Window} eleOrWindow Supports both Elements and as a special * case Windows. */ function failedToLoad(eleOrWindow) { // Mark the element as errored since some elements - like HTMLMediaElement // using HTMLSourceElement - do not provide any synchronous way to verify if // they already errored, even though the error event was already dispatched. if (isHTMLMediaElement(eleOrWindow)) { eleOrWindow[MEDIA_LOAD_FAILURE_SRC_PROPERTY] = eleOrWindow.currentSrc || true; } // Report failed loads as user errors so that they automatically go // into the "document error" bucket. let target = eleOrWindow; if (target && target.src) { target = target.src; } throw user().createError(LOAD_FAILURE_PREFIX, target); } /** * Returns true if the parameter is a HTMLMediaElement. * @param {!Element|!Window} eleOrWindow * @return {boolean} */ function isHTMLMediaElement(eleOrWindow) { return eleOrWindow.tagName === 'AUDIO' || eleOrWindow.tagName === 'VIDEO'; } /** * Returns true if this error message is was created for a load error. * @param {string} message An error message * @return {boolean} */ export function isLoadErrorMessage(message) { return message.indexOf(LOAD_FAILURE_PREFIX) != -1; }