Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

⚗[REPLAY-341] Add VisualViewport tracking (Pinch Zoom) #1118

Merged
merged 40 commits into from
Nov 9, 2021
Merged
Show file tree
Hide file tree
Changes from 29 commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
a7f9412
Adds VisualViewport record as incremental record
jagracey Sep 29, 2021
f10520d
visual viewport normalizer implementation + test
jagracey Oct 4, 2021
54ec328
scrollX/Y fallback
jagracey Oct 4, 2021
d809950
Safari not handling native scrollTo
jagracey Oct 4, 2021
20bd820
Add FF to scrollX/Y()
jagracey Oct 6, 2021
84ff1ff
Implement cancel throttle
jagracey Oct 7, 2021
e3d75ac
UA update
jagracey Oct 8, 2021
0e48dba
Adds VisualViewport record as incremental record
jagracey Sep 29, 2021
ea98284
visual viewport normalizer implementation + test
jagracey Oct 4, 2021
c86966b
scrollX/Y fallback
jagracey Oct 4, 2021
88ba4c4
Safari not handling native scrollTo
jagracey Oct 4, 2021
88094ff
Add FF to scrollX/Y()
jagracey Oct 6, 2021
1102a9d
Implement cancel throttle
jagracey Oct 7, 2021
9c47a1f
UA update
jagracey Oct 8, 2021
60f746e
Merge branch 'main' into john.gracey/pinch-zoom-support
jagracey Oct 8, 2021
1277f77
Pinch Zoom E2E test
jagracey Oct 11, 2021
bcc880d
Merge branch 'main' into john.gracey/pinch-zoom-support
jagracey Oct 11, 2021
7348447
Merge branch 'john.gracey/pinch-zoom-support' of github.com:DataDog/b…
jagracey Oct 11, 2021
40f560d
Move window.* ref into browserExec
jagracey Oct 11, 2021
c4781f3
Debug E2E test
jagracey Oct 11, 2021
638a2d1
add Viewport tests
jagracey Oct 18, 2021
c17e917
flakiness: delay ms
jagracey Oct 19, 2021
829b122
linux device: logging
jagracey Oct 19, 2021
12cf9e1
ignore linux device due to bad spec
jagracey Oct 19, 2021
12532cc
remove console logs
jagracey Oct 19, 2021
797de3f
LGTM
jagracey Oct 19, 2021
610d255
PR review fixes
jagracey Oct 20, 2021
04ff091
Merge branch 'main' into john.gracey/pinch-zoom-support
jagracey Oct 20, 2021
11f2693
PR review fixes
jagracey Oct 20, 2021
d6dbbc6
Split file
jagracey Oct 22, 2021
ace7835
Add data prop
jagracey Oct 22, 2021
cf055e8
benchmark flakiness w/o sleep
jagracey Oct 27, 2021
d750494
PR review
jagracey Oct 29, 2021
a349dfd
remove meta tests
jagracey Nov 3, 2021
a91e14d
renaming vars
jagracey Nov 4, 2021
8e079a5
PR review
jagracey Nov 5, 2021
12f8f5a
PR review
jagracey Nov 8, 2021
ae5a912
merge main
jagracey Nov 8, 2021
3716aa1
merge fix
jagracey Nov 8, 2021
dc63c85
Merge branch 'main' into john.gracey/pinch-zoom-support
jagracey Nov 8, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 78 additions & 10 deletions packages/rum/src/domain/record/observer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import {
addEventListener,
includes,
DefaultPrivacyLevel,
isExperimentalFeatureEnabled,
noop,
} from '@datadog/browser-core'
import { NodePrivacyLevel } from '../../constants'
import { getNodePrivacyLevel, shouldMaskNode } from './privacy'
Expand All @@ -28,12 +30,25 @@ import {
ScrollCallback,
StyleSheetRuleCallback,
ViewportResizeCallback,
VisualViewportResizeCallback,
MousePosition,
MouseInteractionParam,
} from './types'
import { forEach, getWindowHeight, getWindowWidth, hookSetter, isTouchEvent } from './utils'
import { forEach, hookSetter, isTouchEvent } from './utils'
import { startMutationObserver, MutationController } from './mutationObserver'

import {
getVisualViewport,
getWindowHeight,
getWindowWidth,
getScrollX,
getScrollY,
convertMouseEventToLayoutCoordinates,
} from './viewports'

const MOUSE_MOVE_OBSERVER_THRESHOLD = 50
const SCROLL_OBSERVER_THRESHOLD = 100
const VISUAL_VIEWPORT_OBSERVER_THRESHOLD = 200

