Open Bug 481129 Opened 11 years ago Updated 8 months ago

HTML5 is slow to resize


(Core :: Layout, defect, P4)




Tracking Status
firefox65 --- affected
firefox66 --- affected
firefox67 --- affected


(Reporter: ian, Assigned: bzbarsky)




(Keywords: perf)


(3 files)

HTML5 is slow to resize in Firefox.

Specifically, once HTML5 is loaded, resizing the window takes an inordinate amount of time as the whole document is reflowed.


There's a tarball:

You have to run the test case with "?slow-browser" on the end of the URL to disable the scripts, which might detract from this issue.
Blocks: 475606
Attached file partial Shark profile
Bidi seems to take some time, text frame reflow also of course, nsSVGIntegrationUtils::UsingEffectsForFrame is a bit surprising.
That tarball seems to be 141 bytes big, which makes me doubt it contains much.  ;)

I'll take a look at the page itself.
Attachment #365228 - Attachment description: Height resize profile text output → Width resize profile text output
OK, so I did two profiles; one of changing the width, one of changing the height.  As expected, once I got my test actually doing the right thing, the height change is very fast: 786 samples, sampling every 20us.  The width change is a lot slower: around 60000 samples sampling every 20us.

For the width-change profile, it looks like it took a bit over a second (60000 samples, sampling every 20us) to do the reflow.  Of that, about 8600 samples are under nsBidiPresUtils::ReorderFrames.  16000 samples or so are nsTextFrame::Reflow, split about like this: 25% gfxTextRun::BreakAndMeasureText, 25% nsIFrame::InvalidateWithFlags, 12% EnsureTextRun, 8% GetFontGroupForFrame, 8% in Reflow itself, and some minor stuff.  The SVG stuff smaug saw is under InvalidateWithFlags, and seems to be pretty minor.

That said, invalidation is doing a heck of a lot of work, walking up deep frame trees, addrefing and releasing event listener managers (while checking for paint event listeners) and in the process frobbing the cycle collection hashtables, etc, etc.  Of the 8000-some hits under invalidation, about half are under nsPresContext::NotifyInvalidation.

Some obvious questions here: Why can't nsContentUtils::GetListenerManager just return a weak pointer?  Same for nsPIDOMEventTarget::GetListenerManager.  Can we cache stuff like the document's inner window, the chrome event handler, etc in prescontext so we don't have to do a bunch of QIing on every invalidation?  Can we simplify this checking for event listeners somehow in general?

Past that, this all sort of makes sense in the width-changing case, since we actually do have to reflow all lines in that situation....  So I'm not sure there's an obvious algorithmic win here; just need to reduce the amount of work we do for deep inner-loop operations (e.g. per-inline-frame or per-textframe) reflow work.

That's one aspect of things.  The other is the window resizing performance.  It takes those 1.2 seconds to respond to every mouse move.  That really sucks.  I just tried the interruptible-reflow build, and it doesn't help much.  I can get it to be a _bit_ more responsive if I tell it to skip no interrupt checks at all, but even that is pretty laggy.  I wonder whether it'd work better if we backed off on interrupts instead of just immediately putting an event in the event queue...
That all makes sense to me.

We could take invalidation out of this profile by moving to before-and-after display list comparison to do the invalidation.

We may be able to do something to avoid bidi ReorderFrames.

If most lines were not changing length then we might be able to do some optimizations but in this testcase at least, they are.

We can probably optimize BreakAndMeasureText a bit for the case where we already know the width of a segment of the text.

Our reflow code is generally crufty and we could probably speed it up in general in various ways. Certainly for inline reflow.

Those are all piecemeal optimizations that might each help a bit but we'll never be able to rewrap tens of thousands of lines instantly. Doing process separation or otherwise doing layout asynchronously with window resize will be necessary to get smooth performance.

Boris, why doesn't interruptible layout help much here? We spend too much time cleaning up after the interrupt?
> Boris, why doesn't interruptible layout help much here? We spend too much time
> cleaning up after the interrupt?

As far as I can tell, we just don't interrupt enough in the "peek for events" mode.  Presumably because there is the one mouse move event when the mouse moves, which triggers just one interrupt, then we end up not interrupting much after that.

