Skip to content

Commit

Permalink
feat: debounce window user actions (#1326)
Browse files Browse the repository at this point in the history
  • Loading branch information
metal-messiah authored Jan 23, 2025
1 parent 32a6b56 commit dec8f2d
Show file tree
Hide file tree
Showing 2 changed files with 40 additions and 2 deletions.
7 changes: 5 additions & 2 deletions src/features/generic_events/instrument/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import { globalScope, isBrowserScope } from '../../../common/constants/runtime'
import { handle } from '../../../common/event-emitter/handle'
import { windowAddEventListener } from '../../../common/event-listener/event-listener-opts'
import { debounce } from '../../../common/util/invoke'
import { InstrumentBase } from '../../utils/instrument-base'
import { FEATURE_NAME, OBSERVED_EVENTS, OBSERVED_WINDOW_EVENTS } from '../constants'

Expand All @@ -26,8 +27,10 @@ export class Instrument extends InstrumentBase {
OBSERVED_EVENTS.forEach(eventType =>
windowAddEventListener(eventType, (evt) => handle('ua', [evt], undefined, this.featureName, this.ee), true)
)
OBSERVED_WINDOW_EVENTS.forEach(eventType =>
windowAddEventListener(eventType, (evt) => handle('ua', [evt], undefined, this.featureName, this.ee))
OBSERVED_WINDOW_EVENTS.forEach(eventType => {
const debounceHandler = debounce((evt) => { handle('ua', [evt], undefined, this.featureName, this.ee) }, 500, { leading: true })
windowAddEventListener(eventType, debounceHandler)
}
// Capture is not used here so that we don't get element focus/blur events, only the window's as they do not bubble. They are also not cancellable, so no worries about being front of line.
)
}
Expand Down
35 changes: 35 additions & 0 deletions tests/specs/ins/harvesting.e2e.js
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,41 @@ describe('ins harvesting', () => {
})
})

it('should only report duplicative focus and blur events once', async () => {
const testUrl = await browser.testHandle.assetURL('user-actions.html', getInsInit({ user_actions: { enabled: true } }))
await browser.url(testUrl).then(() => browser.pause(2000))

const [insHarvests] = await Promise.all([
insightsCapture.waitForResult({ timeout: 5000 }),
browser.execute(function () {
let i = 0; while (i++ < 10) {
window.dispatchEvent(new Event('focus'))
window.dispatchEvent(new Event('blur'))
}
}).then(() => $('body').click()) // stop aggregating the blur events
])

const userActionsHarvest = insHarvests.flatMap(harvest => harvest.request.body.ins) // firefox sends a window focus event on load, so we may end up with 2 harvests
const focusEvents = userActionsHarvest.filter(ua => ua.action === 'focus')
const blurEvents = userActionsHarvest.filter(ua => ua.action === 'blur')

// firefox generates a focus event when the page loads, which is reported within the time gap between the page load and the first user action. This
// leads to two focus events for firefox and one for the others, but very inconsistently since it could be triggered in the time it takes to debounce
const isFirefox = browserMatch(onlyFirefox)
if (isFirefox) {
expect(focusEvents.length).toBeLessThanOrEqual(2)
expect(JSON.parse(focusEvents[0].actionMs).length).toBeLessThanOrEqual(2)
expect(focusEvents[0].actionCount).toBeLessThanOrEqual(2)
} else {
expect(focusEvents.length).toEqual(1)
expect(JSON.parse(focusEvents[0].actionMs).length).toEqual(1)
expect(focusEvents[0].actionCount).toEqual(1)
}
expect(blurEvents.length).toEqual(1)
expect(JSON.parse(blurEvents[0].actionMs).length).toEqual(1)
expect(blurEvents[0].actionCount).toEqual(1)
})

;[
[getInsInit({ performance: { capture_marks: true } }), 'enabled'],
[getInsInit({ performance: { capture_marks: false }, feature_flags: [FEATURE_FLAGS.MARKS] }), 'feature flag']
Expand Down

0 comments on commit dec8f2d

Please sign in to comment.