export function initObservers(o: ObserverParam): ListenerHandler {
const mutationHandler = initMutationObserver(o.mutationController, o.mutationCb, o.defaultPrivacyLevel)
Expand All @@ -46,6 +61,10 @@ export function initObservers(o: ObserverParam): ListenerHandler {
const styleSheetObserver = initStyleSheetObserver(o.styleSheetRuleCb)
const focusHandler = initFocusObserver(o.focusCb)

const visualViewportResizeHandler = isExperimentalFeatureEnabled('visualviewport')
? initVisualViewportResizeObserver(o.visualViewportResizeCb)
: noop

return () => {
mutationHandler()
mousemoveHandler()
Expand All @@ -56,6 +75,7 @@ export function initObservers(o: ObserverParam): ListenerHandler {
mediaInteractionHandler()
styleSheetObserver()
focusHandler()
visualViewportResizeHandler()
}
}

Expand All @@ -73,12 +93,17 @@ function initMoveObserver(cb: MousemoveCallBack): ListenerHandler {
const target = event.target as Node
if (hasSerializedNode(target)) {
const { clientX, clientY } = isTouchEvent(event) ? event.changedTouches[0] : event
const position = {
const position: MousePosition = {
id: getSerializedNodeId(target),
timeOffset: 0,
x: clientX,
y: clientY,
}
if (isExperimentalFeatureEnabled('visualviewport') && window.visualViewport) {
const { visualViewportX, visualViewportY } = convertMouseEventToLayoutCoordinates(clientX, clientY)
position.x = visualViewportX
position.y = visualViewportY
}
cb([position], isTouchEvent(event) ? IncrementalSource.TouchMove : IncrementalSource.MouseMove)
}
}),
Expand Down Expand Up @@ -115,12 +140,18 @@ function initMouseInteractionObserver(
return
}
const { clientX, clientY } = isTouchEvent(event) ? event.changedTouches[0] : event
cb({
const position: MouseInteractionParam = {
id: getSerializedNodeId(target),
type: eventTypeToMouseInteraction[event.type as keyof typeof eventTypeToMouseInteraction],
x: clientX,
y: clientY,
})
}
if (isExperimentalFeatureEnabled('visualviewport') && window.visualViewport) {
const { visualViewportX, visualViewportY } = convertMouseEventToLayoutCoordinates(clientX, clientY)
position.x = visualViewportX
position.y = visualViewportY
}
cb(position)
}
return addEventListeners(document, Object.keys(eventTypeToMouseInteraction) as DOM_EVENT[], handler, {
capture: true,
Expand All @@ -141,12 +172,20 @@ function initScrollObserver(cb: ScrollCallback, defaultPrivacyLevel: DefaultPriv
}
const id = getSerializedNodeId(target)
if (target === document) {
const scrollEl = (document.scrollingElement || document.documentElement)!
cb({
id,
x: scrollEl.scrollLeft,
y: scrollEl.scrollTop,
})
if (isExperimentalFeatureEnabled('visualviewport')) {
cb({
id,
x: getScrollX(),
y: getScrollY(),
})
} else {
const scrollEl = (document.scrollingElement || document.documentElement)!
cb({
id,
x: scrollEl.scrollLeft,
y: scrollEl.scrollTop,
})
}
jagracey marked this conversation as resolved.
Show resolved Hide resolved
} else {
cb({
id,
Expand Down Expand Up @@ -338,3 +377,32 @@ function initFocusObserver(focusCb: FocusCallback): ListenerHandler {
focusCb({ has_focus: document.hasFocus() })
}).stop
}

function initVisualViewportResizeObserver(cb: VisualViewportResizeCallback): ListenerHandler {
if (!window.visualViewport) {
return noop
}
const { throttled: updateDimension, cancel: cancelThrottle } = throttle(
monitor(() => {
cb(getVisualViewport())
}),
VISUAL_VIEWPORT_OBSERVER_THRESHOLD,
{
trailing: false,
}
)
const removeListener = addEventListeners(
window.visualViewport,
[DOM_EVENT.RESIZE, DOM_EVENT.SCROLL],
updateDimension,
{
capture: true,
passive: true,
}
).stop

return function stop() {
removeListener()
cancelThrottle()
}
}
33 changes: 18 additions & 15 deletions packages/rum/src/domain/record/record.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { isExperimentalFeatureEnabled } from '@datadog/browser-core'
import { RecordType } from '../../types'
import { serializeDocument } from './serialize'
import { initObservers } from './observer'
import { IncrementalSource, RecordAPI, RecordOptions } from './types'
import { getWindowHeight, getWindowWidth } from './utils'

import { MutationController } from './mutationObserver'
import { getVisualViewport, getScrollX, getScrollY, getWindowHeight, getWindowWidth } from './viewports'

export function record(options: RecordOptions): RecordAPI {
const { emit } = options
Expand Down Expand Up @@ -37,26 +39,21 @@ export function record(options: RecordOptions): RecordAPI {
data: {
node: serializeDocument(document, options.defaultPrivacyLevel),
initialOffset: {
left:
window.pageXOffset !== undefined
? window.pageXOffset
: document?.documentElement.scrollLeft ||
document?.body?.parentElement?.scrollLeft ||
document?.body.scrollLeft ||
0,
top:
window.pageYOffset !== undefined
? window.pageYOffset
: document?.documentElement.scrollTop ||
document?.body?.parentElement?.scrollTop ||
document?.body.scrollTop ||
0,
left: getScrollX(),
top: getScrollY(),
},
},
type: RecordType.FullSnapshot,
})
}

if (isExperimentalFeatureEnabled('visualviewport') && window.visualViewport) {
emit({
data: getVisualViewport(),
type: RecordType.VisualViewport,
})
}
jagracey marked this conversation as resolved.
Show resolved Hide resolved

takeFullSnapshot()

const stopObservers = initObservers({
Expand Down Expand Up @@ -131,6 +128,12 @@ export function record(options: RecordOptions): RecordAPI {
type: RecordType.Focus,
data,
}),
visualViewportResizeCb: (data) => {
emit({
data,
type: RecordType.VisualViewport,
})
},
})

return {
Expand Down
7 changes: 5 additions & 2 deletions packages/rum/src/domain/record/types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { DefaultPrivacyLevel } from '@datadog/browser-core'
import { FocusRecord, RawRecord } from '../../types'
import { FocusRecord, RawRecord, VisualViewportRecord } from '../../types'
import { MutationController } from './mutationObserver'

export enum IncrementalSource {
Expand Down Expand Up @@ -79,6 +79,7 @@ export interface ObserverParam {
mouseInteractionCb: MouseInteractionCallBack
scrollCb: ScrollCallback
viewportResizeCb: ViewportResizeCallback
visualViewportResizeCb: VisualViewportResizeCallback
inputCb: InputCallback
mediaInteractionCb: MediaInteractionCallback
styleSheetRuleCb: StyleSheetRuleCallback
Expand Down Expand Up @@ -181,7 +182,7 @@ export const MouseInteractions = {

export type MouseInteractions = typeof MouseInteractions[keyof typeof MouseInteractions]

interface MouseInteractionParam {
export interface MouseInteractionParam {
type: MouseInteractions
id: number
x: number
Expand Down Expand Up @@ -242,6 +243,8 @@ export type MediaInteractionCallback = (p: MediaInteractionParam) => void

export type FocusCallback = (data: FocusRecord['data']) => void

export type VisualViewportResizeCallback = (data: VisualViewportRecord['data']) => void

export type ListenerHandler = () => void
export type HookResetter = () => void

Expand Down
16 changes: 0 additions & 16 deletions packages/rum/src/domain/record/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,22 +22,6 @@ export function hookSetter<T>(
}
}

export function getWindowHeight(): number {
return (
window.innerHeight ||
(document.documentElement && document.documentElement.clientHeight) ||
(document.body && document.body.clientHeight)
)
}

export function getWindowWidth(): number {
return (
window.innerWidth ||
(document.documentElement && document.documentElement.clientWidth) ||
(document.body && document.body.clientWidth)
)
}

export function isTouchEvent(event: MouseEvent | TouchEvent): event is TouchEvent {
return Boolean((event as TouchEvent).changedTouches)
}
Expand Down
48 changes: 48 additions & 0 deletions packages/rum/src/domain/record/viewports.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { getScrollX, getScrollY, getWindowWidth, getWindowHeight } from './viewports'

function isMobileSafari12() {
return /iPhone OS 12.* like Mac OS.* Version\/12.* Mobile.*Safari/.test(navigator.userAgent)
}

describe('layout viewport', () => {
beforeEach(() => {
document.body.style.setProperty('margin-bottom', '2000px')
})

afterEach(() => {
document.body.style.removeProperty('margin-bottom')
window.scrollTo(0, 0)
})

describe('get window width and height', () => {
it('normalized scroll matches native behaviour', () => {
const initialInnerWidth = getWindowWidth()
const initialInnerHeight = getWindowHeight()
expect(initialInnerWidth).toBe(window.innerWidth)
expect(initialInnerHeight).toBe(window.innerHeight)
})
})

describe('getScrollX/Y', () => {
it('normalized scroll matches initial behaviour', () => {
expect(getScrollX()).toBe(0)
expect(getScrollY()).toBe(0)
expect(getScrollX()).toBe(window.scrollX || window.pageXOffset)
expect(getScrollY()).toBe(window.scrollY || window.pageYOffset)
})
it('normalized scroll updates when scrolled', () => {
if (isMobileSafari12()) {
// Mobile Safari 12 doesn't support scrollTo() within an iframe
// Karma is evaluating some tests in an iframe
// https://coderwall.com/p/c-aqqw/scrollable-iframe-on-mobile-safari
pending('Mobile Safari 12 not supported')
}
const SCROLL_DOWN_PX = 100
window.scrollTo(0, SCROLL_DOWN_PX)
expect(getScrollX()).toBe(0)
expect(getScrollY()).toBe(100)
expect(getScrollX()).toBe(window.scrollX || window.pageXOffset)
expect(getScrollY()).toBe(window.scrollY || window.pageYOffset)
})
})
})
Loading