For example, if I run the interruptible reflow build like so:


which causes us to interrupt approximately every 500 block lines, then responsiveness during the resize is great.  The window size changes immediately.  If I'm all the way at the bottom of the document, it takes about 3 seconds to get down to reflowing that part.

For comparison, webkit trunk is a bit laggier than that on the resize itself, and takes a bit over a second to get to the bottom after I resize while scrolled to the bottom.  If I change GECKO_REFLOW_INTERRUPT_CHECKS_TO_SKIP to 200 in the above commandline, I get responsiveness similar to webkit trunk without much of a change in how long it takes to complete the reflow.
Note that this page is nice in that it doesn't have much in the way of complex floats, by the way.  If it did, it might never complete reflow with the counter interrupt settings.... :(
I wonder whether it makes sense to interrupt more eagerly for resize reflows....
Maybe I need to look at the interruptible reflow patch again, but what does it mean to "not interrupt enough"? When a mouse-move comes in, we should interrupt the reflow and process it. When we start another reflow and another mouse move comes in, we should interrupt again. How do we get into a state where the mouse has moved but a long resize reflow runs to completion?
I click the resizer in the bottom right of the window, and drag to a new window size.  It takes me about 100ms at most to move the mouse to the new position.  It takes us at least 1.2 seconds to process the ensuing reflow.  This means that the mouse has moved; it's done moving, and we still have reflows to process... Those won't get interrupted, since the mouse is not moving anymore.
So if you keep moving the mouse, we do resize the window smoothly, and it's just the final layout that takes a long time before anything is displayed?
No, if I keep moving the mouse we don't resize smoothly....  and I don't see  interrupts being triggered often enough in that case either.
(tarball fixed; sorry about that)
That brings me back to comment #10 then; what are we doing after a mouse move has happened before we get around to handling it?
I told roc already, but just so it's written down, the problem was that pending event detection was really not working in the interruptible reflow build.  I'll retest once I get that fixed, but for now if I just look for "happening" left mouse button drag events things work quite smoothly.
Depends on: ireflow
Well, with the event detection fixed resizing is not precisely smooth, but a lot better on the url in comment 0.

But it's still bad (though again, better than it used to be) on the HTML5 page at <>.  Ian, are there resize handlers or some such running by any chance?
There shouldn't be any different ones than in the one I gave you. There is one, though, it sets a few variables and a timeout handler, IIRC.
Well, the big difference between the url in cmment 0 and the spec is the scripts (not run in the url in comment 0).  The annotation script just adds a bunch of things to the DOM and shows them, though, right?  It's not dynamically toggling display based on position in the document or anything silly like that?
The annotation script tries to keep one of the boxes (which is position:fixed) in sync with whatever the appropriate box should be shown at the current scroll position. For various reasons, however, it delays most of the computation of that until a couple of seconds after the scroll. It does add a resize handler to catch that case, though; maybe the resize handler is doing something that takes longer than expected?
Ah, that explains what I'm seeing, then.  The resize handler is getting the current scroll position, I expect, which of course triggers a non-interruptible reflow.

I wonder whether we should suppress resize handlers firing until the reflow triggered by the resize completes or something....
Authors have been pushing us in the opposite direction; see bug 114649, bug 457862.
Yes, but in those cases they're complaining because the page layout changes and their resize handler doesn't fire, no?  Here's it's the other way around: the page layout hasn't changed yet, but the resize handler fires (and the things it does force a synchronous layout update).

In either case, that's probably fodder for a followup bug; in the meantime interruptible reflow won't help much with resizing of this page: about 2-3 seconds after you start the resize drag the browser will lock up as the reflow is flushed out.
Priority: -- → P3
Priority: P3 → P4
Component: DOM → DOM: Core & HTML

But this bug isn't about
and that STR doesn't seem to be about resizing.
Ovidiu, did you perhaps comment wrong bug?

I'm not even sure how valid this bug still is. Locally Nightly seems to be faster with resizes than other browsers, at least with

Component: DOM: Core & HTML → Layout
Flags: needinfo?(ovidiu.boca)
You need to log in before you can comment on or make changes to this bug.