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

Expose LCP renderTime in non-TAO cross-origin images, when it doesn't reflect the image decoding time #91

Open
yoavweiss opened this issue Mar 18, 2022 · 12 comments
Assignees

Comments

@yoavweiss
Copy link
Contributor

Currently Chromium has unspecified heuristics that ignore LCP candidates when the document's opacity is 0, and only expose those candidates when the opacity becomes non zero.

Once we've specified those, it may make sense to expose the renderTime for such LCP candidates, even without a TAO opt-in. Not doing that can result in situations where LCP's startTime is lower than FCP.

The rational for that is that the renderTime in such cases doesn't reveal anything about the image's decoding speed.
One caveat to that is that it can reveal that the image finished decoding before the opacity change, so may make sense to include some fixed timeout where renderTime will be expoed only if the opacity change happened X milliseconds after the image's load time.

Thoughts?

@mmocny
Copy link

mmocny commented Mar 18, 2022

Generally, makes sense to me.

My one concern is: is anyone is using the existence of renderTime property as some form of feature detection? I've done this before... but, only to detect when I should use a fallback LCP approach. So this proposal would have been preferred there.

I think the new semantics would be

  • If image is presented soon after decode, expose only loadTime
  • If image is presented long after decode, expose that value as renderTime

Are there other cases where renderTime is decoupled from decodeTime? Could this be applied more broadly?

@yoavweiss
Copy link
Contributor Author

is anyone is using the existence of renderTime property as some form of feature detection?

Can you expand on that?

@mmocny
Copy link

mmocny commented May 18, 2022

Can you expand on that?

We discussed this issue at web perf WG meeting. We had a few interesting points raised which we should summarize here, and I think it's worth summarizing the notes here. (But I am not doing so right now, sorry).


I just wanted to add one more case which is similar: prerendering, where paint timings are also adjusted due to explicit visibility hiding.

It may be nice if we had a solution where we can make it more obvious that the renderTime was due to some arbitrary gap of time where the element was done load and decode, but wasn't even attempting to present since it was hidden.


And, with that in mind, an idea came to mind. First, some background:

  • For Event Timing we already expose startTime, processingStart & processingEnd, and total duration.
  • startTime + duration is equivalent to renderTime for paint timing (~some round()ing).
  • renderTime is really representing a presentationTime on some browsers, which is not well specced (this is an interop issue, and this was also recently discussed).
  • On other browsers, it represents the end time of the main thread update-the-rendering task that eventually leads to the presentation of the result.
  • Paint timings startTime may actually fallback to loadTime (e.g. this issue), and Event timings duration may fallback to using processingEnd, in both cases whenever a real renderTime is unavailable.
  • Separate from this detail, we've already had explicit requests to expose more timings, including explicitly marking the start/end of that rendering task.

So, could we solve multiple problems at once by adding consistency to some of these time points?

  • startTime becomes the "timeOrigin" for any performance entry.
    • For Event Timing, it's the event hardware time.
    • For Paint Timing, it would be based on the navigation (and should work with prerender activation, same-document navigations, bf-cache...)
  • duration becomes the best measure of end time, based on other timings which are available
    • presentationTime, if it is available
    • renderingEnd could be exposed, to be explicitly about measuring the update-the-rendering task
    • renderingStart also exposed if possible
    • finally, fallback to processingEnd or loadTime, as we do today.

With all this, you could probably already expose renderingStart (and maybe even renderingEnd) even for cases of cross-origin images without TAO. That would be a more accurate timing that is unrelated to the image, and would capture these arbitrary time gaps.

You perhaps could possibly also expose a presentationTime, if the image was already decoded prior to renderingStart (e.g. the loaded but explicitly hidden -> shown cases).

You can then explicitly compare the loadTime vs renderingStart times as a clue to know if the LCP was artificially late (e.g. due to content hiding, or due to unrelated script delays). This would be very useful for LCP attribution even outside of this issue here.

Note: this proposes a literal swap of how startTime and duration are currently used for paint timing, which may make this specific suggestion untenable. It also basically suggests deprecating renderTime value since its not used very consistently. We may want something more reasonable :)

@mmocny
Copy link

mmocny commented Jun 1, 2022

#83 seems related here as well (where we suggest exposing the first frame of an animated image as a unique time point distinct from loadTme and final renderTime/presentationTime)

@nicjansma
Copy link

