CleverCloud / clever-components

Collection of Web Components by Clever Cloud
https://www.clever-cloud.com/doc/clever-components/
Apache License 2.0
221 stars 20 forks source link

Improve the `ResizeObserver` loop errors situation (tests and prod) #808

Closed hsablonniere closed 9 months ago

hsablonniere commented 1 year ago

The context

We use a ResizeObserver for some components so they can adapt their style and/or template, depending on their size (and not the size of the viewport). We mostly adapt on the width, not the height.

We wrote a "mixin" called withResizeObserver to hide the boilerplate and provide 2 higher level APIs:

NOTE: we recently modified the mixin to prevent some layout shifts https://github.com/CleverCloud/clever-components/commit/07cc7283ee11dc41d9b27984d60e22af5459d816

The "problem"

When you use a ResizeObserver and do something that triggers a resize inside the resize callback, you can potentially create an infinite loop.

Thankfully, the spec has a section about this:

ResizeObserver extends step 12 with resize notifications. It tries to deliver all pending notifications by looping until no pending notifications are available. This can cause an infinite loop. Infinite loop is prevented by shrinking the set of nodes that can notify at every iteration. In each iteration, only nodes deeper than the shallowest node in previous iteration can notify. An error is generated if notification loop completes, and there are undelivered notifications. Elements with undelivered notifications will be considered for delivery in the next loop.

TL;DR: browsers must prevent infinite loops and generate an error if it happens.

Even when the error is NOT visible in the devtools, it is generated and thrown:

Background research

I read many issues, articles and StackOverflow questions:

The conclusion is: this is not really an error you need to fix but more of a warning you can ignore (most of the time).

The solutions

If our goal is to stop seeing these errors in our CI and error reports, I see 2 solutions.

Solution 1: Prevent the error from happening in the first place

This article by Jon Elantha explains everything about the whole problem (in a react context). He proposes a technique for solution 1.

🙂 Pros:

🤬 Cons:

Solution 2: Prevent the error message from being passed to our tooling

The @lit-labs/virtualizer repo uses a ResizeObserver. They don't try to prevent the error from happening but they have a way to silence the error messages so CI and error reports don't see them.

Their documentation have a section about ResizeObserver loop errors:

https://github.com/lit/lit/blob/main/packages/labs/virtualizer/README.md#resizeobserver-loop-limit-exceeded-errors

The error itself is benign and only means that the ResizeObserver was not able to deliver all observations within a single animation frame. It may be safely ignored, but it may be necessary to instruct your framework to ignore it as well to prevent unnecessary side-effects and error-handling.

They provide a helper function to ignore these errors in test environments.

🙂 Pros:

🤬 Cons:

Open questions:

A. Should we use this helper function in production?

B. If we use this helper function in production:

The proposition

For our tests

For production:

WDYT?

The plan

  1. We wait for our cc-logs branch to be merged (it brings the @lit-labs/virtualizer dependency)
  2. We add @lit-labs/virtualizer helper function in our tests so we don't see these errors anymore
  3. We wrap and expose the helper function in a lib
    • It will be easy for npm users
    • It will be tricky for CDN users but I don't think we go so far
  4. We document how to use it
  5. We apply this in the console

WDYT?

pdesoyres-cc commented 1 year ago

As said by Elantha

Most of the time the situation isn't really an error, just a notification. That's probably why Chrome and Firefox have decided not to report the errors in the console.

Because of that, I would not try to implement the solution 1.

I find the lit-labs implementation very smart so I would be ok with solution 2.

roberttran-cc commented 1 year ago

I agree, I like solution 2 better.

Thank you for the analysis, it helps so much with decision making! :pray:

florian-sanders-cc commented 1 year ago

Thanks a lot for all the context and explanation! :raised_hands: Here's my take based on that:

About the solution 1 within the tests: we went for a quick & dirty solution when we first implemented a11y tests. We also catch other errors (a ChartJS / Canvas issue if I remember correctly). Like the resize observer issue, we chose not to "silence" it at the time because we were unsure of its impact. It would be better to silence it and open a proper issue about this?