From 5572e9c687abc6bf218d5dceb280a2f38c895f2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Zugmeyer?= Date: Mon, 12 Sep 2022 17:45:31 +0200 Subject: [PATCH 01/14] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20[RUMF-1368]=20add=20?= =?UTF-8?q?a=20time=20util=20function=20helper=20to=20add=20durations?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/core/src/tools/contextHistory.spec.ts | 6 +++--- packages/core/src/tools/timeUtils.ts | 7 +++++++ .../rum-core/src/domain/contexts/foregroundContexts.ts | 5 ++--- .../rumEventsCollection/resource/matchRequestTiming.ts | 9 ++++----- .../rumEventsCollection/view/trackViewMetrics.spec.ts | 10 ++++------ 5 files changed, 20 insertions(+), 17 deletions(-) diff --git a/packages/core/src/tools/contextHistory.spec.ts b/packages/core/src/tools/contextHistory.spec.ts index 3f782fdaa3..46d8e18a7c 100644 --- a/packages/core/src/tools/contextHistory.spec.ts +++ b/packages/core/src/tools/contextHistory.spec.ts @@ -1,6 +1,7 @@ import type { Clock } from '../../test/specHelper' import { mockClock } from '../../test/specHelper' -import type { RelativeTime } from './timeUtils' +import type { Duration, RelativeTime } from './timeUtils' +import { addDuration } from './timeUtils' import { ONE_MINUTE } from './utils' import { CLEAR_OLD_CONTEXTS_INTERVAL, ContextHistory } from './contextHistory' @@ -115,8 +116,7 @@ describe('contextHistory', () => { it('should clear old contexts', () => { const originalTime = performance.now() as RelativeTime - // eslint-disable-next-line @typescript-eslint/restrict-plus-operands - contextHistory.add('foo', originalTime).close((originalTime + 10) as RelativeTime) + contextHistory.add('foo', originalTime).close(addDuration(originalTime, 10 as Duration)) clock.tick(10) expect(contextHistory.find(originalTime)).toBeDefined() diff --git a/packages/core/src/tools/timeUtils.ts b/packages/core/src/tools/timeUtils.ts index f96fa5394e..e704928175 100644 --- a/packages/core/src/tools/timeUtils.ts +++ b/packages/core/src/tools/timeUtils.ts @@ -65,6 +65,13 @@ export function elapsed(start: number, end: number) { return (end - start) as Duration } +export function addDuration(a: TimeStamp, b: Duration): TimeStamp +export function addDuration(a: RelativeTime, b: Duration): RelativeTime +export function addDuration(a: Duration, b: Duration): Duration +export function addDuration(a: number, b: number) { + return a + b +} + /** * Get the time since the navigation was started. * diff --git a/packages/rum-core/src/domain/contexts/foregroundContexts.ts b/packages/rum-core/src/domain/contexts/foregroundContexts.ts index a626d10443..15b7290a05 100644 --- a/packages/rum-core/src/domain/contexts/foregroundContexts.ts +++ b/packages/rum-core/src/domain/contexts/foregroundContexts.ts @@ -1,5 +1,5 @@ import type { RelativeTime, Duration } from '@datadog/browser-core' -import { addEventListener, DOM_EVENT, elapsed, relativeNow, toServerDuration } from '@datadog/browser-core' +import { addDuration, addEventListener, DOM_EVENT, elapsed, relativeNow, toServerDuration } from '@datadog/browser-core' import type { InForegroundPeriod } from '../../rawRumEvent.types' // Arbitrary value to cap number of element mostly for backend & to save bandwidth @@ -99,8 +99,7 @@ function isInForegroundAt(startTime: RelativeTime): boolean { } function selectInForegroundPeriodsFor(eventStartTime: RelativeTime, duration: Duration): InForegroundPeriod[] { - // eslint-disable-next-line @typescript-eslint/restrict-plus-operands - const eventEndTime = (eventStartTime + duration) as RelativeTime + const eventEndTime = addDuration(eventStartTime, duration) const filteredForegroundPeriods: InForegroundPeriod[] = [] const earliestIndex = Math.max(0, foregroundPeriods.length - MAX_NUMBER_OF_SELECTABLE_FOREGROUND_PERIODS) diff --git a/packages/rum-core/src/domain/rumEventsCollection/resource/matchRequestTiming.ts b/packages/rum-core/src/domain/rumEventsCollection/resource/matchRequestTiming.ts index c2abd2c5a9..6d965d7c79 100644 --- a/packages/rum-core/src/domain/rumEventsCollection/resource/matchRequestTiming.ts +++ b/packages/rum-core/src/domain/rumEventsCollection/resource/matchRequestTiming.ts @@ -1,4 +1,5 @@ import type { Duration, RelativeTime } from '@datadog/browser-core' +import { addDuration } from '@datadog/browser-core' import type { RumPerformanceResourceTiming } from '../../../browser/performanceCollection' import type { RequestCompleteEvent } from '../../requestCollection' import { toValidEntry } from './resourceUtils' @@ -59,12 +60,10 @@ function firstCanBeOptionRequest(correspondingEntries: RumPerformanceResourceTim } function endTime(timing: Timing) { - // eslint-disable-next-line @typescript-eslint/restrict-plus-operands - return (timing.startTime + timing.duration) as RelativeTime + return addDuration(timing.startTime, timing.duration) } function isBetween(timing: Timing, start: RelativeTime, end: RelativeTime) { - const errorMargin = 1 - // eslint-disable-next-line @typescript-eslint/restrict-plus-operands - return timing.startTime >= start - errorMargin && endTime(timing) <= end + errorMargin + const errorMargin = 1 as Duration + return timing.startTime >= start - errorMargin && endTime(timing) <= addDuration(end, errorMargin) } diff --git a/packages/rum-core/src/domain/rumEventsCollection/view/trackViewMetrics.spec.ts b/packages/rum-core/src/domain/rumEventsCollection/view/trackViewMetrics.spec.ts index da4c9ddfb1..dd9948351e 100644 --- a/packages/rum-core/src/domain/rumEventsCollection/view/trackViewMetrics.spec.ts +++ b/packages/rum-core/src/domain/rumEventsCollection/view/trackViewMetrics.spec.ts @@ -1,5 +1,5 @@ import type { Context, RelativeTime, Duration } from '@datadog/browser-core' -import { relativeNow } from '@datadog/browser-core' +import { addDuration, relativeNow } from '@datadog/browser-core' import type { RumEvent } from '../../../rumEvent.types' import type { TestSetupBuilder, ViewTest } from '../../../../test/specHelper' import { setup, setupViewTest } from '../../../../test/specHelper' @@ -145,10 +145,9 @@ describe('rum track view metrics', () => { // introduce a gap between time origin and tracking start // ensure that `load event > activity delay` and `load event < activity delay + clock gap` // to make the test fail if the clock gap is not correctly taken into account - const CLOCK_GAP = - FAKE_NAVIGATION_ENTRY_WITH_LOADEVENT_AFTER_ACTIVITY_TIMING.loadEventEnd - + const CLOCK_GAP = (FAKE_NAVIGATION_ENTRY_WITH_LOADEVENT_AFTER_ACTIVITY_TIMING.loadEventEnd - BEFORE_PAGE_ACTIVITY_VALIDATION_DELAY + - 1 + 1) as Duration setupBuilder.clock!.tick(CLOCK_GAP) @@ -169,8 +168,7 @@ describe('rum track view metrics', () => { clock.tick(THROTTLE_VIEW_UPDATE_PERIOD) expect(getViewUpdateCount()).toEqual(2) - // eslint-disable-next-line @typescript-eslint/restrict-plus-operands - expect(getViewUpdate(1).loadingTime).toEqual((BEFORE_PAGE_ACTIVITY_VALIDATION_DELAY + CLOCK_GAP) as Duration) + expect(getViewUpdate(1).loadingTime).toEqual(addDuration(BEFORE_PAGE_ACTIVITY_VALIDATION_DELAY, CLOCK_GAP)) }) }) From 7c3c8d9612cbee9466ca4ca6cda01704d4904784 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Zugmeyer?= Date: Mon, 12 Sep 2022 17:52:57 +0200 Subject: [PATCH 02/14] =?UTF-8?q?=E2=9C=85=20[RUMF-1368]=20tweak=20emulate?= =?UTF-8?q?d=20clicks=20to=20better=20match=20reality?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * dispatch `pointerdown` and `pointerup` events * add a duration between those events --- .../action/trackClickActions.spec.ts | 71 +++++++++++-------- 1 file changed, 41 insertions(+), 30 deletions(-) diff --git a/packages/rum-core/src/domain/rumEventsCollection/action/trackClickActions.spec.ts b/packages/rum-core/src/domain/rumEventsCollection/action/trackClickActions.spec.ts index accab5a5af..96b8b09df2 100644 --- a/packages/rum-core/src/domain/rumEventsCollection/action/trackClickActions.spec.ts +++ b/packages/rum-core/src/domain/rumEventsCollection/action/trackClickActions.spec.ts @@ -1,5 +1,6 @@ import type { Context, Observable, Duration } from '@datadog/browser-core' import { + addDuration, updateExperimentalFeatures, resetExperimentalFeatures, clocksNow, @@ -24,6 +25,8 @@ import { MAX_DURATION_BETWEEN_CLICKS } from './clickChain' const BEFORE_PAGE_ACTIVITY_VALIDATION_DELAY = PAGE_ACTIVITY_VALIDATION_DELAY * 0.8 // A long delay used to wait after any action is finished. const EXPIRE_DELAY = CLICK_ACTION_MAX_DURATION * 10 +// Arbitrary duration between pointerdown and pointerup for emulated clicks +const EMULATED_CLICK_DURATION = 80 as Duration function eventsCollector() { const events: T[] = [] @@ -83,6 +86,7 @@ describe('trackClickActions', () => { it('starts a click action when clicking on an element', () => { const { domMutationObservable, clock } = setupBuilder.build() + const pointerDownClocks = clocksNow() emulateClickWithActivity(domMutationObservable, clock) expect(findActionId()).not.toBeUndefined() clock.tick(EXPIRE_DELAY) @@ -97,7 +101,10 @@ describe('trackClickActions', () => { duration: BEFORE_PAGE_ACTIVITY_VALIDATION_DELAY as Duration, id: jasmine.any(String), name: 'Click me', - startClocks: jasmine.any(Object), + startClocks: { + relative: addDuration(pointerDownClocks.relative, EMULATED_CLICK_DURATION), + timeStamp: addDuration(pointerDownClocks.timeStamp, EMULATED_CLICK_DURATION), + }, type: ActionType.CLICK, event: domEvent, frustrationTypes: [], @@ -141,11 +148,11 @@ describe('trackClickActions', () => { it('should keep track of previously validated click actions', () => { const { domMutationObservable, clock } = setupBuilder.build() - const clickActionStartTime = relativeNow() + const pointerDownStart = relativeNow() emulateClickWithActivity(domMutationObservable, clock) clock.tick(EXPIRE_DELAY) - expect(findActionId(clickActionStartTime)).not.toBeUndefined() + expect(findActionId(addDuration(pointerDownStart, EMULATED_CLICK_DURATION))).not.toBeUndefined() }) it('counts errors occurring during the click action', () => { @@ -211,18 +218,18 @@ describe('trackClickActions', () => { it('ignores any starting click action while another one is ongoing', () => { const { domMutationObservable, clock } = setupBuilder.build() - const firstClickTimeStamp = timeStampNow() + const firstPointerDownTimeStamp = timeStampNow() emulateClickWithActivity(domMutationObservable, clock) emulateClickWithActivity(domMutationObservable, clock) clock.tick(EXPIRE_DELAY) expect(events.length).toBe(1) - expect(events[0].startClocks.timeStamp).toBe(firstClickTimeStamp) + expect(events[0].startClocks.timeStamp).toBe(addDuration(firstPointerDownTimeStamp, EMULATED_CLICK_DURATION)) }) it('discards a click action when nothing happens after a click', () => { const { clock } = setupBuilder.build() - emulateClickWithoutActivity() + emulateClickWithoutActivity(clock) clock.tick(EXPIRE_DELAY) expect(events).toEqual([]) @@ -282,20 +289,20 @@ describe('trackClickActions', () => { it('collect click actions even if another one is ongoing', () => { const { domMutationObservable, clock } = setupBuilder.build() - const firstClickTimeStamp = timeStampNow() + const firstPointerDownTimeStamp = timeStampNow() emulateClickWithActivity(domMutationObservable, clock) - const secondClickTimeStamp = timeStampNow() + const secondPointerDownTimeStamp = timeStampNow() emulateClickWithActivity(domMutationObservable, clock) clock.tick(EXPIRE_DELAY) expect(events.length).toBe(2) - expect(events[0].startClocks.timeStamp).toBe(firstClickTimeStamp) - expect(events[1].startClocks.timeStamp).toBe(secondClickTimeStamp) + expect(events[0].startClocks.timeStamp).toBe(addDuration(firstPointerDownTimeStamp, EMULATED_CLICK_DURATION)) + expect(events[1].startClocks.timeStamp).toBe(addDuration(secondPointerDownTimeStamp, EMULATED_CLICK_DURATION)) }) it('collect click actions even if nothing happens after a click (dead click)', () => { const { clock } = setupBuilder.build() - emulateClickWithoutActivity() + emulateClickWithoutActivity(clock) clock.tick(EXPIRE_DELAY) expect(events.length).toBe(1) @@ -305,7 +312,7 @@ describe('trackClickActions', () => { it('does not set a duration for dead clicks', () => { const { clock } = setupBuilder.build() - emulateClickWithoutActivity() + emulateClickWithoutActivity(clock) clock.tick(EXPIRE_DELAY) expect(events.length).toBe(1) @@ -324,7 +331,7 @@ describe('trackClickActions', () => { describe('rage clicks', () => { it('considers a chain of three clicks or more as a single action with "rage" frustration type', () => { const { domMutationObservable, clock } = setupBuilder.build() - const firstClickTimeStamp = timeStampNow() + const firstPointerDownTimeStamp = timeStampNow() const actionDuration = 5 emulateClickWithActivity(domMutationObservable, clock, undefined, actionDuration) emulateClickWithActivity(domMutationObservable, clock, undefined, actionDuration) @@ -332,9 +339,11 @@ describe('trackClickActions', () => { clock.tick(EXPIRE_DELAY) expect(events.length).toBe(1) - expect(events[0].startClocks.timeStamp).toBe(firstClickTimeStamp) + expect(events[0].startClocks.timeStamp).toBe(addDuration(firstPointerDownTimeStamp, EMULATED_CLICK_DURATION)) expect(events[0].frustrationTypes).toEqual([FrustrationType.RAGE_CLICK]) - expect(events[0].duration).toBe((MAX_DURATION_BETWEEN_CLICKS + 2 * actionDuration) as Duration) + expect(events[0].duration).toBe( + (MAX_DURATION_BETWEEN_CLICKS + 2 * actionDuration + 2 * EMULATED_CLICK_DURATION) as Duration + ) }) it('should contain original events from of rage sequence', () => { @@ -354,7 +363,7 @@ describe('trackClickActions', () => { const { lifeCycle, domMutationObservable, clock } = setupBuilder.build() // Dead - emulateClickWithoutActivity() + emulateClickWithoutActivity(clock) clock.tick(PAGE_ACTIVITY_VALIDATION_DELAY) // Error @@ -392,7 +401,7 @@ describe('trackClickActions', () => { it('considers a "click without activity" followed by an error as a click action with "error" (and "dead") frustration type', () => { const { lifeCycle, clock } = setupBuilder.build() - emulateClickWithoutActivity() + emulateClickWithoutActivity(clock) lifeCycle.notify(LifeCycleEventType.RUM_EVENT_COLLECTED, RAW_ERROR_EVENT) clock.tick(EXPIRE_DELAY) @@ -407,7 +416,7 @@ describe('trackClickActions', () => { it('considers a "click without activity" as a dead click', () => { const { clock } = setupBuilder.build() - emulateClickWithoutActivity() + emulateClickWithoutActivity(clock) clock.tick(EXPIRE_DELAY) expect(events.length).toBe(1) @@ -422,26 +431,28 @@ describe('trackClickActions', () => { target: HTMLElement = button, clickActionDuration: number = BEFORE_PAGE_ACTIVITY_VALIDATION_DELAY ) { - emulateClickWithoutActivity(target) + emulateClickWithoutActivity(clock, target) clock.tick(clickActionDuration) // Since we don't collect dom mutations for this test, manually dispatch one domMutationObservable.notify() } - function emulateClickWithoutActivity(target: HTMLElement = button) { + function emulateClickWithoutActivity(clock: Clock, target: HTMLElement = button) { const targetPosition = target.getBoundingClientRect() const offsetX = targetPosition.width / 2 const offsetY = targetPosition.height / 2 - target.dispatchEvent( - createNewEvent('click', { - target, - clientX: targetPosition.left + offsetX, - clientY: targetPosition.top + offsetY, - offsetX, - offsetY, - timeStamp: timeStampNow(), - }) - ) + const eventProperties = { + target, + clientX: targetPosition.left + offsetX, + clientY: targetPosition.top + offsetY, + offsetX, + offsetY, + timeStamp: timeStampNow(), + } + target.dispatchEvent(createNewEvent('pointerdown', eventProperties)) + clock.tick(EMULATED_CLICK_DURATION) + target.dispatchEvent(createNewEvent('pointerup', eventProperties)) + target.dispatchEvent(createNewEvent('click', eventProperties)) } }) From 75052d4dc4ab0f1f506adb3afd605af7ebf60661 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Zugmeyer?= Date: Fri, 9 Sep 2022 15:30:10 +0200 Subject: [PATCH 03/14] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20[RUMF-1368]=20extrac?= =?UTF-8?q?t=20processClick=20to=20a=20module=20function?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../action/trackClickActions.ts | 140 ++++++++++-------- 1 file changed, 82 insertions(+), 58 deletions(-) diff --git a/packages/rum-core/src/domain/rumEventsCollection/action/trackClickActions.ts b/packages/rum-core/src/domain/rumEventsCollection/action/trackClickActions.ts index a49212a60f..b3a8dbd880 100644 --- a/packages/rum-core/src/domain/rumEventsCollection/action/trackClickActions.ts +++ b/packages/rum-core/src/domain/rumEventsCollection/action/trackClickActions.ts @@ -69,7 +69,6 @@ export function trackClickActions( ) { const history: ClickActionIdHistory = new ContextHistory(ACTION_CONTEXT_TIME_OUT_DELAY) const stopObservable = new Observable() - const { trackFrustrations } = configuration let currentClickChain: ClickChain | undefined lifeCycle.subscribe(LifeCycleEventType.SESSION_RENEWED, () => { @@ -79,11 +78,22 @@ export function trackClickActions( lifeCycle.subscribe(LifeCycleEventType.BEFORE_UNLOAD, stopClickChain) lifeCycle.subscribe(LifeCycleEventType.VIEW_ENDED, stopClickChain) - const { stop: stopActionEventsListener } = listenActionEvents({ onClick: processClick }) + const { stop: stopActionEventsListener } = listenActionEvents({ + onClick: (onClickContext) => + processClick( + configuration, + lifeCycle, + domMutationObservable, + history, + stopObservable, + appendClickToClickChain, + onClickContext + ), + }) const actionContexts: ActionContexts = { findActionId: (startTime?: RelativeTime) => - trackFrustrations ? history.findAll(startTime) : history.find(startTime), + configuration.trackFrustrations ? history.findAll(startTime) : history.find(startTime), } return { @@ -95,76 +105,90 @@ export function trackClickActions( actionContexts, } + function appendClickToClickChain(click: Click) { + if (!currentClickChain || !currentClickChain.tryAppend(click)) { + const rageClick = click.clone() + currentClickChain = createClickChain(click, (clicks) => { + finalizeClicks(clicks, rageClick) + }) + } + } + function stopClickChain() { if (currentClickChain) { currentClickChain.stop() } } +} - function processClick({ event, getUserActivity }: OnClickContext) { - if (!trackFrustrations && history.find()) { - // TODO: remove this in a future major version. To keep retrocompatibility, ignore any new - // action if another one is already occurring. - return - } +function processClick( + configuration: RumConfiguration, + lifeCycle: LifeCycle, + domMutationObservable: Observable, + history: ClickActionIdHistory, + stopObservable: Observable, + appendClickToClickChain: (click: Click) => void, + { event, getUserActivity }: OnClickContext +) { + if (!configuration.trackFrustrations && history.find()) { + // TODO: remove this in a future major version. To keep retrocompatibility, ignore any new + // action if another one is already occurring. + return + } - const clickActionBase = computeClickActionBase(event, configuration.actionNameAttribute) - if (!trackFrustrations && !clickActionBase.name) { - // TODO: remove this in a future major version. To keep retrocompatibility, ignore any action - // with a blank name - return - } + const clickActionBase = computeClickActionBase(event, configuration.actionNameAttribute) + if (!configuration.trackFrustrations && !clickActionBase.name) { + // TODO: remove this in a future major version. To keep retrocompatibility, ignore any action + // with a blank name + return + } - const click = newClick(lifeCycle, history, getUserActivity, clickActionBase) + const click = newClick(lifeCycle, history, getUserActivity, clickActionBase) - if (trackFrustrations && (!currentClickChain || !currentClickChain.tryAppend(click))) { - const rageClick = click.clone() - currentClickChain = createClickChain(click, (clicks) => { - finalizeClicks(clicks, rageClick) - }) - } + if (configuration.trackFrustrations) { + appendClickToClickChain(click) + } - const { stop: stopWaitPageActivityEnd } = waitPageActivityEnd( - lifeCycle, - domMutationObservable, - configuration, - (pageActivityEndEvent) => { - if (pageActivityEndEvent.hadActivity && pageActivityEndEvent.end < clickActionBase.startClocks.timeStamp) { - // If the clock is looking weird, just discard the click - click.discard() - } else { - click.stop(pageActivityEndEvent.hadActivity ? pageActivityEndEvent.end : undefined) - - // Validate or discard the click only if we don't track frustrations. It'll be done when - // the click chain is finalized. - if (!trackFrustrations) { - if (!pageActivityEndEvent.hadActivity) { - // If we are not tracking frustrations, we should discard the click to keep backward - // compatibility. - click.discard() - } else { - click.validate() - } + const { stop: stopWaitPageActivityEnd } = waitPageActivityEnd( + lifeCycle, + domMutationObservable, + configuration, + (pageActivityEndEvent) => { + if (pageActivityEndEvent.hadActivity && pageActivityEndEvent.end < clickActionBase.startClocks.timeStamp) { + // If the clock is looking weird, just discard the click + click.discard() + } else { + click.stop(pageActivityEndEvent.hadActivity ? pageActivityEndEvent.end : undefined) + + // Validate or discard the click only if we don't track frustrations. It'll be done when + // the click chain is finalized. + if (!configuration.trackFrustrations) { + if (!pageActivityEndEvent.hadActivity) { + // If we are not tracking frustrations, we should discard the click to keep backward + // compatibility. + click.discard() + } else { + click.validate() } } - }, - CLICK_ACTION_MAX_DURATION - ) + } + }, + CLICK_ACTION_MAX_DURATION + ) - const viewEndedSubscription = lifeCycle.subscribe(LifeCycleEventType.VIEW_ENDED, ({ endClocks }) => { - click.stop(endClocks.timeStamp) - }) + const viewEndedSubscription = lifeCycle.subscribe(LifeCycleEventType.VIEW_ENDED, ({ endClocks }) => { + click.stop(endClocks.timeStamp) + }) - const stopSubscription = stopObservable.subscribe(() => { - click.stop() - }) + const stopSubscription = stopObservable.subscribe(() => { + click.stop() + }) - click.stopObservable.subscribe(() => { - viewEndedSubscription.unsubscribe() - stopWaitPageActivityEnd() - stopSubscription.unsubscribe() - }) - } + click.stopObservable.subscribe(() => { + viewEndedSubscription.unsubscribe() + stopWaitPageActivityEnd() + stopSubscription.unsubscribe() + }) } function computeClickActionBase(event: MouseEvent & { target: Element }, actionNameAttribute?: string) { From 4c000a08d2b34be6b4a036a8b8f5744e2600e6c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Zugmeyer?= Date: Fri, 9 Sep 2022 17:11:21 +0200 Subject: [PATCH 04/14] =?UTF-8?q?=F0=9F=8F=B7=EF=B8=8F=E2=99=BB=EF=B8=8F?= =?UTF-8?q?=20=20[RUMF-1368]=20factorize=20a=20few=20types?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../action/listenActionEvents.spec.ts | 4 ++-- .../action/listenActionEvents.ts | 15 +++++++++++---- .../action/trackClickActions.ts | 6 +++--- 3 files changed, 16 insertions(+), 9 deletions(-) diff --git a/packages/rum-core/src/domain/rumEventsCollection/action/listenActionEvents.spec.ts b/packages/rum-core/src/domain/rumEventsCollection/action/listenActionEvents.spec.ts index d6ddcca769..3aec7600ac 100644 --- a/packages/rum-core/src/domain/rumEventsCollection/action/listenActionEvents.spec.ts +++ b/packages/rum-core/src/domain/rumEventsCollection/action/listenActionEvents.spec.ts @@ -1,10 +1,10 @@ import type { Clock } from '../../../../../core/test/specHelper' import { createNewEvent, mockClock } from '../../../../../core/test/specHelper' -import type { OnClickContext } from './listenActionEvents' +import type { OnClickCallback } from './listenActionEvents' import { listenActionEvents } from './listenActionEvents' describe('listenActionEvents', () => { - let onClickSpy: jasmine.Spy<(context: OnClickContext) => void> + let onClickSpy: jasmine.Spy let stopListenEvents: () => void beforeEach(() => { diff --git a/packages/rum-core/src/domain/rumEventsCollection/action/listenActionEvents.ts b/packages/rum-core/src/domain/rumEventsCollection/action/listenActionEvents.ts index bce94173bc..c1a6e26755 100644 --- a/packages/rum-core/src/domain/rumEventsCollection/action/listenActionEvents.ts +++ b/packages/rum-core/src/domain/rumEventsCollection/action/listenActionEvents.ts @@ -1,11 +1,14 @@ import { addEventListener, DOM_EVENT, monitor } from '@datadog/browser-core' +export type MouseEventOnElement = MouseEvent & { target: Element } + +export type OnClickCallback = (context: OnClickContext) => void export interface OnClickContext { - event: MouseEvent & { target: Element } + event: MouseEventOnElement getUserActivity(): { selection: boolean; input: boolean } } -export function listenActionEvents({ onClick }: { onClick(context: OnClickContext): void }) { +export function listenActionEvents({ onClick }: { onClick: OnClickCallback }) { let hasSelectionChanged = false let selectionEmptyAtMouseDown: boolean let hasInputChanged = false @@ -36,7 +39,7 @@ export function listenActionEvents({ onClick }: { onClick(context: OnClickContex window, DOM_EVENT.CLICK, (clickEvent: MouseEvent) => { - if (clickEvent.target instanceof Element) { + if (isMouseEventOnElement(clickEvent)) { // Use a scoped variable to make sure the value is not changed by other clicks const userActivity = { selection: hasSelectionChanged, @@ -51,7 +54,7 @@ export function listenActionEvents({ onClick }: { onClick(context: OnClickContex } onClick({ - event: clickEvent as MouseEvent & { target: Element }, + event: clickEvent, getUserActivity: () => userActivity, }) } @@ -80,3 +83,7 @@ function isSelectionEmpty(): boolean { const selection = window.getSelection() return !selection || selection.isCollapsed } + +function isMouseEventOnElement(event: Event): event is MouseEventOnElement { + return event.target instanceof Element +} diff --git a/packages/rum-core/src/domain/rumEventsCollection/action/trackClickActions.ts b/packages/rum-core/src/domain/rumEventsCollection/action/trackClickActions.ts index b3a8dbd880..8a6c982200 100644 --- a/packages/rum-core/src/domain/rumEventsCollection/action/trackClickActions.ts +++ b/packages/rum-core/src/domain/rumEventsCollection/action/trackClickActions.ts @@ -23,7 +23,7 @@ import type { ClickChain } from './clickChain' import { createClickChain } from './clickChain' import { getActionNameFromElement } from './getActionNameFromElement' import { getSelectorsFromElement } from './getSelectorsFromElement' -import type { OnClickContext } from './listenActionEvents' +import type { MouseEventOnElement, OnClickContext } from './listenActionEvents' import { listenActionEvents } from './listenActionEvents' import { computeFrustration } from './computeFrustration' @@ -47,7 +47,7 @@ export interface ClickAction { startClocks: ClocksState duration?: Duration counts: ActionCounts - event: MouseEvent & { target: Element } + event: MouseEventOnElement frustrationTypes: FrustrationType[] events: Event[] } @@ -191,7 +191,7 @@ function processClick( }) } -function computeClickActionBase(event: MouseEvent & { target: Element }, actionNameAttribute?: string) { +function computeClickActionBase(event: MouseEventOnElement, actionNameAttribute?: string) { let target: ClickAction['target'] let position: ClickAction['position'] From 2909b3baa63a002f38ad2420ce821005f67b965a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Zugmeyer?= Date: Fri, 9 Sep 2022 17:44:17 +0200 Subject: [PATCH 05/14] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20[RUMF-1368]=20introd?= =?UTF-8?q?uce=20a=20"onPointerDown"=20callback?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../action/listenActionEvents.spec.ts | 27 +++- .../action/listenActionEvents.ts | 20 +-- .../action/trackClickActions.ts | 119 +++++++++--------- 3 files changed, 91 insertions(+), 75 deletions(-) diff --git a/packages/rum-core/src/domain/rumEventsCollection/action/listenActionEvents.spec.ts b/packages/rum-core/src/domain/rumEventsCollection/action/listenActionEvents.spec.ts index 3aec7600ac..1af2e3967d 100644 --- a/packages/rum-core/src/domain/rumEventsCollection/action/listenActionEvents.spec.ts +++ b/packages/rum-core/src/domain/rumEventsCollection/action/listenActionEvents.spec.ts @@ -1,15 +1,17 @@ import type { Clock } from '../../../../../core/test/specHelper' import { createNewEvent, mockClock } from '../../../../../core/test/specHelper' -import type { OnClickCallback } from './listenActionEvents' +import type { OnClickCallback, OnPointerDownCallback } from './listenActionEvents' import { listenActionEvents } from './listenActionEvents' describe('listenActionEvents', () => { let onClickSpy: jasmine.Spy + let onPointerDownSpy: jasmine.Spy let stopListenEvents: () => void beforeEach(() => { onClickSpy = jasmine.createSpy() - ;({ stop: stopListenEvents } = listenActionEvents({ onClick: onClickSpy })) + onPointerDownSpy = jasmine.createSpy().and.returnValue({ onClick: onClickSpy }) + ;({ stop: stopListenEvents } = listenActionEvents({ onPointerDown: onPointerDownSpy })) }) afterEach(() => { @@ -24,6 +26,17 @@ describe('listenActionEvents', () => { }) }) + it('aborts click lifecycle if the pointerdown event occurs on a non-element', () => { + emulateClick({ target: document.createTextNode('foo') }) + expect(onClickSpy).not.toHaveBeenCalled() + }) + + it('can abort click lifecycle by returning undefined from the onPointerDown callback', () => { + onPointerDownSpy.and.returnValue(undefined) + emulateClick() + expect(onClickSpy).not.toHaveBeenCalled() + }) + describe('selection change', () => { let text: Text @@ -162,10 +175,12 @@ describe('listenActionEvents', () => { } }) - function emulateClick({ beforeMouseUp }: { beforeMouseUp?(): void } = {}) { - document.body.dispatchEvent(createNewEvent('mousedown')) + function emulateClick({ beforeMouseUp, target = document.body }: { beforeMouseUp?(): void; target?: Node } = {}) { + window.dispatchEvent(createNewEvent('pointerdown', { target })) + window.dispatchEvent(createNewEvent('mousedown', { target })) beforeMouseUp?.() - document.body.dispatchEvent(createNewEvent('mouseup')) - document.body.dispatchEvent(createNewEvent('click')) + window.dispatchEvent(createNewEvent('pointerup', { target })) + window.dispatchEvent(createNewEvent('mouseup', { target })) + window.dispatchEvent(createNewEvent('click', { target })) } }) diff --git a/packages/rum-core/src/domain/rumEventsCollection/action/listenActionEvents.ts b/packages/rum-core/src/domain/rumEventsCollection/action/listenActionEvents.ts index c1a6e26755..673d76e011 100644 --- a/packages/rum-core/src/domain/rumEventsCollection/action/listenActionEvents.ts +++ b/packages/rum-core/src/domain/rumEventsCollection/action/listenActionEvents.ts @@ -2,24 +2,29 @@ import { addEventListener, DOM_EVENT, monitor } from '@datadog/browser-core' export type MouseEventOnElement = MouseEvent & { target: Element } +export type OnPointerDownCallback = (event: MouseEventOnElement) => { onClick: OnClickCallback } | undefined export type OnClickCallback = (context: OnClickContext) => void export interface OnClickContext { event: MouseEventOnElement getUserActivity(): { selection: boolean; input: boolean } } -export function listenActionEvents({ onClick }: { onClick: OnClickCallback }) { +export function listenActionEvents({ onPointerDown }: { onPointerDown: OnPointerDownCallback }) { let hasSelectionChanged = false - let selectionEmptyAtMouseDown: boolean + let selectionEmptyAtPointerDown: boolean let hasInputChanged = false + let onClick: ((context: OnClickContext) => void) | undefined const listeners = [ addEventListener( window, - DOM_EVENT.MOUSE_DOWN, - () => { + DOM_EVENT.POINTER_DOWN, + (event) => { hasSelectionChanged = false - selectionEmptyAtMouseDown = isSelectionEmpty() + selectionEmptyAtPointerDown = isSelectionEmpty() + if (isMouseEventOnElement(event)) { + onClick = onPointerDown(event)?.onClick + } }, { capture: true } ), @@ -28,7 +33,7 @@ export function listenActionEvents({ onClick }: { onClick: OnClickCallback }) { window, DOM_EVENT.SELECTION_CHANGE, () => { - if (!selectionEmptyAtMouseDown || !isSelectionEmpty()) { + if (!selectionEmptyAtPointerDown || !isSelectionEmpty()) { hasSelectionChanged = true } }, @@ -39,7 +44,7 @@ export function listenActionEvents({ onClick }: { onClick: OnClickCallback }) { window, DOM_EVENT.CLICK, (clickEvent: MouseEvent) => { - if (isMouseEventOnElement(clickEvent)) { + if (isMouseEventOnElement(clickEvent) && onClick) { // Use a scoped variable to make sure the value is not changed by other clicks const userActivity = { selection: hasSelectionChanged, @@ -57,6 +62,7 @@ export function listenActionEvents({ onClick }: { onClick: OnClickCallback }) { event: clickEvent, getUserActivity: () => userActivity, }) + onClick = undefined } }, { capture: true } diff --git a/packages/rum-core/src/domain/rumEventsCollection/action/trackClickActions.ts b/packages/rum-core/src/domain/rumEventsCollection/action/trackClickActions.ts index 8a6c982200..026e1d8810 100644 --- a/packages/rum-core/src/domain/rumEventsCollection/action/trackClickActions.ts +++ b/packages/rum-core/src/domain/rumEventsCollection/action/trackClickActions.ts @@ -79,16 +79,8 @@ export function trackClickActions( lifeCycle.subscribe(LifeCycleEventType.VIEW_ENDED, stopClickChain) const { stop: stopActionEventsListener } = listenActionEvents({ - onClick: (onClickContext) => - processClick( - configuration, - lifeCycle, - domMutationObservable, - history, - stopObservable, - appendClickToClickChain, - onClickContext - ), + onPointerDown: () => + onPointerDown(configuration, lifeCycle, domMutationObservable, history, stopObservable, appendClickToClickChain), }) const actionContexts: ActionContexts = { @@ -121,74 +113,77 @@ export function trackClickActions( } } -function processClick( +function onPointerDown( configuration: RumConfiguration, lifeCycle: LifeCycle, domMutationObservable: Observable, history: ClickActionIdHistory, stopObservable: Observable, - appendClickToClickChain: (click: Click) => void, - { event, getUserActivity }: OnClickContext + appendClickToClickChain: (click: Click) => void ) { - if (!configuration.trackFrustrations && history.find()) { - // TODO: remove this in a future major version. To keep retrocompatibility, ignore any new - // action if another one is already occurring. - return - } + return { + onClick({ event, getUserActivity }: OnClickContext) { + if (!configuration.trackFrustrations && history.find()) { + // TODO: remove this in a future major version. To keep retrocompatibility, ignore any new + // action if another one is already occurring. + return + } - const clickActionBase = computeClickActionBase(event, configuration.actionNameAttribute) - if (!configuration.trackFrustrations && !clickActionBase.name) { - // TODO: remove this in a future major version. To keep retrocompatibility, ignore any action - // with a blank name - return - } + const clickActionBase = computeClickActionBase(event, configuration.actionNameAttribute) + if (!configuration.trackFrustrations && !clickActionBase.name) { + // TODO: remove this in a future major version. To keep retrocompatibility, ignore any action + // with a blank name + return + } - const click = newClick(lifeCycle, history, getUserActivity, clickActionBase) + const click = newClick(lifeCycle, history, getUserActivity, clickActionBase) - if (configuration.trackFrustrations) { - appendClickToClickChain(click) - } + if (configuration.trackFrustrations) { + appendClickToClickChain(click) + } - const { stop: stopWaitPageActivityEnd } = waitPageActivityEnd( - lifeCycle, - domMutationObservable, - configuration, - (pageActivityEndEvent) => { - if (pageActivityEndEvent.hadActivity && pageActivityEndEvent.end < clickActionBase.startClocks.timeStamp) { - // If the clock is looking weird, just discard the click - click.discard() - } else { - click.stop(pageActivityEndEvent.hadActivity ? pageActivityEndEvent.end : undefined) - - // Validate or discard the click only if we don't track frustrations. It'll be done when - // the click chain is finalized. - if (!configuration.trackFrustrations) { - if (!pageActivityEndEvent.hadActivity) { - // If we are not tracking frustrations, we should discard the click to keep backward - // compatibility. + const { stop: stopWaitPageActivityEnd } = waitPageActivityEnd( + lifeCycle, + domMutationObservable, + configuration, + (pageActivityEndEvent) => { + if (pageActivityEndEvent.hadActivity && pageActivityEndEvent.end < clickActionBase.startClocks.timeStamp) { + // If the clock is looking weird, just discard the click click.discard() } else { - click.validate() + click.stop(pageActivityEndEvent.hadActivity ? pageActivityEndEvent.end : undefined) + + // Validate or discard the click only if we don't track frustrations. It'll be done when + // the click chain is finalized. + if (!configuration.trackFrustrations) { + if (!pageActivityEndEvent.hadActivity) { + // If we are not tracking frustrations, we should discard the click to keep backward + // compatibility. + click.discard() + } else { + click.validate() + } + } } - } - } - }, - CLICK_ACTION_MAX_DURATION - ) + }, + CLICK_ACTION_MAX_DURATION + ) - const viewEndedSubscription = lifeCycle.subscribe(LifeCycleEventType.VIEW_ENDED, ({ endClocks }) => { - click.stop(endClocks.timeStamp) - }) + const viewEndedSubscription = lifeCycle.subscribe(LifeCycleEventType.VIEW_ENDED, ({ endClocks }) => { + click.stop(endClocks.timeStamp) + }) - const stopSubscription = stopObservable.subscribe(() => { - click.stop() - }) + const stopSubscription = stopObservable.subscribe(() => { + click.stop() + }) - click.stopObservable.subscribe(() => { - viewEndedSubscription.unsubscribe() - stopWaitPageActivityEnd() - stopSubscription.unsubscribe() - }) + click.stopObservable.subscribe(() => { + viewEndedSubscription.unsubscribe() + stopWaitPageActivityEnd() + stopSubscription.unsubscribe() + }) + }, + } } function computeClickActionBase(event: MouseEventOnElement, actionNameAttribute?: string) { From ddbfe25065269cc840fe44b2c6d83dbf650f6786 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Zugmeyer?= Date: Fri, 9 Sep 2022 18:31:26 +0200 Subject: [PATCH 06/14] =?UTF-8?q?=F0=9F=90=9B=20[RUMF-1368]=20use=20the=20?= =?UTF-8?q?pointerdown=20target=20to=20get=20the=20selectors=20and=20name?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../action/trackClickActions.ts | 64 +++++++++++-------- packages/rum-core/test/createFakeClick.ts | 3 +- 2 files changed, 40 insertions(+), 27 deletions(-) diff --git a/packages/rum-core/src/domain/rumEventsCollection/action/trackClickActions.ts b/packages/rum-core/src/domain/rumEventsCollection/action/trackClickActions.ts index 026e1d8810..18dd73909d 100644 --- a/packages/rum-core/src/domain/rumEventsCollection/action/trackClickActions.ts +++ b/packages/rum-core/src/domain/rumEventsCollection/action/trackClickActions.ts @@ -79,8 +79,16 @@ export function trackClickActions( lifeCycle.subscribe(LifeCycleEventType.VIEW_ENDED, stopClickChain) const { stop: stopActionEventsListener } = listenActionEvents({ - onPointerDown: () => - onPointerDown(configuration, lifeCycle, domMutationObservable, history, stopObservable, appendClickToClickChain), + onPointerDown: (pointerDownEvent) => + onPointerDown( + configuration, + lifeCycle, + domMutationObservable, + history, + stopObservable, + appendClickToClickChain, + pointerDownEvent + ), }) const actionContexts: ActionContexts = { @@ -119,24 +127,25 @@ function onPointerDown( domMutationObservable: Observable, history: ClickActionIdHistory, stopObservable: Observable, - appendClickToClickChain: (click: Click) => void + appendClickToClickChain: (click: Click) => void, + pointerDownEvent: MouseEventOnElement ) { - return { - onClick({ event, getUserActivity }: OnClickContext) { - if (!configuration.trackFrustrations && history.find()) { - // TODO: remove this in a future major version. To keep retrocompatibility, ignore any new - // action if another one is already occurring. - return - } + if (!configuration.trackFrustrations && history.find()) { + // TODO: remove this in a future major version. To keep retrocompatibility, ignore any new + // action if another one is already occurring. + return + } - const clickActionBase = computeClickActionBase(event, configuration.actionNameAttribute) - if (!configuration.trackFrustrations && !clickActionBase.name) { - // TODO: remove this in a future major version. To keep retrocompatibility, ignore any action - // with a blank name - return - } + const clickActionBase = computeClickActionBase(pointerDownEvent, configuration.actionNameAttribute) + if (!configuration.trackFrustrations && !clickActionBase.name) { + // TODO: remove this in a future major version. To keep retrocompatibility, ignore any action + // with a blank name + return + } - const click = newClick(lifeCycle, history, getUserActivity, clickActionBase) + return { + onClick({ event: clickEvent, getUserActivity }: OnClickContext) { + const click = newClick(lifeCycle, history, getUserActivity, clickActionBase, clickEvent) if (configuration.trackFrustrations) { appendClickToClickChain(click) @@ -147,7 +156,7 @@ function onPointerDown( domMutationObservable, configuration, (pageActivityEndEvent) => { - if (pageActivityEndEvent.hadActivity && pageActivityEndEvent.end < clickActionBase.startClocks.timeStamp) { + if (pageActivityEndEvent.hadActivity && pageActivityEndEvent.end < click.startClocks.timeStamp) { // If the clock is looking weird, just discard the click click.discard() } else { @@ -211,8 +220,6 @@ function computeClickActionBase(event: MouseEventOnElement, actionNameAttribute? target, position, name: getActionNameFromElement(event.target, actionNameAttribute), - event, - startClocks: clocksNow(), } } @@ -231,10 +238,12 @@ function newClick( lifeCycle: LifeCycle, history: ClickActionIdHistory, getUserActivity: OnClickContext['getUserActivity'], - clickActionBase: Pick + clickActionBase: Pick, + clickEvent: MouseEventOnElement ) { const id = generateUUID() - const historyEntry = history.add(id, clickActionBase.startClocks.relative) + const startClocks = clocksNow() + const historyEntry = history.add(id, startClocks.relative) const eventCountsSubscription = trackEventCounts(lifeCycle) let status = ClickStatus.ONGOING let activityEndTime: undefined | TimeStamp @@ -257,7 +266,7 @@ function newClick( } return { - event: clickActionBase.event, + event: clickEvent, stop, stopObservable, @@ -271,10 +280,11 @@ function newClick( addFrustration: (frustrationType: FrustrationType) => { frustrationTypes.push(frustrationType) }, + startClocks, isStopped: () => status === ClickStatus.STOPPED || status === ClickStatus.FINALIZED, - clone: () => newClick(lifeCycle, history, getUserActivity, clickActionBase), + clone: () => newClick(lifeCycle, history, getUserActivity, clickActionBase, clickEvent), validate: (domEvents?: Event[]) => { stop() @@ -286,7 +296,8 @@ function newClick( const clickAction: ClickAction = assign( { type: ActionType.CLICK as const, - duration: activityEndTime && elapsed(clickActionBase.startClocks.timeStamp, activityEndTime), + duration: activityEndTime && elapsed(startClocks.timeStamp, activityEndTime), + startClocks, id, frustrationTypes, counts: { @@ -294,7 +305,8 @@ function newClick( errorCount, longTaskCount, }, - events: domEvents ?? [clickActionBase.event], + events: domEvents ?? [clickEvent], + event: clickEvent, }, clickActionBase ) diff --git a/packages/rum-core/test/createFakeClick.ts b/packages/rum-core/test/createFakeClick.ts index d6d7879971..2c899a340d 100644 --- a/packages/rum-core/test/createFakeClick.ts +++ b/packages/rum-core/test/createFakeClick.ts @@ -1,4 +1,4 @@ -import { Observable, timeStampNow } from '@datadog/browser-core' +import { clocksNow, Observable, timeStampNow } from '@datadog/browser-core' import { createNewEvent } from '../../core/test/specHelper' import type { Click } from '../src/domain/rumEventsCollection/action/trackClickActions' @@ -31,6 +31,7 @@ export function createFakeClick({ }, discard: jasmine.createSpy(), validate: jasmine.createSpy(), + startClocks: clocksNow(), hasError, hasPageActivity, getUserActivity: () => ({ From 5d3342efa7bd8160f0c9d3aac0d33997e2e449e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Zugmeyer?= Date: Tue, 13 Sep 2022 12:09:13 +0200 Subject: [PATCH 07/14] =?UTF-8?q?=E2=9C=85=20[RUMF-1368]=20add=20e2e=20tes?= =?UTF-8?q?t?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/e2e/scenario/rum/actions.scenario.ts | 28 +++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/test/e2e/scenario/rum/actions.scenario.ts b/test/e2e/scenario/rum/actions.scenario.ts index 350adb1fe7..7e3f130d30 100644 --- a/test/e2e/scenario/rum/actions.scenario.ts +++ b/test/e2e/scenario/rum/actions.scenario.ts @@ -61,6 +61,34 @@ describe('action collection', () => { ) }) + createTest('compute action target information before the UI changes') + .withRum({ trackFrustrations: true, enableExperimentalFeatures: ['clickmap'] }) + .withBody( + html` + + + ` + ) + .run(async ({ serverEvents }) => { + const button = await $('button') + await button.click() + await flushEvents() + const actionEvents = serverEvents.rumActions + + expect(actionEvents.length).toBe(1) + expect(actionEvents[0].action?.target?.name).toBe('click me') + expect(actionEvents[0]._dd.action?.target?.selector).toBe('BODY>BUTTON') + }) + createTest('associate a request to its action') .withRum({ trackInteractions: true }) .withBody( From e50b677e4b1f09686395c9bfd9e0adcc9cb1a8c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Zugmeyer?= Date: Tue, 13 Sep 2022 14:26:09 +0200 Subject: [PATCH 08/14] =?UTF-8?q?=E2=9C=85=20split=20e2e=20test=20and=20di?= =?UTF-8?q?sable=20the=20unsupported=20part=20in=20Firefox?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/e2e/scenario/rum/actions.scenario.ts | 36 +++++++++++++++++++---- 1 file changed, 31 insertions(+), 5 deletions(-) diff --git a/test/e2e/scenario/rum/actions.scenario.ts b/test/e2e/scenario/rum/actions.scenario.ts index 7e3f130d30..34d331e9ae 100644 --- a/test/e2e/scenario/rum/actions.scenario.ts +++ b/test/e2e/scenario/rum/actions.scenario.ts @@ -1,4 +1,4 @@ -import { withBrowserLogs } from '../../lib/helpers/browser' +import { getBrowserName, withBrowserLogs } from '../../lib/helpers/browser' import { createTest, flushEvents, html, waitForServersIdle } from '../../lib/framework' describe('action collection', () => { @@ -69,10 +69,7 @@ describe('action collection', () => { @@ -89,6 +86,35 @@ describe('action collection', () => { expect(actionEvents[0]._dd.action?.target?.selector).toBe('BODY>BUTTON') }) + // When the target element changes between mousedown and mousedown, Firefox does not dispatch a + // click event. Skip this test. + if (getBrowserName() !== 'firefox') { + createTest('does not report a click on the body when the target element changes between mouseup and mousedown') + .withRum({ trackFrustrations: true, enableExperimentalFeatures: ['clickmap'] }) + .withBody( + html` + + + ` + ) + .run(async ({ serverEvents }) => { + const button = await $('button') + await button.click() + await flushEvents() + const actionEvents = serverEvents.rumActions + + expect(actionEvents.length).toBe(1) + expect(actionEvents[0].action?.target?.name).toBe('click me') + }) + } + createTest('associate a request to its action') .withRum({ trackInteractions: true }) .withBody( From 3e8e8d1caa922cbebcabadb88afbb4f50a1c2f16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Zugmeyer?= Date: Tue, 13 Sep 2022 15:30:55 +0200 Subject: [PATCH 09/14] =?UTF-8?q?=E2=9C=85=20fix=20e2e=20test=20on=20Safar?= =?UTF-8?q?i?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/e2e/scenario/rum/actions.scenario.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/e2e/scenario/rum/actions.scenario.ts b/test/e2e/scenario/rum/actions.scenario.ts index 34d331e9ae..40623f566b 100644 --- a/test/e2e/scenario/rum/actions.scenario.ts +++ b/test/e2e/scenario/rum/actions.scenario.ts @@ -69,7 +69,8 @@ describe('action collection', () => { From 18d69aa58737edbdd3855c82fd505aab5999ba4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Zugmeyer?= Date: Wed, 14 Sep 2022 16:55:42 +0200 Subject: [PATCH 10/14] =?UTF-8?q?=F0=9F=91=8C=20remove=20remaining=20restr?= =?UTF-8?q?ict-plus-operands?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/core/src/tools/timeUtils.ts | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/packages/core/src/tools/timeUtils.ts b/packages/core/src/tools/timeUtils.ts index e704928175..a43f7d6ec0 100644 --- a/packages/core/src/tools/timeUtils.ts +++ b/packages/core/src/tools/timeUtils.ts @@ -11,18 +11,16 @@ export function relativeToClocks(relative: RelativeTime) { } function getCorrectedTimeStamp(relativeTime: RelativeTime) { - const correctedOrigin = dateNow() - performance.now() + const correctedOrigin = (dateNow() - performance.now()) as TimeStamp // apply correction only for positive drift if (correctedOrigin > getNavigationStart()) { - // eslint-disable-next-line @typescript-eslint/restrict-plus-operands - return Math.round(correctedOrigin + relativeTime) as TimeStamp + return Math.round(addDuration(correctedOrigin, relativeTime)) as TimeStamp } return getTimeStamp(relativeTime) } export function currentDrift() { - // eslint-disable-next-line @typescript-eslint/restrict-plus-operands - return Math.round(dateNow() - (getNavigationStart() + performance.now())) + return Math.round(dateNow() - addDuration(getNavigationStart(), performance.now() as Duration)) } export function toServerDuration(duration: Duration): ServerDuration @@ -84,8 +82,7 @@ export function getRelativeTime(timestamp: TimeStamp) { } export function getTimeStamp(relativeTime: RelativeTime) { - // eslint-disable-next-line @typescript-eslint/restrict-plus-operands - return Math.round(getNavigationStart() + relativeTime) as TimeStamp + return Math.round(addDuration(getNavigationStart(), relativeTime)) as TimeStamp } export function looksLikeRelativeTime(time: RelativeTime | TimeStamp): time is RelativeTime { From d0a61ff577eec50230d82a171307a1a6ad2a6aa6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Zugmeyer?= Date: Wed, 14 Sep 2022 16:57:10 +0200 Subject: [PATCH 11/14] =?UTF-8?q?=F0=9F=91=8C=20fix=20e2e=20test=20name?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/e2e/scenario/rum/actions.scenario.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/e2e/scenario/rum/actions.scenario.ts b/test/e2e/scenario/rum/actions.scenario.ts index 40623f566b..6fd4de5696 100644 --- a/test/e2e/scenario/rum/actions.scenario.ts +++ b/test/e2e/scenario/rum/actions.scenario.ts @@ -87,10 +87,10 @@ describe('action collection', () => { expect(actionEvents[0]._dd.action?.target?.selector).toBe('BODY>BUTTON') }) - // When the target element changes between mousedown and mousedown, Firefox does not dispatch a + // When the target element changes between mousedown and mouseup, Firefox does not dispatch a // click event. Skip this test. if (getBrowserName() !== 'firefox') { - createTest('does not report a click on the body when the target element changes between mouseup and mousedown') + createTest('does not report a click on the body when the target element changes between mousedown and mouseup') .withRum({ trackFrustrations: true, enableExperimentalFeatures: ['clickmap'] }) .withBody( html` From 5f4e7dc5382dcbb97956d74b637fdc68c35d9a62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Zugmeyer?= Date: Thu, 15 Sep 2022 11:41:45 +0200 Subject: [PATCH 12/14] =?UTF-8?q?=F0=9F=91=8C=20split=20onClick=20and=20on?= =?UTF-8?q?PointerDown=20processing?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../action/listenActionEvents.spec.ts | 47 ++++--- .../action/listenActionEvents.ts | 24 ++-- .../action/trackClickActions.ts | 121 ++++++++++-------- 3 files changed, 108 insertions(+), 84 deletions(-) diff --git a/packages/rum-core/src/domain/rumEventsCollection/action/listenActionEvents.spec.ts b/packages/rum-core/src/domain/rumEventsCollection/action/listenActionEvents.spec.ts index 1af2e3967d..839a6cb3b9 100644 --- a/packages/rum-core/src/domain/rumEventsCollection/action/listenActionEvents.spec.ts +++ b/packages/rum-core/src/domain/rumEventsCollection/action/listenActionEvents.spec.ts @@ -1,40 +1,57 @@ import type { Clock } from '../../../../../core/test/specHelper' import { createNewEvent, mockClock } from '../../../../../core/test/specHelper' -import type { OnClickCallback, OnPointerDownCallback } from './listenActionEvents' +import type { ActionEventsHooks } from './listenActionEvents' import { listenActionEvents } from './listenActionEvents' describe('listenActionEvents', () => { - let onClickSpy: jasmine.Spy - let onPointerDownSpy: jasmine.Spy + let actionEventsHooks: { + onClick: jasmine.Spy['onClick']> + onPointerDown: jasmine.Spy['onPointerDown']> + } let stopListenEvents: () => void beforeEach(() => { - onClickSpy = jasmine.createSpy() - onPointerDownSpy = jasmine.createSpy().and.returnValue({ onClick: onClickSpy }) - ;({ stop: stopListenEvents } = listenActionEvents({ onPointerDown: onPointerDownSpy })) + actionEventsHooks = { + onClick: jasmine.createSpy(), + onPointerDown: jasmine.createSpy().and.returnValue({}), + } + ;({ stop: stopListenEvents } = listenActionEvents(actionEventsHooks)) }) afterEach(() => { stopListenEvents() }) + it('listen to mousedown events', () => { + emulateClick() + expect(actionEventsHooks.onPointerDown).toHaveBeenCalledOnceWith(jasmine.objectContaining({ type: 'pointerdown' })) + }) + it('listen to click events', () => { emulateClick() - expect(onClickSpy).toHaveBeenCalledOnceWith({ - event: jasmine.objectContaining({ type: 'click' }), - getUserActivity: jasmine.any(Function), - }) + expect(actionEventsHooks.onClick).toHaveBeenCalledOnceWith( + {}, + jasmine.objectContaining({ type: 'click' }), + jasmine.any(Function) + ) }) it('aborts click lifecycle if the pointerdown event occurs on a non-element', () => { emulateClick({ target: document.createTextNode('foo') }) - expect(onClickSpy).not.toHaveBeenCalled() + expect(actionEventsHooks.onPointerDown).not.toHaveBeenCalled() }) it('can abort click lifecycle by returning undefined from the onPointerDown callback', () => { - onPointerDownSpy.and.returnValue(undefined) + actionEventsHooks.onPointerDown.and.returnValue(undefined) + emulateClick() + expect(actionEventsHooks.onClick).not.toHaveBeenCalled() + }) + + it('passes the context created in onPointerDown to onClick', () => { + const context = {} + actionEventsHooks.onPointerDown.and.returnValue(context) emulateClick() - expect(onClickSpy).not.toHaveBeenCalled() + expect(actionEventsHooks.onClick.calls.mostRecent().args[0]).toBe(context) }) describe('selection change', () => { @@ -106,7 +123,7 @@ describe('listenActionEvents', () => { }) function hasSelectionChanged() { - return onClickSpy.calls.mostRecent().args[0].getUserActivity().selection + return actionEventsHooks.onClick.calls.mostRecent().args[2]().selection } function emulateNodeSelection( @@ -171,7 +188,7 @@ describe('listenActionEvents', () => { window.dispatchEvent(createNewEvent('input')) } function hasInputUserActivity() { - return onClickSpy.calls.mostRecent().args[0].getUserActivity().input + return actionEventsHooks.onClick.calls.mostRecent().args[2]().input } }) diff --git a/packages/rum-core/src/domain/rumEventsCollection/action/listenActionEvents.ts b/packages/rum-core/src/domain/rumEventsCollection/action/listenActionEvents.ts index 673d76e011..1a259de74c 100644 --- a/packages/rum-core/src/domain/rumEventsCollection/action/listenActionEvents.ts +++ b/packages/rum-core/src/domain/rumEventsCollection/action/listenActionEvents.ts @@ -2,18 +2,17 @@ import { addEventListener, DOM_EVENT, monitor } from '@datadog/browser-core' export type MouseEventOnElement = MouseEvent & { target: Element } -export type OnPointerDownCallback = (event: MouseEventOnElement) => { onClick: OnClickCallback } | undefined -export type OnClickCallback = (context: OnClickContext) => void -export interface OnClickContext { - event: MouseEventOnElement - getUserActivity(): { selection: boolean; input: boolean } +export type GetUserActivity = () => { selection: boolean; input: boolean } +export interface ActionEventsHooks { + onPointerDown: (event: MouseEventOnElement) => ClickContext | undefined + onClick: (context: ClickContext, event: MouseEventOnElement, getUserActivity: GetUserActivity) => void } -export function listenActionEvents({ onPointerDown }: { onPointerDown: OnPointerDownCallback }) { +export function listenActionEvents({ onPointerDown, onClick }: ActionEventsHooks) { let hasSelectionChanged = false let selectionEmptyAtPointerDown: boolean let hasInputChanged = false - let onClick: ((context: OnClickContext) => void) | undefined + let clickContext: ClickContext | undefined const listeners = [ addEventListener( @@ -23,7 +22,7 @@ export function listenActionEvents({ onPointerDown }: { onPointerDown: OnPointer hasSelectionChanged = false selectionEmptyAtPointerDown = isSelectionEmpty() if (isMouseEventOnElement(event)) { - onClick = onPointerDown(event)?.onClick + clickContext = onPointerDown(event) } }, { capture: true } @@ -44,7 +43,7 @@ export function listenActionEvents({ onPointerDown }: { onPointerDown: OnPointer window, DOM_EVENT.CLICK, (clickEvent: MouseEvent) => { - if (isMouseEventOnElement(clickEvent) && onClick) { + if (isMouseEventOnElement(clickEvent) && clickContext) { // Use a scoped variable to make sure the value is not changed by other clicks const userActivity = { selection: hasSelectionChanged, @@ -58,11 +57,8 @@ export function listenActionEvents({ onPointerDown }: { onPointerDown: OnPointer ) } - onClick({ - event: clickEvent, - getUserActivity: () => userActivity, - }) - onClick = undefined + onClick(clickContext, clickEvent, () => userActivity) + clickContext = undefined } }, { capture: true } diff --git a/packages/rum-core/src/domain/rumEventsCollection/action/trackClickActions.ts b/packages/rum-core/src/domain/rumEventsCollection/action/trackClickActions.ts index 18dd73909d..2661c170ee 100644 --- a/packages/rum-core/src/domain/rumEventsCollection/action/trackClickActions.ts +++ b/packages/rum-core/src/domain/rumEventsCollection/action/trackClickActions.ts @@ -23,7 +23,7 @@ import type { ClickChain } from './clickChain' import { createClickChain } from './clickChain' import { getActionNameFromElement } from './getActionNameFromElement' import { getSelectorsFromElement } from './getSelectorsFromElement' -import type { MouseEventOnElement, OnClickContext } from './listenActionEvents' +import type { MouseEventOnElement, GetUserActivity } from './listenActionEvents' import { listenActionEvents } from './listenActionEvents' import { computeFrustration } from './computeFrustration' @@ -78,16 +78,19 @@ export function trackClickActions( lifeCycle.subscribe(LifeCycleEventType.BEFORE_UNLOAD, stopClickChain) lifeCycle.subscribe(LifeCycleEventType.VIEW_ENDED, stopClickChain) - const { stop: stopActionEventsListener } = listenActionEvents({ - onPointerDown: (pointerDownEvent) => - onPointerDown( + const { stop: stopActionEventsListener } = listenActionEvents({ + onPointerDown: (pointerDownEvent) => onPointerDown(configuration, history, pointerDownEvent), + onClick: (clickActionBase, clickEvent, getUserActivity) => + onClick( configuration, lifeCycle, domMutationObservable, history, stopObservable, appendClickToClickChain, - pointerDownEvent + clickActionBase, + clickEvent, + getUserActivity ), }) @@ -123,11 +126,7 @@ export function trackClickActions( function onPointerDown( configuration: RumConfiguration, - lifeCycle: LifeCycle, - domMutationObservable: Observable, history: ClickActionIdHistory, - stopObservable: Observable, - appendClickToClickChain: (click: Click) => void, pointerDownEvent: MouseEventOnElement ) { if (!configuration.trackFrustrations && history.find()) { @@ -143,59 +142,71 @@ function onPointerDown( return } - return { - onClick({ event: clickEvent, getUserActivity }: OnClickContext) { - const click = newClick(lifeCycle, history, getUserActivity, clickActionBase, clickEvent) + return clickActionBase +} - if (configuration.trackFrustrations) { - appendClickToClickChain(click) - } +function onClick( + configuration: RumConfiguration, + lifeCycle: LifeCycle, + domMutationObservable: Observable, + history: ClickActionIdHistory, + stopObservable: Observable, + appendClickToClickChain: (click: Click) => void, + clickActionBase: ClickActionBase, + clickEvent: MouseEventOnElement, + getUserActivity: GetUserActivity +) { + const click = newClick(lifeCycle, history, getUserActivity, clickActionBase, clickEvent) - const { stop: stopWaitPageActivityEnd } = waitPageActivityEnd( - lifeCycle, - domMutationObservable, - configuration, - (pageActivityEndEvent) => { - if (pageActivityEndEvent.hadActivity && pageActivityEndEvent.end < click.startClocks.timeStamp) { - // If the clock is looking weird, just discard the click + if (configuration.trackFrustrations) { + appendClickToClickChain(click) + } + + const { stop: stopWaitPageActivityEnd } = waitPageActivityEnd( + lifeCycle, + domMutationObservable, + configuration, + (pageActivityEndEvent) => { + if (pageActivityEndEvent.hadActivity && pageActivityEndEvent.end < click.startClocks.timeStamp) { + // If the clock is looking weird, just discard the click + click.discard() + } else { + click.stop(pageActivityEndEvent.hadActivity ? pageActivityEndEvent.end : undefined) + + // Validate or discard the click only if we don't track frustrations. It'll be done when + // the click chain is finalized. + if (!configuration.trackFrustrations) { + if (!pageActivityEndEvent.hadActivity) { + // If we are not tracking frustrations, we should discard the click to keep backward + // compatibility. click.discard() } else { - click.stop(pageActivityEndEvent.hadActivity ? pageActivityEndEvent.end : undefined) - - // Validate or discard the click only if we don't track frustrations. It'll be done when - // the click chain is finalized. - if (!configuration.trackFrustrations) { - if (!pageActivityEndEvent.hadActivity) { - // If we are not tracking frustrations, we should discard the click to keep backward - // compatibility. - click.discard() - } else { - click.validate() - } - } + click.validate() } - }, - CLICK_ACTION_MAX_DURATION - ) + } + } + }, + CLICK_ACTION_MAX_DURATION + ) - const viewEndedSubscription = lifeCycle.subscribe(LifeCycleEventType.VIEW_ENDED, ({ endClocks }) => { - click.stop(endClocks.timeStamp) - }) + const viewEndedSubscription = lifeCycle.subscribe(LifeCycleEventType.VIEW_ENDED, ({ endClocks }) => { + click.stop(endClocks.timeStamp) + }) - const stopSubscription = stopObservable.subscribe(() => { - click.stop() - }) + const stopSubscription = stopObservable.subscribe(() => { + click.stop() + }) - click.stopObservable.subscribe(() => { - viewEndedSubscription.unsubscribe() - stopWaitPageActivityEnd() - stopSubscription.unsubscribe() - }) - }, - } + click.stopObservable.subscribe(() => { + viewEndedSubscription.unsubscribe() + stopWaitPageActivityEnd() + stopSubscription.unsubscribe() + }) } -function computeClickActionBase(event: MouseEventOnElement, actionNameAttribute?: string) { +type ClickActionBase = Pick + +function computeClickActionBase(event: MouseEventOnElement, actionNameAttribute?: string): ClickActionBase { let target: ClickAction['target'] let position: ClickAction['position'] @@ -216,7 +227,7 @@ function computeClickActionBase(event: MouseEventOnElement, actionNameAttribute? } return { - type: 'click', + type: ActionType.CLICK, target, position, name: getActionNameFromElement(event.target, actionNameAttribute), @@ -237,8 +248,8 @@ export type Click = ReturnType function newClick( lifeCycle: LifeCycle, history: ClickActionIdHistory, - getUserActivity: OnClickContext['getUserActivity'], - clickActionBase: Pick, + getUserActivity: GetUserActivity, + clickActionBase: ClickActionBase, clickEvent: MouseEventOnElement ) { const id = generateUUID() From 14000b96d9154d5d638558582978b122245d0bd5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Zugmeyer?= Date: Thu, 15 Sep 2022 12:05:59 +0200 Subject: [PATCH 13/14] =?UTF-8?q?=F0=9F=91=8C=E2=9C=85=20add=20a=20test=20?= =?UTF-8?q?to=20make=20sure=20we=20don't=20reuse=20pointerdown=20context?= =?UTF-8?q?=20in=20two=20clicks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../action/listenActionEvents.spec.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/packages/rum-core/src/domain/rumEventsCollection/action/listenActionEvents.spec.ts b/packages/rum-core/src/domain/rumEventsCollection/action/listenActionEvents.spec.ts index 839a6cb3b9..0651d7441b 100644 --- a/packages/rum-core/src/domain/rumEventsCollection/action/listenActionEvents.spec.ts +++ b/packages/rum-core/src/domain/rumEventsCollection/action/listenActionEvents.spec.ts @@ -54,6 +54,15 @@ describe('listenActionEvents', () => { expect(actionEventsHooks.onClick.calls.mostRecent().args[0]).toBe(context) }) + it('ignore "click" events if no "pointerdown" event happened since the previous "click" event', () => { + emulateClick() + actionEventsHooks.onClick.calls.reset() + + window.dispatchEvent(createNewEvent('click', { target: document.body })) + + expect(actionEventsHooks.onClick).not.toHaveBeenCalled() + }) + describe('selection change', () => { let text: Text From ba4acb62921ac89b51b9bb3a30dedf1f053f0cea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Zugmeyer?= Date: Thu, 15 Sep 2022 14:09:29 +0200 Subject: [PATCH 14/14] =?UTF-8?q?=F0=9F=91=8C=20light=20renaming?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../rumEventsCollection/action/listenActionEvents.spec.ts | 2 +- .../rumEventsCollection/action/trackClickActions.ts | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/rum-core/src/domain/rumEventsCollection/action/listenActionEvents.spec.ts b/packages/rum-core/src/domain/rumEventsCollection/action/listenActionEvents.spec.ts index 0651d7441b..10728543c1 100644 --- a/packages/rum-core/src/domain/rumEventsCollection/action/listenActionEvents.spec.ts +++ b/packages/rum-core/src/domain/rumEventsCollection/action/listenActionEvents.spec.ts @@ -22,7 +22,7 @@ describe('listenActionEvents', () => { stopListenEvents() }) - it('listen to mousedown events', () => { + it('listen to pointerdown events', () => { emulateClick() expect(actionEventsHooks.onPointerDown).toHaveBeenCalledOnceWith(jasmine.objectContaining({ type: 'pointerdown' })) }) diff --git a/packages/rum-core/src/domain/rumEventsCollection/action/trackClickActions.ts b/packages/rum-core/src/domain/rumEventsCollection/action/trackClickActions.ts index 2661c170ee..3cd4b9b344 100644 --- a/packages/rum-core/src/domain/rumEventsCollection/action/trackClickActions.ts +++ b/packages/rum-core/src/domain/rumEventsCollection/action/trackClickActions.ts @@ -79,9 +79,9 @@ export function trackClickActions( lifeCycle.subscribe(LifeCycleEventType.VIEW_ENDED, stopClickChain) const { stop: stopActionEventsListener } = listenActionEvents({ - onPointerDown: (pointerDownEvent) => onPointerDown(configuration, history, pointerDownEvent), + onPointerDown: (pointerDownEvent) => processPointerDown(configuration, history, pointerDownEvent), onClick: (clickActionBase, clickEvent, getUserActivity) => - onClick( + processClick( configuration, lifeCycle, domMutationObservable, @@ -124,7 +124,7 @@ export function trackClickActions( } } -function onPointerDown( +function processPointerDown( configuration: RumConfiguration, history: ClickActionIdHistory, pointerDownEvent: MouseEventOnElement @@ -145,7 +145,7 @@ function onPointerDown( return clickActionBase } -function onClick( +function processClick( configuration: RumConfiguration, lifeCycle: LifeCycle, domMutationObservable: Observable,