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
🐛Intersection Observer Polyfill Mutation Observer - Fixed Race Condition #19168
Changes from 9 commits
7ef9930
ad491aa
63caf4b
2be1f3b
59f561e
c17cb5c
87ec925
f96fd1b
0eba342
d5051b9
9892c8e
42a9964
c7282f8
241a4fe
05f8046
e75f2b5
14fc692
095f94f
9bf91c1
5861c3b
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 |
---|---|---|
|
@@ -297,7 +297,7 @@ export class IntersectionObserverPolyfill { | |
this.viewport_ = Services.viewportForDoc(element); | ||
this.mutationPass_ = new Pass(ampdoc.win, () => { | ||
if (this.viewport_) { | ||
this.tick(this.viewport_.getRect()); | ||
this.tick(this.viewport_.getRect(), undefined, true); | ||
} | ||
}); | ||
this.mutationObserver_ = new ampdoc.win.MutationObserver( | ||
|
@@ -339,8 +339,9 @@ export class IntersectionObserverPolyfill { | |
* The iframe must be a non-scrollable iframe. | ||
* @param {!./layout-rect.LayoutRectDef} hostViewport | ||
* @param {./layout-rect.LayoutRectDef=} opt_iframe | ||
* @param {boolean=} opt_calledFromMutationObserver | ||
*/ | ||
tick(hostViewport, opt_iframe) { | ||
tick(hostViewport, opt_iframe, opt_calledFromMutationObserver) { | ||
if (opt_iframe) { | ||
// If element inside an iframe. Adjust origin to the iframe.left/top. | ||
hostViewport = | ||
|
@@ -356,7 +357,11 @@ export class IntersectionObserverPolyfill { | |
|
||
for (let i = 0; i < this.observeEntries_.length; i++) { | ||
const change = this.getValidIntersectionChangeEntry_( | ||
this.observeEntries_[i], hostViewport, opt_iframe); | ||
this.observeEntries_[i], | ||
hostViewport, | ||
opt_iframe, | ||
opt_calledFromMutationObserver | ||
); | ||
if (change) { | ||
changes.push(change); | ||
} | ||
|
@@ -376,22 +381,37 @@ export class IntersectionObserverPolyfill { | |
* @param {!ElementIntersectionStateDef} state | ||
* @param {!./layout-rect.LayoutRectDef} hostViewport hostViewport's rect | ||
* @param {./layout-rect.LayoutRectDef=} opt_iframe iframe container rect | ||
* If opt_iframe is provided, all LayoutRect has position relative to | ||
* the iframe. If opt_iframe is not provided, | ||
* all LayoutRect has position relative to the host document. | ||
* @param {boolean=} opt_calledFromMutationObserver | ||
* Whether this was called from a mutation observer | ||
* This will signal that the layout of the elements | ||
* must use getBoundingClientRect, as Resource Scheduler | ||
* could be called too late, and not clear the layout cache. | ||
* @return {?IntersectionObserverEntry} A valid change entry or null if ratio | ||
* @private | ||
*/ | ||
getValidIntersectionChangeEntry_(state, hostViewport, opt_iframe) { | ||
getValidIntersectionChangeEntry_(state, hostViewport, | ||
opt_iframe, opt_calledFromMutationObserver) { | ||
const {element} = state; | ||
|
||
// Normalize container LayoutRect to be relative to page | ||
let elementRect = null; | ||
const owner = element.getOwner(); | ||
let ownerRect = null; | ||
|
||
// If opt_iframe is provided, all LayoutRect has position relative to | ||
// the iframe. | ||
// If opt_iframe is not provided, all LayoutRect has position relative to | ||
// the host document. | ||
const elementRect = element.getLayoutBox(); | ||
const owner = element.getOwner(); | ||
ownerRect = owner && owner.getLayoutBox(); | ||
// If opt_calledFromMutationObserver is provided, | ||
// we need to get our bounding rect rather | ||
// Than our layout box, in order to avoid race conditions | ||
// with the resource scheduler | ||
if (opt_calledFromMutationObserver) { | ||
elementRect = this.viewport_.getLayoutRect(element); | ||
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. @jridgewell Do we need different method when layer is enabled? 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. No, layers is corrected implemented in |
||
ownerRect = owner && this.viewport_.getLayoutRect(owner); | ||
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. These need to be inside measure phases. |
||
} else { | ||
elementRect = element.getLayoutBox(); | ||
ownerRect = owner && owner.getLayoutBox(); | ||
} | ||
|
||
// calculate intersectionRect. that the element intersects with hostViewport | ||
// and intersects with owner element and container iframe if exists. | ||
|
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.
You could instead trigger tick on the next Resource pass, which will mean everything's been remeasured. Then we don't need booleans floating around.
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.
Oh nice! Yeah let's try that, I think that would be the most optimal solution. Are the Resource passes usually done fast? As in there is nothing that could make it take longer than a second?
And what is the function/service to wait for this?
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.
They can take up to 30s if there's no activity on the page (or it's in the background). But since there was a mutation, it'll happen in the next 100ms.
There's not currently one to do this. We can add a method to
Resources
, sayonNextPass
. Then, at the end ofResources.p.doPass
, we resolve call all the pending callbacks, and clear out the array.