Intent to Ship: ResizeObserver

3132 views
Skip to first unread message

Aleks Totic

unread,
Sep 29, 2017, 5:08:44 PM9/29/17
to blink-dev

Intent to Ship: ResizeObserver


Contact emails

ato...@chromium.org


Explainer


https://github.com/WICG/ResizeObserver/blob/master/explainer.md


Spec


Spec: https://wicg.github.io/ResizeObserver/

Tag review: https://github.com/w3ctag/design-reviews/issues/187


Notable final comments on Tag review were:

  • Is this going to be launched as an Origin Trial? If so, this seems to be in good shape.


Summary


ResizeObserver is a new DOM observer API. It is intended to be used for observing size of DOM elements like this:


let ro = new ResizeObserver(entries => {

 for (entry of entries) {

   console.log(entry.target);

   console.log(entry.contentRect);

 }
ro.observe(document.element);


Link to “Intent to Implement” blink-dev discussion


Intent To Implement: ResizeObserver


Link to Origin Trial feedback summary


There was no Origin Trial for ResizeObserver.


The reason why I believe shipping is preferable to Origin Trial is that ResizeObserver has already received a lot of feedback.


After the spec was finalized, Internet produced a polyfill that is widely used:


>>>>> by Philip Walton

The ResizeObserver polyfill has 182,689 monthly downloads on npm (for comparison, the IntersectionObserver polyfill only has 57,453)

According to HTTP Archive, the `ResizeObserver` constructor is used on 667 of the top alexa sites including dropbox.com and uber.com (presumably with the polyfill, so they're slower than they need to be).

<<<<<


We've also gotten feedback from:

- the polyfill implementors, especially que-etc. His conclusion is that people often want to track other box dimensions too.

- bmauer from Facebook hacked on it. Wanted to use it for chat window, wanted to track scroll dimensions, wanted to use it to obtain precise timing information for layout.

- blois@google has used it to propagate iframe sizes to parent.

- Firefox had an intern work on it in summer of 2016. They used our tests to test interoperability, but never shipped.


Feedback can be summarized as:

1) want to observe more sizes, not just content size. (border box size, scroll size)

2)  want previous size reported together with current size.


Implementing 1)  performantly would be challenging in current Layout code.

Implementing 2) is easy, but I have not seen any real world examples where it would be useful.


Both of these would be nice extensions for v2, and can be (mostly) implemented on top of v1.


Is this feature supported on all six Blink platforms (Windows, Mac, Linux, Chrome OS, Android, and Android WebView)?

Yes.


Demo link


To run demos in today's Chrome, launch it with --enable-blink-features=ResizeObserver


Demo on rawgit.


Debuggability


ResizeObserver implements standard observer pattern also used by MutationObserver, and IntersectionObserver. When notification delivery is delayed because of depth restrictions, a window error event is generated, and devtools prints it to console.


Risks

Interoperability and Compatibility


ResizeObserver was discussed at 2016 TPAC.


Edge: Under consideration, priority low,  status here. Greg seemed supportive in person at TPAC.

Firefox: In development, an intern worked on it is 2016. Their implementation passed most of the w-p-t test, and dholbert commented upon the spec #17 #21
Safari: No public signals, status here. Did not protest at TPAC.


Web developers: Positive: polyfill in wide use, see Origin Trial feedback above.


web-platform-tests submitted here. Firefox already used them to check compatibility.


Ergonomics

ResizeObserver can be used in tandem with scroll event to implement "chat window" scrolling to bottom.


Could the default usage of this API make it hard for Chrome to maintain good performance?


No. ResizeObserver can be implemented efficiently, and only runs if observed element's size has changed. If the Element's size has changed, cost of layout dwarfs cost of broadcasting resize observations.


Activation

Will it be challenging for developers to take advantage of this feature immediately, as-is?

No, the polyfill has already been widely used.


Is this feature fully tested by web-platform-tests?


ResizeObserver is fully tested by test suite, and the suite has been submitted to w-p-t repository.


Entry on the feature dashboard


Resize Observer


Dimitri Glazkov

unread,
Sep 29, 2017, 6:12:44 PM9/29/17
to Aleks Totic, blink-dev
LGTM1.

--
You received this message because you are subscribed to the Google Groups "blink-dev" group.
To view this discussion on the web visit https://groups.google.com/a/chromium.org/d/msgid/blink-dev/CAMdyzDuGco-6Nyx%3DQ2cXe%3DzDzaxWPwFJa6XnO9V6ON%2BuCynqhQ%40mail.gmail.com.

Adam Barth

unread,
Sep 30, 2017, 12:42:37 PM9/30/17
to Dimitri Glazkov, Aleks Totic, blink-dev
Have we studied the quality of the user experience and the performance of web content that uses ResizeObserver (by which I mean content written for the polyfill but executed against the built-in implementation)?

The reason I ask is that (as mentioned in the intent-to-implement thread), we had a similar feature in Flutter for a while and were unhappy with the quality and performance of applications that used the feature.  Specifically, we observed that authors often used SizeObserver (Flutter's version of ResizeObserver) notifications to mutate information that changed layout.  That pattern hurt performance because SizeObserver triggered after layout, which meant we needed to layout again.  In particularly bad cases, authors would unwittingly generate cycles whereby their mutations in response to these notifications would trigger the notifications again, ad nauseum.

It's clear from the ResizeObserver specification that the designers of the feature have considered these issues and constructed ResizeObserver to avoid infinite layout loops.  My question is whether we've evaluated how authors use ResizeObserver observer in practice.  Specifically:

1) In practice, how often do authors trigger the layout loop in <https://wicg.github.io/ResizeObserver/#html-event-loop>?  What impact does that have on performance?

2) Because the layout loop is designed to terminate, there can be active notifications that are not delivered before the browser paints.  In practice, how often do authors have pending notifications that are not delivered before the browser paints?  What impact does that have on the quality of the user experience (i.e., flashes of bad layout that are corrected in the next frame when the pending notifications are delivered)?

I asked these questions in the intent-to-implement thread, but it was probably too early for empirical evidence to be available.  Now that the polyfill has significant usage, we might be able to study a representative sample of these usage and get quantitative data about how authors use ResizeObserver in practice.

Thanks for your consideration.

Adam


Dimitri Glazkov

unread,
Oct 1, 2017, 1:59:45 PM10/1/17
to Adam Barth, Aleks Totic, blink-dev
Hi Adam! Long time no blink :)

Thanks for asking these questions, they seem pretty important to answer.

:DG<

Aleks Totic

unread,
Oct 2, 2017, 2:16:52 AM10/2/17
to Adam Barth, blink-dev
1) In practice, how often do authors trigger the layout loop in <https://wicg.github.io/ResizeObserver/#html-event-loop>?  What impact does that have on performance?

2) Because the layout loop is designed to terminate, there can be active notifications that are not delivered before the browser paints.  In practice, how often do authors have pending notifications that are not delivered before the browser paints?  What impact does that have on the quality of the user experience (i.e., flashes of bad layout that are corrected in the next frame when the pending notifications are delivered)?

I asked these questions in the intent-to-implement thread, but it was probably too early for empirical evidence to be available.  Now that the polyfill has significant usage, we might be able to study a representative sample of these usage and get quantitative data about how authors use ResizeObserver in practice.

Instrumenting existing sites, and analyzing their ResizeObserver usage sounds like a great idea. I'll do it.

I have not done such analysis before, so it might take a few days. I'll need a list trace targets to analyze, and I'll have to figure out how to do tracing/analysis.

Aleks

Aleks Totic

unread,
Oct 4, 2017, 8:29:52 PM10/4/17
to Adam Barth, blink-dev

abarth asked:

> 1) In practice, how often do authors trigger the layout loop?  What impact does that have on performance?


> 2) Because the layout loop is designed to terminate, there can be active notifications that are not delivered before the browser paints.  In practice, how often do authors have pending notifications that are not delivered before the browser paints?  What impact does that have on the quality of the user experience (i.e., flashes of bad layout that are corrected in the next frame when the pending notifications are delivered)?


Getting practice data for an unreleased API is not always possible. Luckily, ResizeObserver has a widely used polyfill. @philipwalton has provided a spreadsheet that lists most popular sites using the polyfill.


I've investigated RO usage on these sites using an instrumented binary, and tracing tools. On 9 sites, live usage of RO was detected. Many sites did not trigger RO in my test setup (loading, or window resize).


Result summary


- 8 sites observed between 1 and 4 elements. A single outlier observed 28.

- RO was always used as an element query: children would be rearranged, images loaded depending on size.

- RO was triggered on load, and during window resizing.

- Notifications were handled quickly (<2ms) on 6 sites. On 3 sites, occasionally a notification would trigger a long operation (12, 40, and 90ms).  This happened when site did lots of DOM manipulation inside RO callback.

- No undelivered notifications were observed while tracing. I did see undelivered notifications twice while running in gdb, but this was not consistently reproducible.

- Many of the sites were using React. A popular React component uses the polyfill.


Interpretation


ResizeObserver is being used as intended, to implement responsive components. Main problem with API as used today is too much work being done inside a callback.


Undelivered notifications have not been observed as a problem. This does not mean that they never will be. RO might be widely adopted, and 1000 components start responding to notifications in a single page, webdevs like to push platform to its limits.


The most common performance problem was doing too much DOM manipulation inside a callback. Other event loop callbacks have the same problem. What to do if response to an event involves changing the DOM?


"What is the well-lit path for ResizeObserver if webdevs want to perform DOM manipulation in response to resize?" is an interesting question.

If they perfom DOM manipulation in a RO callback, it might be too slow.

If they post an idle task to perform DOM manipulation, the element's contents will be out of sync with size for 1 frame (bad layout).


There is no 1 size fits all. I think the answer is:

- if your component is changing in response to window resize, post an idle task, DOM is pretty messy during window resizes, no one will notice.

- if you are animating the component, be careful with component design. Either design it so its response to resize is fast enough and skip idle task, or post an idle task but draw your component so that bad layout will not be jarring to the user. (

- the two-years from now answer is use CustomPaint instead :-)


Many sites were written in React. We should survey libraries using ResizeObserver, to make sure they use it appropriately.


Most sites created a new ResizeObserver for every observed element, even the one with 28 observers.


Result details:

ResizeObserver was instrumented to record RO.observe(), RO. deliverNotifications(), and skipped notifications. Page load, and window resize were traced.


uber.com:

Load: 4 observe(), 4 notify() (0.18ms max)

Resize: 4 observer(), 4 notify() (0.18ms max)

Uber got the polyfill by using react-measure-it component. Observes 4 divs. Looks like it is used for layout of the topmost menus.


https://www.visitstockholm.com/

Load: 28 observe, 28 notify (0.2ms max)

Resize: 140 notify (40ms max, rest < 1ms. React.batchedUpdates got triggered inside max)

My favorite. Full custom grid-like layout with ResizeObserver's help. 28 observed elements, feels performant.


jcrew.com

Load: 2 observe, 2 notify (14ms max)

Resize: 3 notify (.5ms max)

React site, might be using the same component as uber.

The 14ms callback was important code that loaded/resized images dynamically, looping through all, checking if they were visible, etc.


https://www.vice.com/en_us

Load: 2 observe, 2 notify (59ms max, something z.moatads.com related happens inside callback)

Resize: 3 notify, 90ms max.

2 Elements observed. Managed to trigger "undelivered notifications" once on load. Observed div wraps ads.


https://www.dropbox.com/

Load: 5 observe, 2 notify (.01ms max)

Resize: 8 notify (0.01ms max)


http://anti-maidan.com/

Observing single div, with no notifications.


https://gg.bet/en/betting

Load: 7 observe, 6 notify (.4ms max)

Resize: 2 notify, (.16ms max)

Max simultaneous notifications: 2

Observing 7 divs. I did manage  to trigger "undelivered notifications" error once after multiple resizes.


https://kau.se

Load: 2 observe, 2 notify (1.5ms max, .5ms inside compiler)

Resize: 5 notify, (1.5ms max, React.setState...)

Max simultaneous notifications: 3

React. Good looking site. Observing menus to change contents in response to resize.


https://busfor.ru/

Load: 5 observe, 3 notify (.25ms max)

Resize: 2 notify (12ms max, 2 recalc styles)


Adam Barth

unread,
Oct 5, 2017, 11:42:56 AM10/5/17
to Aleks Totic, blink-dev
Thanks for collecting this data!  ✨

Adam


--
You received this message because you are subscribed to the Google Groups "blink-dev" group.

Philip Jägenstedt

unread,
Oct 13, 2017, 8:08:52 AM10/13/17
to Adam Barth, Aleks Totic, blink-dev
That's a great analysis indeed, thanks Aleks!

Sounds like you've already discussed this in person at TPAC, and I've poked the Gecko and WebKit bugs, if there are any concerns we should hear about them.

The tests are now merged, but I noticed one of them is a bit funny:

No title for the test, and the Safari failure points to an interop issue entirely unrelated to the feature. Can you check if that is covered by some other test and then work around it?

(Also quite exciting to see just a single Blink-specific test.)

LGTM2!

Chris Harrelson

unread,
Oct 17, 2017, 2:00:35 PM10/17/17
to Philip Jägenstedt, Adam Barth, Aleks Totic, blink-dev
LGTM3!

To unsubscribe from this group and stop receiving emails from it, send an email to blink-dev+unsubscribe@chromium.org.
To view this discussion on the web visit https://groups.google.com/a/chromium.org/d/msgid/blink-dev/CAARdPYd16kcs5h45wWeGD_gMadNXi8uw6inKLZWfgXZZqTC1hA%40mail.gmail.com.

Aleks Totic

unread,
Oct 17, 2017, 2:01:35 PM10/17/17
to Chris Harrelson, Philip Jägenstedt, Adam Barth, blink-dev
\o/

zhangson...@sina.com

unread,
Oct 29, 2017, 10:22:19 PM10/29/17
to blink-dev
在 2017年9月30日星期六 UTC+8上午5:08:44,Aleks Totic写道:

Feedback can be summarized as:

2)  want previous size reported together with current size.


Implementing 2) is easy, but I have not seen any real world examples where it would be useful.


 It's useful when developers want to observe only width / height of elements. I was using RO layouting element's contents based on its width. It was hard to do so with only CSS

Aleks Totic

unread,
Nov 3, 2017, 8:31:49 PM11/3/17
to blink-dev, Domenic Denicola, Dimitri Glazkov, Ian Kilpatrick, Ojan Vafai
In the middle of this "Intent To Ship", Domenic started a discussion about when to use Observer vs. Event pattern.

One issue raised in this discussion was Events vs Observer performance. I wanted to know what the real numbers on this are.

Here they are, and they surprised me:

### Experimental setup:

Observe size of 126 divs during a 2 second animation of width/height.
This would generate ~2x60x126=15120 notifications at 60fps.
A medium size workload. Animations is where performance gets interesting, because of a limited rAF budget.

I ran 4 experiments:

1) All elements are observed by a single ResizeObserver.
This is the recommended observer pattern. For each rAF, there was a single callback that delivered all notifications.
Performance was great.

Total observer time: 69ms
ResizeObserver callback was called 110 times.
98 notifications took < 1ms, 12 < 5ms 

2) Each element is observed by separate ResizeObserver.
This pattern was used in majority of existing sites. For each rAF, 126 callbacks would be called, with 1 notification each.
Performance was surprisingly bad, with notifications taking up 25% of total animation time.

Total observer time: 585ms
ResizeObserver callback was called 14878 times.
Most notifications took < .2ms, but since there were so many of them, total time taken was large

3) All elements are observed by a single ResizeObserver, + manual handler dispatch.
The slowness of experiment #2 surprised me. I was curious whether calling back from C++ to JS was the slow part.
In this experiment, I added manual event dispatch to the notification handler.

Each DOM element was added a "handleResize" method:

function handleResize(entry) {
  elementsResized++;
}
div.handleResize = handleResize;

The ResizeObserver callback used "handleResize" to manually dispatch notifications to all elements:

function roDispatchCallback(entries) {
  callbackCount++;
  for (e of entries)
    e.target.handleResize(e);
}
new ResizeObserver(roDispatchCallback);

Surprisingly, this experiment ran almost as fast as #1:

Total observer time: 75ms
ResizeObserver callback was called 125 times.
103 notifications took < 1ms, 12 took < 5ms

It looks like C++ to JS is the bottleneck.

4) Final experiment: ResizeObserver as an event handler.
For this experiment, instead of calling RO callback routine, I used C++ EventTarget API to emit an event instead.
Everything else remained the same.
This was twice as slow as 2nd experiment, half of the animation time was taken by event notifications. it was 10x as slow as #1.
I think what I am seeing here is the event path traversal penalty mentioned by Ian and Ojan.

Total observer time: 1018ms
Events broadcast: 12980
4 notifications took < 10ms, one <16ms

Detailed results are here:

### Conclusion

Ship it! ResizeObserver as designed today performs 10x better than events.

How should we nudge developers to use ResizeObserver as intended? Maybe build a default RO that calls Element.handleResize?

Aleks
Reply all
Reply to author
Forward
0 new messages