UNPKG

@dash0/sdk-web

Version:

Dash0's Web SDK to collect telemetry from end-users' web browsers

106 lines (105 loc) 4.59 kB
const WRAPPED_EVENT_HANDLERS_ORIGINAL_FUNCTION_STORAGE_KEY = "__dash0OriginalFunctions"; // Asynchronous function wrapping: The process of wrapping a listener which goes into one function, e.g. // // - EventTarget#addEventListener // - EventEmitter#on // // and is removed via another function, e.g. // // - EventTarget#removeEventListener // - EventEmitter#off // // What is complicated about this, is that these methods identify registered listeners by function reference. // When we wrap a function, we naturally change the reference. We must therefore keep track of which // original function belongs to what wrapped function. // // This file provides helpers that help in the typical cases. It is removed from all browser specific APIs // in order to allow simple unit test execution. // // Note that this file follows the behavior outlined in DOM specification. Among others, this means that it is not // possible to register the same listener twice. // http://dom.spec.whatwg.org export function addWrappedFunction(storageTarget, wrappedFunction, valuesForEqualityCheck) { if (!storageTarget) return wrappedFunction; const storage = (storageTarget[WRAPPED_EVENT_HANDLERS_ORIGINAL_FUNCTION_STORAGE_KEY] = storageTarget[WRAPPED_EVENT_HANDLERS_ORIGINAL_FUNCTION_STORAGE_KEY] || []); const index = findInStorage(storageTarget, valuesForEqualityCheck); if (index !== -1) { // already registered. Do not allow re-registration return storage[index].wrappedFunction; } storage.push({ wrappedFunction, valuesForEqualityCheck, }); return wrappedFunction; } function findInStorage(storageTarget, valuesForEqualityCheck) { const storage = storageTarget[WRAPPED_EVENT_HANDLERS_ORIGINAL_FUNCTION_STORAGE_KEY]; for (let i = 0; i < storage.length; i++) { const storageItem = storage[i]; if (matchesEqualityCheck(storageItem.valuesForEqualityCheck, valuesForEqualityCheck)) { return i; } } return -1; } export function popWrappedFunction(storageTarget, valuesForEqualityCheck, fallback) { const storage = storageTarget?.[WRAPPED_EVENT_HANDLERS_ORIGINAL_FUNCTION_STORAGE_KEY]; if (storage == null) { return fallback; } const index = findInStorage(storageTarget, valuesForEqualityCheck); if (index === -1) { return fallback; } const storageItem = storage[index]; storage.splice(index, 1); return storageItem.wrappedFunction; } function matchesEqualityCheck(valuesForEqualityCheckA, valuesForEqualityCheckB) { if (valuesForEqualityCheckA.length !== valuesForEqualityCheckB.length) { return false; } for (let i = 0; i < valuesForEqualityCheckA.length; i++) { if (valuesForEqualityCheckA[i] !== valuesForEqualityCheckB[i]) { return false; } } return true; } export function addWrappedDomEventListener(storageTarget, wrappedFunction, eventName, eventListener, optionsOrCapture) { return addWrappedFunction(storageTarget, wrappedFunction, getDomEventListenerValuesForEqualityCheck(eventName, eventListener, optionsOrCapture)); } function getDomEventListenerValuesForEqualityCheck(eventName, eventListener, optionsOrCapture) { return [eventName, eventListener, getDomEventListenerCaptureValue(optionsOrCapture)]; } export function getDomEventListenerCaptureValue(optionsOrCapture) { // > Let capture, passive, and once be the result of flattening more options. // https://dom.spec.whatwg.org/#dom-eventtarget-addeventlistener // // > To flatten more options, run these steps: // > 1. Let capture be the result of flattening options. // https://dom.spec.whatwg.org/#event-flatten-more // // > To flatten options, run these steps: // > 1. If options is a boolean, then return options. // > 2. Return options’s capture. // https://dom.spec.whatwg.org/#concept-flatten-options // // > dictionary EventListenerOptions { // > boolean capture = false; // > }; // https://dom.spec.whatwg.org/#dom-eventlisteneroptions-capture if (optionsOrCapture == null) { return false; } else if (typeof optionsOrCapture === "object") { return Boolean(optionsOrCapture.capture); } return Boolean(optionsOrCapture); } export function popWrappedDomEventListener(storageTarget, eventName, eventListener, optionsOrCapture, fallback) { return popWrappedFunction(storageTarget, getDomEventListenerValuesForEqualityCheck(eventName, eventListener, optionsOrCapture), fallback); }