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

Visibility and IntersectionObserver #193

Open
jeremyroman opened this issue May 6, 2020 · 20 comments
Open

Visibility and IntersectionObserver #193

jeremyroman opened this issue May 6, 2020 · 20 comments
Labels
spec todo A nitty-gritty detail that needs spec work

Comments

@jeremyroman
Copy link
Collaborator

We need to address document.visibilityState (thus document.hidden and the visibilitychange event), IntersectionObserver, and related things.

We haven't really written down a concrete answer here. My suggestion is:

same-origin

  • document.visibilityState matches the host document (same as iframes)
  • IntersectionObserver does not intersect with the outer viewport (and so appears fully visible)
  • requestAnimationFrame issues accurate updates, same as iframes

cross-origin

  • document.visibilityState matches the host document (same as iframes)
  • IntersectionObserver does not intersect with the outer viewport (and so appears fully visible)
  • requestAnimationFrame continues to schedule according to the vsync of the outer page, even if hidden or off-screen

This should allow portals to not waste resources when the outer page is a background tab but not allow the outer page to control the state of the inner page if it is cross-origin (except by doing things like opening a new foreground tab, which are highly disruptive).

@jeremyroman
Copy link
Collaborator Author

@domenic @jakearchibald @jyasskin @lucasgadani does this broadly make sense to you before I turn this into a PR against the explainer?

@domenic
Copy link
Collaborator

domenic commented May 6, 2020

requestAnimationFrame continues to schedule according to the vsync of the outer page, even if hidden or off-screen

How do iframes behave? If they behave the same, then this seems reasonable. If iframes throttle RAF, then it seems sad that portals will cause pages to waste more resources than iframes will, and I'd want to think a bit harder about this...

I guess

IntersectionObserver does not intersect with the outer viewport (and so appears fully visible)

causes a similar tradeoff, when people are using IntersectionObserver for, e.g. lazyloading. Speaking of which, I guess this means any <img loading="lazy"> in the portal would be treated as <img loading="eager"> instead? That'd be worth mentioning explicitly.

@jeremyroman
Copy link
Collaborator Author

re. iframe + rAF, not sure off the top of my head.

re. img[loading=lazy], I think they would load lazily if they are outside of the portal contents (inner) viewport, and eagerly if they are within it (even if the <portal> itself is offscreen). Might be worth expressly calling that out of it isn't specced on top of IntersectionObserver.

@jakearchibald
Copy link
Collaborator

This sounds good to me. I guess the rule of thumb is: Visibility APIs behave as if the portal was activated.

We need to have a think about cases where the outer page can influence the scroll position of the potal'd page, eg by changing the hash of the src. I guess we should force a reload for URL changes, even if only the hash has changed. Although, in this case, the data provided in the hash it more exploitable than any scroll position it's turned into.

@jeremyroman
Copy link
Collaborator Author

@jakearchibald might not be a bad idea to file the hash issue so that it's tracked. I'm pretty confident that it doesn't affect the overall design much (and I wouldn't expect the host triggering same-document navigation to be that common anyway), but it's a point that we should keep in mind.

jeremyroman added a commit to jeremyroman/portals that referenced this issue May 7, 2020
In particular this change addresses topics discussed in WICG#193.
@domenic
Copy link
Collaborator

domenic commented May 7, 2020

Might be worth expressly calling that out of it isn't specced on top of IntersectionObserver.

I think it's worth expressly calling it out in the explainer regardless. As for the spec, the plan is to re-spec it on top of IntersectionObserver; see whatwg/html#5510 and whatwg/html#5236 (comment).

jeremyroman added a commit that referenced this issue May 7, 2020
Add some information about portal rendering to the explainer.

In particular this change addresses topics discussed in #193.
@domenic domenic added the spec todo A nitty-gritty detail that needs spec work label May 8, 2020
@lucasgadani
Copy link
Collaborator

requestAnimationFrame continues to schedule according to the vsync of the outer page, even if hidden or off-screen

How do iframes behave? If they behave the same, then this seems reasonable. If iframes throttle RAF, then it seems sad that portals will cause pages to waste more resources than iframes will, and I'd want to think a bit harder about this...

On Chrome's implementation, iframes throttle rAF for same-origin pages. Chrome does not throttle rAF for cross-origin iframes, but that might be a bug/lack of optimization.

If the goal is to limit IntersectionObserver inside a portal due to the implicit communication channel, we would need to limit the possibility of communication over rAF scheduling as well. We can do that either by not throttling rAF, by always throttling rAF, or maybe we can find an in-between where we rate limit changes in rAF scheduling.

@jeremyroman
Copy link
Collaborator Author

@lucasgadani #194 included some proposed behavior for requestAnimationFrame

@jakearchibald
Copy link
Collaborator

@jeremyroman

might not be a bad idea to file the hash issue

Done #197

@jyasskin
Copy link
Member

jyasskin commented Jun 5, 2020

Allowing a portal to react to the visibility of its containing page seems to allow correlation based on the times that a page becomes visible. Specifically, an outer page could record the sequence of times when it becomes visible and/or hidden, the portal could do the same, and when the portal is activated, it could compare those sequences with the source origin to join user IDs.

