Skip to content

Commit c8a294f

Browse files
committed
Allow independent re-entrant history calls
1 parent 79d2ad3 commit c8a294f

1 file changed

Lines changed: 27 additions & 23 deletions

File tree

plugins/3rd-party-optimizer/src/yieldGTMCalls.ts

Lines changed: 27 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -348,28 +348,30 @@ const gtmObserver = new MutationObserver(() => {
348348
gtmObserver.observe(document.documentElement, { childList: true, subtree: true })
349349

350350
// #region History/submit wrapper override
351-
const originalMethodsCalledInCurrentChain = new Set<Function>()
351+
const skippedWrappedListenerGenerations = new Map<Function, number>()
352+
let nextWrappedListenerGeneration = 0
352353

353-
function callOriginalMethod(this: unknown, originalMethod: Function, args: unknown[]) {
354-
if (originalMethodsCalledInCurrentChain.has(originalMethod)) return
355-
originalMethod.apply(this, args)
356-
}
357-
358-
function withOriginalMethodAlreadyCalled(originalMethod: Function, callback: VoidFunction) {
359-
const alreadyMarked = originalMethodsCalledInCurrentChain.has(originalMethod)
360-
originalMethodsCalledInCurrentChain.add(originalMethod)
354+
function withOlderWrappedListenersSkipped(originalMethod: Function, generation: number, callback: VoidFunction) {
355+
const previousSkippedGeneration = skippedWrappedListenerGenerations.get(originalMethod)
356+
skippedWrappedListenerGenerations.set(
357+
originalMethod,
358+
previousSkippedGeneration === undefined ? generation : Math.max(previousSkippedGeneration, generation)
359+
)
361360
try {
362361
callback()
363362
} finally {
364-
if (!alreadyMarked) {
365-
originalMethodsCalledInCurrentChain.delete(originalMethod)
363+
if (previousSkippedGeneration === undefined) {
364+
skippedWrappedListenerGenerations.delete(originalMethod)
365+
} else {
366+
skippedWrappedListenerGenerations.set(originalMethod, previousSkippedGeneration)
366367
}
367368
}
368369
}
369370

370-
function wrapListener(originalMethod: Function, value: Function) {
371+
function wrapListener(originalMethod: Function, value?: Function) {
372+
const generation = nextWrappedListenerGeneration++
371373
// the function syntax is important here so we keep the correct `this`.
372-
return function yieldingListener(this: unknown, ...args: unknown[]) {
374+
function yieldingListener(this: unknown, ...args: unknown[]) {
373375
if (DEBUG) {
374376
console.log("Yielding for", originalMethod)
375377
console.timeStamp(originalMethod as unknown as string)
@@ -378,29 +380,31 @@ function wrapListener(originalMethod: Function, value: Function) {
378380
// We first call the original: This optimizes for UX & correctness of React components.
379381
// e.g., for pushState, when a component renders on a new route, it might set state and/or read from the URL. If the URL isn't
380382
// accurate, it might lead to wrong behavior.
381-
// We don't want to call the underlying native method twice if an override calls a previously captured wrapper.
382-
callOriginalMethod.call(this, originalMethod, args)
383+
// If an override calls a previously captured wrapper, that older wrapper skips the native method;
384+
// fresh calls through the current getter still update history/submit normally.
385+
const skippedGeneration = skippedWrappedListenerGenerations.get(originalMethod)
386+
if (skippedGeneration === undefined || generation >= skippedGeneration) {
387+
originalMethod.apply(this, args)
388+
}
389+
390+
if (!value) return
383391

384392
// If `method` is overriden N times, it creates N yield points (as overrides might be chained)
385393
yieldUnlessUrgent(() => {
386394
// The arrow FN is important here so we keep the correct `this`.
387-
withOriginalMethodAlreadyCalled(originalMethod, () => {
395+
withOlderWrappedListenersSkipped(originalMethod, generation, () => {
388396
value.apply(this, args)
389397
})
390398
})
391399
}
400+
401+
return yieldingListener
392402
}
393403
function overrideListener<T extends object>(target: T, method: keyof T) {
394404
// @ts-expect-error TS(2339): Prototype chain call. We try __proto__ first, as this will usually be the original method.
395405
const originalMethod: Function = (target.__proto__ as unknown as T)[method] ?? (target[method] as Function)
396406

397-
let mostRecentWrapper: Function | undefined = wrapListener(
398-
originalMethod,
399-
// The function syntax is important here so we keep the correct `this`.
400-
function firstOverride(this: unknown, ...args: unknown[]) {
401-
callOriginalMethod.call(this, originalMethod, args)
402-
},
403-
)
407+
let mostRecentWrapper: Function | undefined = wrapListener(originalMethod)
404408

405409
Object.defineProperty(target, method, {
406410
enumerable: true,

0 commit comments

Comments
 (0)