May 12 W3C WebPerf Meeting minutes and summary:

  • For LCP, we only expose renderTime when it is same origin (or TAO opt-in). Otherwise we expose load time. As a result, in some cases, LCP's startTime is lower than FCP.
  • In the case where renderTime is not a function of image itself, but a function of logic on page (invisible to visible), then the proposal is to expose renderTime for these, as it doesn't reveal any information about itself, just about the page.
  • Discussion of some other options, such as a property noting if LCP was due to an image that was held back before being added to the DOM. Or another property such as imageReadyTime.

@mmocny am I missing anything else that you recall?

@mmocny
Copy link

mmocny commented Jun 30, 2022

I think the one large-ish question that was raised was: what happens is there is still some rendering time left that is still specific to the image? One example given was: what if the image finishes loading, but doesn't start decoding? Thats a case where there is a large "idle" time gap, but exposing renderTime can still leak information about the image presumably?

Another, more minor point: the proposal is that whenever loadTime and renderTime are separated by some idle time (perhaps +gap), its safe to expose because idle time dominates... but thats really only easy to detect when the image is attached to DOM and hidden with CSS. But what happens when you use JS, and something like a vdom, to prefetch the image but then choose not attach it to DOM until some time later... Its hard to know if thats some sort of long-task related delay, but the image is still racing... vs induced idle time. It's possible we can report for this case... but then maybe that opens the door for even more reporting?

@yoavweiss
Copy link
Contributor Author

If image decoding work doesn't start before we add the image to the DOM, then we cannot expose render time in those cases. If it's done independently, then we can.
I'm not sure which of the above is the current behavior in Chromium and other engines. I also believe those parts of implementations are not specified, and hence I'd be reluctant to web expose them here, unless we also want to specify them.

@yoavweiss
Copy link
Contributor Author

In discussions elsewhere, @mmocny suggested that we can potentially expose the renderTime of non-TAO LCP images when it equals the FCP time, which would tackle the opacity change case, foregrounding case and maybe others. It won't cover all cases in which we can expose the render time without revealing details about the image, but it'd be significantly simpler to do so. We could expand on that in the future if needed.

@yoavweiss
Copy link
Contributor Author

Assuming there's agreement on the above direction, I think the next step would be to whip up a PR, and tests to go along with it.

@mmocny
Copy link

mmocny commented Aug 9, 2024

This issue hasn't been touched for a while, so here's a quick update:


Chromium has added a feature flag called ExposeRenderTimeNonTaoDelayedImage.

Despite that name, it doesn't actually expose renderTime. Instead, it adds the FCP time as a final fallback for LCP. This behaviour is somewhat equivalent to a developer using const lcp_time = Math.max(FCP.startTime, LCP.startTime) in their own rum reporting.

Besides being ergonomically useful, it also addresses issues that come up in developer feedback such as LCP time being 0, or even "appearing to be negative", whenever you have an activation time that is non-0. See how web-vitals.js library expects lcp.startTime to be at least as large as page activation time.

I think we should update the LCP spec to clamp the LCP candidate startTime to at least the FCP.startTime.

Then, Chromium can consider shipping this feature.


As a distinct change, @noamr has been poking away at the paint timing interop issue: w3c/paint-timing/issues/62.

I am not certain, but I think that one opens a pathway by which even for non-TAO images which might still be able to report the animation frame's paint time, or raf time, or something that isn't falling back to loadTime.

If that work lands, it is possible that LCP.startTime will no longer be 0, but it might still be less than FCP.startTime (if FCP does measure presentation time and LCP does not).

It is not clear to me what should be the right behaviour in that use case, but it may be that this feature here entirely goes away. I suspect we will always want to use some sort of minimum possible paint time, and that FCP.startTime is a good default.


@yoavweiss @noamr @nicjansma WDYT?

@mmocny
Copy link

mmocny commented Aug 9, 2024

One risk with the above: today FCP.startTime is always either renderTime or loadTime and now we are adding a third option.

If any scripts specifically test for renderTime == 0 or startTime == renderTime, and then assume certain things based on that test... it could be possible that adding a new case (LCP.startTime as FCP.startTime) might break some expectations. Theoretically.

But, I tried to search github for any snippets that might do something like this, and I have not successfully done so. The very small number of cases where snippets actually read renderTime is usually just for diagnostics, it seems.

@mmocny
Copy link

mmocny commented Nov 6, 2024

Update: I believe @noamr is working on this.

I think that:

  • render timings are available to the whole page via carious mechanisms, not just specific elements, so hiding renderTime from one element is not useful
  • when the page is not cross origin isolated (right?) then all render timings should be significantly coarsened, otherwise they don't need to be
  • then, all element timings, TAO or not, can expose these values for convenience

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants