w3c / IntersectionObserver

Intersection Observer

Home Page:https://www.w3.org/TR/intersection-observer/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Cannot track intersection with an iframe's viewport

szager-chromium opened this issue · comments

The implicit root refers to the top window's scrolling viewport, but there is currently no way to track the intersection of an element inside an iframe with the iframe window's scrolling viewport. We might try something like this (running in the iframe's context):

new IntersectionObserver(callback, {root: document.scrollingElement})

... but that doesn't work, because document.scrollingElement.getBoundingClientRect gives you the full size of the document's content, not the window's scrolling viewport.

I think document.scrollingElement ought to be treated specially when it's used as the root of an IntersectionObserver: it should be treated as measuring intersection with the document window's scrolling viewport.

AMP documents would benefit from this. In many cases, content within AMP is rendered within an iframe. The lack of support for this use-case means AMP can't use IntersectionObserver for such situations.

The proposal as stated would be a change of semantics without any IDL change, meaning it's technically not backwards-compatible. However, @szager-chromium and I agree that there seems to be no good use-case for supplying an explicit root that is a scrolling element without such a clip, so we expect usage of the API in this mode to be almost non-existent.

Also, there is an easy workaround if the other behavior is desired: make sure the element is the scrolling element, and observe relative to .

Copying from an email thread:

Off-hand the idea seems fine, I agree this is a really unfortunate limitation. It seems unfortunate to change the semantics of the root option though... Maybe we should extend to allow root take a window or a document instead, and use that to represent the viewport root?

That'd make {root: window} or {root: document} on a top level document behave as no-root, and on an iframe behave as you want with respect to the viewport.

I haven't thought it through all that much though, but given the scrollingElement can change over time as a result of style changes, at least in quirks mode documents, if you define it as you propose you need to decide whether the "is scrolling element" check happens at the moment you create the observer (which would mean that creating an observer would need to update layout, which is bad), or at the moment the notifications are sent, or at some other point (when?).

It seems it'd cause confusion / subtle bugs either way though.

cc @mstange / @dholbert in case they have other opinions.

FYI, we landed this change behind a flag in chromium:

https://chromium-review.googlesource.com/c/chromium/src/+/1865257

In this implementation, the (root == document.scrollingElement) check happens every time we run the intersection algorithm.

We can still change this if we decide that { root: document } or { root: window } makes more sense. My objection to that proposal is that it's simplifying if root can be an Element rather than a Node, but I don't feel especially strongly about it. I would be surprised if using scrollingElement broke any sites, but I suppose we can run a experiment to measure it.

My objection to that proposal is that it's simplifying if root can be an Element rather than a Node, but I don't feel especially strongly about it.

Can you elaborate? If you mean on the IDL level, you can use "unions" on IDL (so Element or Document or such, I think, which limit all the stuff you can pass.

I still think scrollingelement is not great due to the quirks mode stuff, and I think it's a bug that you call scrollingElement() directly from blink in quirks mode (will file), but I guess the intersection of pages using intersection observer and pages using quirks mode is close to zero.

cc @bzbarsky in case he has opinions.

I think relying on scrollingElement, given that it's not time-invariant, is just not a great idea. Furthermore, the whole "have APIs behave weirdly on this one element" thing we have in CSSOM for the scrollingElement is not really how we'd design an API if we were designing it today; it all just exists for backwards compat. If we want to observe something about the entire document, it's probably better to just accept a Document as an argument to indicate that rather than adding more scrollingElement bits.

What about documentElement?

The IDL does get kinda messy if we have to use a union type.

documentElement has the same "we're going to make this one element work weirdly unlike everything else and represent the document" issue. We can do it; lots of other CSSOM-view APIs do that sort of thing for historical reasons. But it seems like it would be good to start moving away from that in new APIs.

Prettiness of IDL is dead last in the priority of constituencies. I'd be a lot more interested in what developers think about the options than in how pretty the IDL is..

OK, I will put together a patch based on accepting a Document as root and see how gnarly it gets.

Off-hand the idea seems fine, I agree this is a really unfortunate limitation. It seems unfortunate to change the semantics of the root option though... Maybe we should extend to allow root take a window or a document instead, and use that to represent the viewport root?

That'd make {root: window} or {root: document} on a top level document behave as no-root, and on an iframe behave as you want with respect to the viewport.

This issue has been getting a good deal of developer attention, so I'd like to move towards finalizing a spec change. It sounds like the behavior of accepting {root: document} is the most popular and palatable option.

Any objections or other comments?

@emilio @bzbarsky -- please take a look at the pull request and let me know if it looks good to you.

Any thoughts how to feature detect this new functionality? E.g. by UA?

With document, I suppose callers could try/catch the constructor TypeError:

TypeError: Failed to construct 'IntersectionObserver': member root is not of type Element.

This error appears to fire consistently across browsers but not sure if it's technically spec behavior.

@choumx I think it's pretty safe to rely on TypeError, especially if you use a completely simple and inert IntersectionObserver construction:

var document_root_supported = true;
try {
new IntersectionObserver(() => {}, {root: document});
catch (e if e instanceof TypeError) {
document_root_supported = false;
}

Because the IDL for the 'root' argument changes from "Element?" to "(Element or Document)?", every browser should catch it in their bindings layer.

@choumx I think it's pretty safe to rely on TypeError, especially if you use a completely simple and inert IntersectionObserver construction:

var document_root_supported = true;
try {
new IntersectionObserver(() => {}, {root: document});
catch (e if e instanceof TypeError) {
document_root_supported = false;
}

Because the IDL for the 'root' argument changes from "Element?" to "(Element or Document)?", every browser should catch it in their bindings layer.

Some Safari versions now pass this feature-detection but have a bug in the implementation. Callbacks are not invoked when the target intersects with the viewport: https://bugs.webkit.org/show_bug.cgi?id=224324