-
Notifications
You must be signed in to change notification settings - Fork 33
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
Use only DOMHighResTimeStamp to calculate duration #143
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -11,7 +11,9 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
|
||
import { | ||
DOMMargin, | ||
DOMRectPojo, | ||
DOMString, | ||
InternalIntersectionObserverEntry, | ||
IntersectionObserverInit, | ||
SpanielObserverEntry, | ||
SpanielObserverInit, | ||
|
@@ -26,7 +28,7 @@ import { generateToken, off, on, scheduleWork } from './metal/index'; | |
import w from './metal/window-proxy'; | ||
import { calculateIsIntersecting } from './utils'; | ||
|
||
let emptyRect = { x: 0, y: 0, width: 0, height: 0 }; | ||
let emptyRect: DOMRectPojo = { x: 0, y: 0, width: 0, height: 0, bottom: 0, left: 0, top: 0, right: 0 }; | ||
|
||
export function DOMMarginToRootMargin(d: DOMMargin): DOMString { | ||
return `${d.top}px ${d.right}px ${d.bottom}px ${d.left}px`; | ||
|
@@ -108,7 +110,8 @@ export class SpanielObserver implements SpanielObserverInterface { | |
this.paused = false; | ||
|
||
let ids = Object.keys(this.recordStore); | ||
let time = this.generateObserverTimestamp(); | ||
const highResTime = performance.now(); | ||
const time = this.generateObserverTimestamp(); | ||
for (let i = 0; i < ids.length; i++) { | ||
let entry = this.recordStore[ids[i]].lastSeenEntry; | ||
if (entry) { | ||
|
@@ -117,6 +120,7 @@ export class SpanielObserver implements SpanielObserverInterface { | |
intersectionRatio, | ||
boundingClientRect, | ||
time, | ||
highResTime, | ||
isIntersecting, | ||
rootBounds, | ||
intersectionRect, | ||
|
@@ -134,41 +138,57 @@ export class SpanielObserver implements SpanielObserverInterface { | |
this.queuedEntries = []; | ||
} | ||
} | ||
private generateSpanielEntry(entry: IntersectionObserverEntry, state: SpanielThresholdState): SpanielObserverEntry { | ||
private generateSpanielEntry( | ||
entry: InternalIntersectionObserverEntry, | ||
state: SpanielThresholdState | ||
): SpanielObserverEntry { | ||
let { intersectionRatio, rootBounds, boundingClientRect, intersectionRect, isIntersecting, time, target } = entry; | ||
let record = this.recordStore[(<SpanielTrackedElement>target).__spanielId]; | ||
const timeOrigin = w.performance.timeOrigin || w.performance.timing.navigationStart; | ||
const unixTime = this.usingNativeIo ? Math.floor(timeOrigin + time) : time; | ||
const unixTime = this.usingNativeIo | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This ultimately becomes We should stick to Pausing the hr time clock during sleep is okay for us to calculate impression duration. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. if we use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also, I don't think we are ever mixing There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The line below, Left another related comment at line 209. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If the logic is indeed incorrect, do we have a test to cover? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Sort of, it's correct if we want to mirror native IO, but that is a breaking change. This change just modifies how duration is calculated. Added tests for visibleTime earlier today: https://github.com/linkedin/spaniel/blob/v2-release/test/playwright/modules/visible-time.ts There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah wait I see what you mean, how using native IO previously meant using hr time. I will add another test for this behavior first. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. wait no we always are using unix timestamp time for the |
||
? Math.floor((w.performance.timeOrigin || w.performance.timing.navigationStart) + time) | ||
: time; | ||
const highResTime = this.usingNativeIo ? time : entry.highResTime; | ||
if (!highResTime) { | ||
throw new Error('Missing intersection entry timestamp'); | ||
} | ||
return { | ||
intersectionRatio, | ||
isIntersecting, | ||
unixTime, | ||
time: unixTime, | ||
highResTime, | ||
rootBounds, | ||
boundingClientRect, | ||
intersectionRect, | ||
target: <SpanielTrackedElement>target, | ||
duration: 0, | ||
visibleTime: isIntersecting ? unixTime : -1, | ||
entering: false, | ||
payload: record.payload, | ||
label: state.threshold.label | ||
}; | ||
} | ||
private handleRecordExiting(record: SpanielRecord) { | ||
const time = Date.now(); | ||
const perfTime = performance.now(); | ||
record.thresholdStates.forEach((state: SpanielThresholdState) => { | ||
const boundingClientRect = record.lastSeenEntry && record.lastSeenEntry.boundingClientRect; | ||
this.handleThresholdExiting( | ||
{ | ||
intersectionRatio: -1, | ||
isIntersecting: false, | ||
unixTime: time, | ||
time, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Looks like this is not used anywhere anymore, can we remove? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. While it's true that it's technically not needed anymore (because There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added tests for visibleTime earlier today: https://github.com/linkedin/spaniel/blob/v2-release/test/playwright/modules/visible-time-tests.ts |
||
highResTime: perfTime, | ||
payload: record.payload, | ||
label: state.threshold.label, | ||
entering: false, | ||
rootBounds: emptyRect, | ||
boundingClientRect: boundingClientRect || emptyRect, | ||
intersectionRect: emptyRect, | ||
duration: time - state.lastVisible, | ||
visibleTime: state.lastVisible.unixTime, | ||
// Next line (duration) is always overwritten if the record becomes a callback event | ||
duration: perfTime - state.lastVisible.highResTime, | ||
target: record.target | ||
}, | ||
state | ||
|
@@ -179,19 +199,20 @@ export class SpanielObserver implements SpanielObserverInterface { | |
}); | ||
} | ||
private handleThresholdExiting(spanielEntry: SpanielObserverEntry, state: SpanielThresholdState) { | ||
let { time } = spanielEntry; | ||
let { highResTime } = spanielEntry; | ||
let hasTimeThreshold = !!state.threshold.time; | ||
if (state.lastSatisfied && (!hasTimeThreshold || (hasTimeThreshold && state.visible))) { | ||
// Make into function | ||
spanielEntry.duration = time - state.lastVisible; | ||
spanielEntry.duration = highResTime - state.lastVisible.highResTime; | ||
spanielEntry.visibleTime = state.lastVisible.unixTime; | ||
spanielEntry.entering = false; | ||
state.visible = false; | ||
this.queuedEntries.push(spanielEntry); | ||
} | ||
|
||
clearTimeout(state.timeoutId); | ||
} | ||
private handleObserverEntry(entry: IntersectionObserverEntry) { | ||
private handleObserverEntry(entry: InternalIntersectionObserverEntry) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure about this. |
||
let target = <SpanielTrackedElement>entry.target; | ||
let record = this.recordStore[target.__spanielId]; | ||
|
||
|
@@ -217,19 +238,26 @@ export class SpanielObserver implements SpanielObserverInterface { | |
if (isSatisfied != state.lastSatisfied) { | ||
if (isSatisfied) { | ||
spanielEntry.entering = true; | ||
state.lastVisible = spanielEntry.time; | ||
state.lastVisible = { | ||
highResTime: spanielEntry.highResTime, | ||
unixTime: spanielEntry.unixTime | ||
}; | ||
if (hasTimeThreshold) { | ||
const timerId: number = Number( | ||
setTimeout(() => { | ||
state.visible = true; | ||
spanielEntry.duration = Date.now() - state.lastVisible; | ||
spanielEntry.duration = performance.now() - state.lastVisible.highResTime; | ||
spanielEntry.visibleTime = state.lastVisible.unixTime; | ||
this.callback([spanielEntry]); | ||
}, state.threshold.time) | ||
); | ||
state.timeoutId = timerId; | ||
} else { | ||
state.visible = true; | ||
spanielEntry.duration = Date.now() - state.lastVisible; | ||
// TODO: Remove setting duration here, as it's irrelevant and should be very close to 0. | ||
// It doesn't make sense to calculate duration when the entry represents entering, not | ||
// exiting the viewport. | ||
spanielEntry.duration = Date.now() - state.lastVisible.unixTime; | ||
this.queuedEntries.push(spanielEntry); | ||
} | ||
} else { | ||
|
@@ -286,7 +314,10 @@ export class SpanielObserver implements SpanielObserverInterface { | |
lastEntry: null, | ||
threshold, | ||
visible: false, | ||
lastVisible: 0 | ||
lastVisible: { | ||
unixTime: 0, | ||
highResTime: -1 | ||
} | ||
})) | ||
}; | ||
this.observer.observe(trackedTarget); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are these new? I might have missed where are
x
andy
read?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no, it's just that we previously were not actually returning a https://developer.mozilla.org/en-US/docs/Web/API/DOMRect
so adding it to be closer to the native behavior.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like
ClientRect
is a subset ofSpanielRect
so there's no need to doClientRect & SpanielRect
. I also noticed there's built-inDOMRectReadOnly
from https://github.com/microsoft/TypeScript/blob/main/lib/lib.dom.d.ts. Sospaniel/src/interfaces.ts
Line 83 in 6b74e4d
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok I created
export type DOMRectPojo = Omit<DOMRectReadOnly, 'toJSON'>;
Instead of
ClientRect & SpanielRect