@dash0/sdk-web
Version:
Dash0's Web SDK to collect telemetry from end-users' web browsers
106 lines (105 loc) • 4.59 kB
JavaScript
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);
}