{"version":3,"file":"dom.js","sources":["../../../src/instrument/dom.ts"],"sourcesContent":["// TODO(v8): Move everything in this file into the browser package. Nothing here is generic and we run risk of leaking browser types into non-browser packages.\n\n/* eslint-disable @typescript-eslint/no-explicit-any */\n/* eslint-disable @typescript-eslint/ban-types */\nimport type { HandlerDataDom } from '@sentry/types';\n\nimport { uuid4 } from '../misc';\nimport { addNonEnumerableProperty, fill } from '../object';\nimport { GLOBAL_OBJ } from '../worldwide';\nimport { addHandler, maybeInstrument, triggerHandlers } from './_handlers';\n\ntype SentryWrappedTarget = HTMLElement & { _sentryId?: string };\n\ntype AddEventListener = (\n type: string,\n listener: EventListenerOrEventListenerObject,\n options?: boolean | AddEventListenerOptions,\n) => void;\ntype RemoveEventListener = (\n type: string,\n listener: EventListenerOrEventListenerObject,\n options?: boolean | EventListenerOptions,\n) => void;\n\ntype InstrumentedElement = Element & {\n __sentry_instrumentation_handlers__?: {\n [key in 'click' | 'keypress']?: {\n handler?: Function;\n /** The number of custom listeners attached to this element */\n refCount: number;\n };\n };\n};\n\nconst WINDOW = GLOBAL_OBJ as unknown as Window;\nconst DEBOUNCE_DURATION = 1000;\n\nlet debounceTimerID: number | undefined;\nlet lastCapturedEventType: string | undefined;\nlet lastCapturedEventTargetId: string | undefined;\n\n/**\n * Add an instrumentation handler for when a click or a keypress happens.\n *\n * Use at your own risk, this might break without changelog notice, only used internally.\n * @hidden\n */\nexport function addClickKeypressInstrumentationHandler(handler: (data: HandlerDataDom) => void): void {\n const type = 'dom';\n addHandler(type, handler);\n maybeInstrument(type, instrumentDOM);\n}\n\n/** Exported for tests only. */\nexport function instrumentDOM(): void {\n if (!WINDOW.document) {\n return;\n }\n\n // Make it so that any click or keypress that is unhandled / bubbled up all the way to the document triggers our dom\n // handlers. (Normally we have only one, which captures a breadcrumb for each click or keypress.) Do this before\n // we instrument `addEventListener` so that we don't end up attaching this handler twice.\n const triggerDOMHandler = triggerHandlers.bind(null, 'dom');\n const globalDOMEventHandler = makeDOMEventHandler(triggerDOMHandler, true);\n WINDOW.document.addEventListener('click', globalDOMEventHandler, false);\n WINDOW.document.addEventListener('keypress', globalDOMEventHandler, false);\n\n // After hooking into click and keypress events bubbled up to `document`, we also hook into user-handled\n // clicks & keypresses, by adding an event listener of our own to any element to which they add a listener. That\n // way, whenever one of their handlers is triggered, ours will be, too. (This is needed because their handler\n // could potentially prevent the event from bubbling up to our global listeners. This way, our handler are still\n // guaranteed to fire at least once.)\n ['EventTarget', 'Node'].forEach((target: string) => {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\n const proto = (WINDOW as any)[target] && (WINDOW as any)[target].prototype;\n // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, no-prototype-builtins\n if (!proto || !proto.hasOwnProperty || !proto.hasOwnProperty('addEventListener')) {\n return;\n }\n\n fill(proto, 'addEventListener', function (originalAddEventListener: AddEventListener): AddEventListener {\n return function (\n this: Element,\n type: string,\n listener: EventListenerOrEventListenerObject,\n options?: boolean | AddEventListenerOptions,\n ): AddEventListener {\n if (type === 'click' || type == 'keypress') {\n try {\n const el = this as InstrumentedElement;\n const handlers = (el.__sentry_instrumentation_handlers__ = el.__sentry_instrumentation_handlers__ || {});\n const handlerForType = (handlers[type] = handlers[type] || { refCount: 0 });\n\n if (!handlerForType.handler) {\n const handler = makeDOMEventHandler(triggerDOMHandler);\n handlerForType.handler = handler;\n originalAddEventListener.call(this, type, handler, options);\n }\n\n handlerForType.refCount++;\n } catch (e) {\n // Accessing dom properties is always fragile.\n // Also allows us to skip `addEventListenrs` calls with no proper `this` context.\n }\n }\n\n return originalAddEventListener.call(this, type, listener, options);\n };\n });\n\n fill(\n proto,\n 'removeEventListener',\n function (originalRemoveEventListener: RemoveEventListener): RemoveEventListener {\n return function (\n this: Element,\n type: string,\n listener: EventListenerOrEventListenerObject,\n options?: boolean | EventListenerOptions,\n ): () => void {\n if (type === 'click' || type == 'keypress') {\n try {\n const el = this as InstrumentedElement;\n const handlers = el.__sentry_instrumentation_handlers__ || {};\n const handlerForType = handlers[type];\n\n if (handlerForType) {\n handlerForType.refCount--;\n // If there are no longer any custom handlers of the current type on this element, we can remove ours, too.\n if (handlerForType.refCount <= 0) {\n originalRemoveEventListener.call(this, type, handlerForType.handler, options);\n handlerForType.handler = undefined;\n delete handlers[type]; // eslint-disable-line @typescript-eslint/no-dynamic-delete\n }\n\n // If there are no longer any custom handlers of any type on this element, cleanup everything.\n if (Object.keys(handlers).length === 0) {\n delete el.__sentry_instrumentation_handlers__;\n }\n }\n } catch (e) {\n // Accessing dom properties is always fragile.\n // Also allows us to skip `addEventListenrs` calls with no proper `this` context.\n }\n }\n\n return originalRemoveEventListener.call(this, type, listener, options);\n };\n },\n );\n });\n}\n\n/**\n * Check whether the event is similar to the last captured one. For example, two click events on the same button.\n */\nfunction isSimilarToLastCapturedEvent(event: Event): boolean {\n // If both events have different type, then user definitely performed two separate actions. e.g. click + keypress.\n if (event.type !== lastCapturedEventType) {\n return false;\n }\n\n try {\n // If both events have the same type, it's still possible that actions were performed on different targets.\n // e.g. 2 clicks on different buttons.\n if (!event.target || (event.target as SentryWrappedTarget)._sentryId !== lastCapturedEventTargetId) {\n return false;\n }\n } catch (e) {\n // just accessing `target` property can throw an exception in some rare circumstances\n // see: https://github.com/getsentry/sentry-javascript/issues/838\n }\n\n // If both events have the same type _and_ same `target` (an element which triggered an event, _not necessarily_\n // to which an event listener was attached), we treat them as the same action, as we want to capture\n // only one breadcrumb. e.g. multiple clicks on the same button, or typing inside a user input box.\n return true;\n}\n\n/**\n * Decide whether an event should be captured.\n * @param event event to be captured\n */\nfunction shouldSkipDOMEvent(eventType: string, target: SentryWrappedTarget | null): boolean {\n // We are only interested in filtering `keypress` events for now.\n if (eventType !== 'keypress') {\n return false;\n }\n\n if (!target || !target.tagName) {\n return true;\n }\n\n // Only consider keypress events on actual input elements. This will disregard keypresses targeting body\n // e.g.tabbing through elements, hotkeys, etc.\n if (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' || target.isContentEditable) {\n return false;\n }\n\n return true;\n}\n\n/**\n * Wraps addEventListener to capture UI breadcrumbs\n */\nfunction makeDOMEventHandler(\n handler: (data: HandlerDataDom) => void,\n globalListener: boolean = false,\n): (event: Event) => void {\n return (event: Event & { _sentryCaptured?: true }): void => {\n // It's possible this handler might trigger multiple times for the same\n // event (e.g. event propagation through node ancestors).\n // Ignore if we've already captured that event.\n if (!event || event['_sentryCaptured']) {\n return;\n }\n\n const target = getEventTarget(event);\n\n // We always want to skip _some_ events.\n if (shouldSkipDOMEvent(event.type, target)) {\n return;\n }\n\n // Mark event as \"seen\"\n addNonEnumerableProperty(event, '_sentryCaptured', true);\n\n if (target && !target._sentryId) {\n // Add UUID to event target so we can identify if\n addNonEnumerableProperty(target, '_sentryId', uuid4());\n }\n\n const name = event.type === 'keypress' ? 'input' : event.type;\n\n // If there is no last captured event, it means that we can safely capture the new event and store it for future comparisons.\n // If there is a last captured event, see if the new event is different enough to treat it as a unique one.\n // If that's the case, emit the previous event and store locally the newly-captured DOM event.\n if (!isSimilarToLastCapturedEvent(event)) {\n const handlerData: HandlerDataDom = { event, name, global: globalListener };\n handler(handlerData);\n lastCapturedEventType = event.type;\n lastCapturedEventTargetId = target ? target._sentryId : undefined;\n }\n\n // Start a new debounce timer that will prevent us from capturing multiple events that should be grouped together.\n clearTimeout(debounceTimerID);\n debounceTimerID = WINDOW.setTimeout(() => {\n lastCapturedEventTargetId = undefined;\n lastCapturedEventType = undefined;\n }, DEBOUNCE_DURATION);\n };\n}\n\nfunction getEventTarget(event: Event): SentryWrappedTarget | null {\n try {\n return event.target as SentryWrappedTarget | null;\n } catch (e) {\n // just accessing `target` property can throw an exception in some rare circumstances\n // see: https://github.com/getsentry/sentry-javascript/issues/838\n return null;\n }\n}\n"],"names":[],"mappings":"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"}