This is similar to many of the issues discussed in https://github.com/asankah/ephemeral-fingerprinting, but that assumes separate top-level pages, which are less likely to be visible together. https://w3cping.github.io/privacy-threat-model/#model-cross-site-recognition proposes a compromise where sites that are co-visible can exchange identifiers in this way, but I think that's less reasonable when one of the sites is inside a portal.

@jeremyroman
Copy link
Collaborator Author

@jyasskin

My main motivation here is to reduce the likelihood that a portal contents wastes time remaining fresh when it's in a background tab. We could consider mitigations like adding random delays before we propagate such changes, but you could probably see past this with a little bit of math if the main page is long-lived. What do you think the right approach is here?

@asankah
Copy link

asankah commented Jun 11, 2020

Looking at design-discussion.md, ephemeral fingerprinting falls under constraint 2. The list of timestamps for visibility events (or some identifier thereof) needs to be exfiltrated out of the yet-to-be-activated portal in order for them to be used for joining identities across the two first parties. Hence a portal that can observe those events have to be in Isolated mode with the (added?) restriction that it can't initiate a navigation out of the isolated origin.

It's a pretty tight restriction.

@jakearchibald
Copy link
Collaborator

Good catch @jyasskin. This is also an issue for window resizing, and that can't really be mitigated by random delays. Ugh.

@bokand
Copy link
Contributor

bokand commented Aug 14, 2020

Just to clarify:

IntersectionObserver does not intersect with the outer viewport (and so appears fully visible)

Means that it doesn't perform an intersection test with the outer viewport of the embedder? That is, IntersectionObserver should behave as if the portaled content is the top-level browsing context and not bubble up through to the embedder. Is that right?

Separate question: is there a way for cross-origin content embedded in the portaled content (e.g. an ad within a portaled page) to know that it's within a portal context? The portaled content can use window.portalHost but (my read of the current spec) is that a cross-origin frame within it can't? Given that visibility and IntersectionObserver will behave as-if the portal is the top-level browsing context, could pages use this to spawn 10 portals to themselves and get 10X ad impressions?

@bokand
Copy link
Contributor

bokand commented Aug 14, 2020

On Chrome's implementation, iframes throttle rAF for same-origin pages. Chrome does not throttle rAF for cross-origin iframes, but that might be a bug/lack of optimization.

This may have changed (or is different for OOPIFs) but https://bokand.github.io/portal-io.html (see console log) shows rAFs stop getting called when the <portal> is offscreen. As a result intersection observer callbacks aren't called while the portal is offscreen.

@jeremyroman
Copy link
Collaborator Author

It certainly shouldn't be able to observe where it is in relation to the outer (i.e., embedder's viewport). My preference had been for overall visibility to be tied to that of the outer page but I could be convinced otherwise. Maybe we can convince ourselves that it should always be marked as not-visible, which might address that, but I'd worry that it would cause content not to load stuff it really should.

I don't think portalHost should be exposed in subframes, but maybe there is value in some way for subframes to tell that they're in a portal, to address the ad fraud case you mention. Maybe the media query you propose elsewhere is a solution to that.

@bokand
Copy link
Contributor

bokand commented Sep 2, 2020

Specifically, an outer page could record the sequence of times when it becomes visible and/or hidden, the portal could do the same, and when the portal is activated, it could compare those sequences with the source origin to join user IDs.

@jyasskin @asankah: is there any precedent or guidance on what amount of fuzzing might make this kind of joining sufficiently impractical? For visibility, it seems like it'd only be an issue on desktop, and even then, fairly rare outside of long-lived tabs. Additionally, I suspect we can tolerate significantly delaying the events (many seconds), particularly visible->hidden so it seems to me like a plausible mitigation? WDYT?

Resize might be a bit more complicated but we could just avoid propagating resizes at all (and scaling the output surface) until activation, it'd lead to a bit of layout jank but this should be a fairly rare case (particularly on mobile).

@bokand
Copy link
Contributor

bokand commented Sep 16, 2020

Ping @asankah regarding above questions.

In particular, https://github.com/asankah/ephemeral-fingerprinting states

Precise timing may not be required. I.e. the lowered level of confidence could still be sufficient for most uses.

If we were to, say, fuzz the visible->hidden signal by 60+ seconds (and not send at all if the user returns to the tab before that's sent) and 5-10 seconds for hidden->visible, it seems like that would make it a rather weak and unreliable signal but I'm not sure how to formalize this. Thoughts?

@jeremyroman
Copy link
Collaborator Author

It was brought up recently that there is a prerender visibility state that we used to use (since removed from the spec) that some content may already handle. Might be worth resurrecting it.

https://developer.mozilla.org/en-US/docs/Web/API/Page_Visibility_API

@bokand
Copy link
Contributor

bokand commented Sep 28, 2020

Anyone know of discussion related to why it was removed?

The MDN docs say prerender can only be a starting state but a page can become portaled - we'd have to change the semantics there. We'd still have to figure out what that means for document.hidden.

It'd also be nice to have a common model with fenced frames. Perhaps an "unknown" value to indicate that the browser can't accurately tell the page it's status?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
spec todo A nitty-gritty detail that needs spec work
Projects
None yet
Development

No branches or pull requests

7 participants