Closed
Bug 914854
Opened 11 years ago
Closed 11 years ago
BrowserElementPanning causes sync reflow by quering scrollLeft/Top
Categories
(Core :: DOM: Device Interfaces, defect, P1)
Tracking
()
People
(Reporter: BenWa, Assigned: fabrice)
References
Details
(Keywords: perf, Whiteboard: [c= p=3 s= u=1.2])
Attachments
(1 file, 5 obsolete files)
(deleted),
patch
|
bzbarsky
:
review+
vingtetun
:
review+
|
Details | Diff | Splinter Review |
Profile:
http://people.mozilla.org/~bgirard/cleopatra/#report=1502035bb127ab8c08c3deef3463e54c4880945d
doScroll is responsive for 16% of the time while scrolling the contact app.
Comment 1•11 years ago
|
||
Vivian mentioned this an hour ago. Great confirmation from the profile. Lets fix this. This will help all over the place.
Comment 2•11 years ago
|
||
We can probably avoid the sync reflow here if we track the last value via onscroll.
Updated•11 years ago
|
Comment 4•11 years ago
|
||
Hub,
This is a 1.2 blocker I need you to make your #1 priority as it's blocking a Scrolling FPS regression.
Assignee: nobody → hub
Status: NEW → ASSIGNED
Priority: -- → P1
Whiteboard: [c= p= s= u=] → [c= p= s= u=1.2]
Comment 5•11 years ago
|
||
Pinging Vivien to this, not sure the onscroll is the best solution as we may not have enough information to keep track in the scollevent, plus it means attaching a sync event handler thats constantly fired
We may be able to keep track of the scroll position event ourself, we also need to try avoid doing 2 reads of offsetLeft etc when at least one and probably both are causing sync reflow
Flags: needinfo?(21)
Reporter | ||
Comment 6•11 years ago
|
||
As long as you don't dirty the DOM in between, doing x queries only causes 1 reflows. So we're only going to get the win if we can avoid queries all properties that cause a sync reflow. Otherwise we need to move that work at some point after the primary draw reflow or perhaps the platform can have special cases to answer this particular case without having to reflow. You'd have to ask someone from Layout for that.
Comment 7•11 years ago
|
||
Flags: needinfo?(dbaron)
Flags: needinfo?(bzbarsky)
Assignee | ||
Comment 8•11 years ago
|
||
Wip patch in which we only set the property once per doScroll(). I can't say if we get really better performance or not, but if someone else want to carry on and run test, you're welcome!
Comment 9•11 years ago
|
||
Thanks Fabrice !
I'll take over the patch.
Reporter | ||
Comment 10•11 years ago
|
||
Comment on attachment 803758 [details] [diff] [review]
wip patch
Review of attachment 803758 [details] [diff] [review]:
-----------------------------------------------------------------
::: dom/browser-element/BrowserElementPanning.js
@@ +401,3 @@
> // recalculate scrolling direction
> + let xScrollable = node.scrollWidth > node.clientWidth;
> + let yScrollable = node.scrollHeight > node.clientHeight;
clientWidth/clientHeight will also reflow the dirty nodes I believe. :(
Assignee | ||
Comment 11•11 years ago
|
||
(In reply to Benoit Girard (:BenWa) from comment #10)
> Comment on attachment 803758 [details] [diff] [review]
> wip patch
>
> Review of attachment 803758 [details] [diff] [review]:
> -----------------------------------------------------------------
>
> ::: dom/browser-element/BrowserElementPanning.js
> @@ +401,3 @@
> > // recalculate scrolling direction
> > + let xScrollable = node.scrollWidth > node.clientWidth;
> > + let yScrollable = node.scrollHeight > node.clientHeight;
>
> clientWidth/clientHeight will also reflow the dirty nodes I believe. :(
Ha. Then we should cache them and listen for resizes.
Comment 12•11 years ago
|
||
(In reply to Fabrice Desré [:fabrice] from comment #11)
> (In reply to Benoit Girard (:BenWa) from comment #10)
> > Comment on attachment 803758 [details] [diff] [review]
> > wip patch
> >
> > Review of attachment 803758 [details] [diff] [review]:
> > -----------------------------------------------------------------
> >
> > ::: dom/browser-element/BrowserElementPanning.js
> > @@ +401,3 @@
> > > // recalculate scrolling direction
> > > + let xScrollable = node.scrollWidth > node.clientWidth;
> > > + let yScrollable = node.scrollHeight > node.clientHeight;
> >
> > clientWidth/clientHeight will also reflow the dirty nodes I believe. :(
>
> Ha. Then we should cache them and listen for resizes.
Those attributes can change their value based on any modification to the DOM or styles, not just window resizes, so caching them would probably not do the right thing.
Comment 13•11 years ago
|
||
The basic reason we end up doing layout when you ask for geometry information is that we assume you want up-to-date information.
What information is the script actually looking for and why?
Flags: needinfo?(bzbarsky)
Assignee | ||
Comment 14•11 years ago
|
||
(In reply to Boris Zbarsky [:bz] from comment #13)
> The basic reason we end up doing layout when you ask for geometry
> information is that we assume you want up-to-date information.
>
> What information is the script actually looking for and why?
We have a js implementation of kinetic panning in b2g (it will disappear, don't worry) that basically look for the element to scroll in the DOM, and moves it by changing scrollLeft/scrollTop or calling scrollBy() at regular intervals. We also use scrollWidth and clientWidth to check whether we can scroll at all.
In a typical RAF callback, for a given node we:
- read scrollWidth, scrollHeight, clientWidth, clientHeight.
- assign a new value to scrollLeft and scrollTop.
That leads to lots of "JS is triggering a sync reflow" in profiles. I have profiles where we spend 20% of our time reflowing (this is when scrolling a message thread in Gaia sms app).
Comment 15•11 years ago
|
||
OK. So we have some existing APIs on nsIDOMWindowUtils to get geometry information without flushing. The obvious problem with that approach is that you might get stale geometry information... Do you have some sort of out-of-band information that would tell you that the staleness is ok in this case?
Assignee | ||
Comment 16•11 years ago
|
||
A slightly different approach, where I make sure that we don't flush during the pan handler. That does not prevent all the JS sync reflow, and I could not really see obvious improvements in fps with this patch, but I may have missed something
Comment 17•11 years ago
|
||
Proposed patch following discussion with Vivien.
Comment 18•11 years ago
|
||
(In reply to Fabrice Desré [:fabrice] from comment #16)
> Created attachment 806658 [details] [diff] [review]
> wip 2
>
> A slightly different approach, where I make sure that we don't flush during
> the pan handler. That does not prevent all the JS sync reflow, and I could
> not really see obvious improvements in fps with this patch, but I may have
> missed something
I talked to Vivien last Friday and we discussed a more adequate solution. See comment 17
Assignee | ||
Comment 19•11 years ago
|
||
Comment on attachment 807361 [details] [diff] [review]
Bug 914854 - don't call scrollTop and scrollLeft that often.
Review of attachment 807361 [details] [diff] [review]:
-----------------------------------------------------------------
::: dom/browser-element/BrowserElementPanning.js
@@ +407,2 @@
> xScrollable = node.scrollWidth > node.clientWidth;
> yScrollable = node.scrollHeight > node.clientHeight;
You're still touching these at every doScroll() and they cause a flush.
Comment 20•11 years ago
|
||
(In reply to Fabrice Desré [:fabrice] from comment #19)
> Comment on attachment 807361 [details] [diff] [review]
> Bug 914854 - don't call scrollTop and scrollLeft that often.
>
> Review of attachment 807361 [details] [diff] [review]:
> -----------------------------------------------------------------
>
> ::: dom/browser-element/BrowserElementPanning.js
> @@ +407,2 @@
> > xScrollable = node.scrollWidth > node.clientWidth;
> > yScrollable = node.scrollHeight > node.clientHeight;
>
> You're still touching these at every doScroll() and they cause a flush.
ew. yeah. good point.
back to the drawing board.
Assignee | ||
Comment 21•11 years ago
|
||
Check my patch, it does avoid that at the cost of some c++
Comment 22•11 years ago
|
||
(In reply to Fabrice Desré [:fabrice] from comment #21)
> Check my patch, it does avoid that at the cost of some c++
Indeed this patch show much better result in the profiler.
Interestingly we don't really have any FPS change with b2gperf. That puzzles me.
Updated•11 years ago
|
Whiteboard: [c= p= s= u=1.2] → [c= p=3 s= u=1.2]
Comment 23•11 years ago
|
||
Updated•11 years ago
|
Attachment #806658 -
Attachment is obsolete: true
Updated•11 years ago
|
Attachment #803758 -
Attachment is obsolete: true
Comment 24•11 years ago
|
||
I'll make a try push from m-c instead. The above is with my inbound tree. Not the best idea for green.
Updated•11 years ago
|
Attachment #807361 -
Attachment is obsolete: true
Updated•11 years ago
|
Attachment #806658 -
Attachment is obsolete: false
Comment 25•11 years ago
|
||
Comment 26•11 years ago
|
||
(In reply to Hubert Figuiere [:hub] from comment #17)
> Created attachment 807361 [details] [diff] [review]
> Bug 914854 - don't call scrollTop and scrollLeft that often.
>
> Proposed patch following discussion with Vivien.
adequate solution is to avoid reflows. I think the question is why fabrice's patch still triggers JS reflows now.
Flags: needinfo?(21)
Comment 27•11 years ago
|
||
This bug would result in a big overall improvement. Lets try to get this figured out.
Comment 28•11 years ago
|
||
(In reply to Fabrice Desré [:fabrice] from comment #16)
> Created attachment 806658 [details] [diff] [review]
> wip 2
>
> A slightly different approach, where I make sure that we don't flush during
> the pan handler. That does not prevent all the JS sync reflow, and I could
> not really see obvious improvements in fps with this patch, but I may have
> missed something
Are you sure you are still seeing sync JS reflows? I see a lot of Main Thread IO when there is a
- IPDL:PBrowser:RecvRealTouchMoveEvent
- IPDL:PLayerTransaction:SendPGrallocBufferConstructor
- IPDL:PLayerTransaction:SendUpdate
But I don't see any js sync reflows in the profiler.
For what it worth I'm trying in settings since contacts and sms have custom handlers to update the label based on the position in the list, and I assume that those can trigger js reflows.
Assignee | ||
Comment 29•11 years ago
|
||
Ok, so I was testing with sms using the reference-workload-light and a sms thread with images. I didn't test with the settings app, but your explanation makes sense. The main thread IO with IPC are a profiler bug (it erroneously thinks that the ipc write() happens on the main thread).
So I guess I can clean up the patch a bit and ask for feedback to people that actually know this part of the codebase ;)
Comment 30•11 years ago
|
||
(In reply to Fabrice Desré [:fabrice] from comment #29)
> Ok, so I was testing with sms using the reference-workload-light and a sms
> thread with images. I didn't test with the settings app, but your
> explanation makes sense. The main thread IO with IPC are a profiler bug (it
> erroneously thinks that the ipc write() happens on the main thread).
>
> So I guess I can clean up the patch a bit and ask for feedback to people
> that actually know this part of the codebase ;)
For sms and contacts the plan is to use |position: sticky| ftw (http://coreyford.name/files/position-sticky-presentation/) to avoid JS here.
Updated•11 years ago
|
Flags: needinfo?(dbaron)
Assignee | ||
Comment 31•11 years ago
|
||
Vivien, I removed a lot of code from the BEP.js because I could not find any case where the patch is failing. If you have some, I'll add the DOMWindow code path back in.
Boris, this patch adds a way to scroll elements without flushing layout. This helps a lot with scrolling performance in b2g (I now get consistently >55fps on a leo device)
Assignee: hub → fabrice
Attachment #806658 -
Attachment is obsolete: true
Attachment #810702 -
Flags: review?(bzbarsky)
Attachment #810702 -
Flags: review?(21)
Comment 32•11 years ago
|
||
Thanks Fabrice !
Comment 33•11 years ago
|
||
Fabrice this is awesome.
Comment 34•11 years ago
|
||
Comment on attachment 810702 [details] [diff] [review]
patch
>+ bool ScrollBy(int32_t aDx, int32_t aDy);
This needs much better documentation about what the arguments mean. What I thought they meant is not what this patch implements (but more on that below).
>+ nsIFrame* GetPrimaryFrame(mozFlushType aType, bool aFlushLayout = true);
How about we introduce a Flush_None value for mozFlushType? Esp because what's happening here might not be a layout flush.
>+ nsIScrollableFrame::DEVICE_PIXELS,
Why? I would have expected CSS px here, not device px....
>+ CSSIntPoint after = sf->GetScrollPositionCSSPixels();
That's exploitable. See the comments about how scrollBy can destroy the world.
>+ return (before.x != after.x || before.y || after.y);
Why is the latter 2/3 of this condition correct?
>+nsDOMWindowUtils::ScrollNode(nsIDOMNode* aNode,
This is exposed to web pages, looks like. Is that expected? Seems like it would be better to have this as an Element method only exposed to privileged enough script.
Attachment #810702 -
Flags: review?(bzbarsky) → review-
Assignee | ||
Comment 35•11 years ago
|
||
Now using CSS pixels consistently, and a Flush_None type, along with fixes to glaring mistakes.
(In reply to Boris Zbarsky [:bz] from comment #34)
> That's exploitable. See the comments about how scrollBy can destroy the
> world.
It's not clear to me how I'm supposed to use it without destroying the world though.
>
> >+nsDOMWindowUtils::ScrollNode(nsIDOMNode* aNode,
>
> This is exposed to web pages, looks like. Is that expected? Seems like it
> would be better to have this as an Element method only exposed to privileged
> enough script.
This is not exposed to content, like all the other methods from nsIDOMWindowUtils. I double checked on a web page.
Attachment #810702 -
Attachment is obsolete: true
Attachment #810702 -
Flags: review?(21)
Attachment #810887 -
Flags: review?
Attachment #810887 -
Flags: feedback?(bzbarsky)
Attachment #810887 -
Flags: feedback?(21)
Comment 36•11 years ago
|
||
> It's not clear to me how I'm supposed to use it without destroying the world though.
You don't. What it means is that you can't use "sf" after the call without first checking whether it's died (using an nsWeakFrame).
> This is not exposed to content, like all the other methods from nsIDOMWindowUtils
Er... A bunch of windowutils methods have explicit "is caller chrome?" checks, precisely because in some cases content _can_ get its hands on a windowutils.
Again, this should just be a method on Element, exposed to the relevant scopes. There's no need for the windowutils indirection here.
Assignee | ||
Comment 37•11 years ago
|
||
(In reply to Boris Zbarsky [:bz] from comment #36)
>
> Again, this should just be a method on Element, exposed to the relevant
> scopes. There's no need for the windowutils indirection here.
The caller being in chrome JS, I can't call directly on Element. That's the only reason for the windowutils indirection.
Comment 38•11 years ago
|
||
> The caller being in chrome JS, I can't call directly on Element.
I don't mean mozilla::dom::Element. I mean Element.webidl.
Comment 39•11 years ago
|
||
Comment on attachment 810887 [details] [diff] [review]
patch v2
Review of attachment 810887 [details] [diff] [review]:
-----------------------------------------------------------------
::: dom/browser-element/BrowserElementPanning.js
@@ +395,4 @@
>
> function doScroll(node, delta) {
> + return utils.scrollNode(node, delta.x, delta.y);
> + }
You don't really need the doScroll function if you do that, you can just |isScrolling = utils.scrollNode(target, delta.x, delta.y);| in scroll()
@@ +395,4 @@
>
> function doScroll(node, delta) {
> + return utils.scrollNode(node, delta.x, delta.y);
> + }
The case normally hit by |if (node instanceof Ci.nsIDOMWindow) | is when the body is not scrollable but is bigger than the window when the other cases are for scrollable divs for example.
I don't think there is such cases in Gaia but you still want to check if this can happens. I suggest that you install a small app that contains nothing but a body taller and larger than the window.
@@ +403,5 @@
> }
> if (node.frameElement) {
> return node.frameElement;
> }
> return null;
nit: I guess this function can be written |return node.parentNode || node.frameElement || null;|
@@ +407,5 @@
> return null;
> }
>
> function scroll(delta) {
> + let target;
let's keep target out of this function next to isScrolling and firstScroll
@@ +424,2 @@
> }
> return isScrolling;
let current = root;
let firstScroll = true;
function scroll(delta) {
while (current) {
if (utils.scrollNode(currentTarget, delta.x, delta.y)) {
firstScroll = false;
return true;
}
// TODO The current code looks for possible scrolling regions only if
// this is the first scroll action but this should be more dynamic.
if (!firstScroll) {
return false;
}
current = ContentPanning._findPannable(targetParent(current));
}
// There is nothing scrollable here.
return false;
}
seems easier to read to me (but it could be just for me).
@@ +424,4 @@
> }
> return isScrolling;
> }
> +
no need for the extra whitespace :)
Attachment #810887 -
Flags: feedback?(21) → feedback+
Assignee | ||
Comment 40•11 years ago
|
||
Updated with :
- no more nsIDOMWindowUtils indirection, but a [ChromeOnly] method in Element.idl
- using a weakFrame to check whether we can still touch the scroll frame after scrolling.
Vivien, I put the nsIDOMWindow code back because we need that for a lots of 3rd party apps, including the marketplace.
Attachment #810887 -
Attachment is obsolete: true
Attachment #810887 -
Flags: review?
Attachment #810887 -
Flags: feedback?(bzbarsky)
Attachment #811407 -
Flags: review?(bzbarsky)
Attachment #811407 -
Flags: review?(21)
Comment 41•11 years ago
|
||
Comment on attachment 811407 [details] [diff] [review]
patch v3
>+Element::ScrollBy(int32_t aDx, int32_t aDy) {
Curly on next line, please.
>+ return (before.x != after.x || before.y != after.y);
return before != after;
Please document in the IDL the units, and the fact that the scrolling does not flush? And make the method name, in both IDL and C++, reflect the non-flushing clearly.
r=me with those fixed.
Attachment #811407 -
Flags: review?(bzbarsky) → review+
Comment 42•11 years ago
|
||
Comment on attachment 811407 [details] [diff] [review]
patch v3
Review of attachment 811407 [details] [diff] [review]:
-----------------------------------------------------------------
r+ with nits.
::: dom/base/nsDOMWindowUtils.cpp
@@ +3426,5 @@
> GetComputedStyle(elem, aProperty, getter_AddRefs(style));
> NS_ENSURE_SUCCESS(res, res);
>
> return style->GetPropertyValue(aProperty, aResult);
> }
nit: I think you want to avoid this line change in this file that is unrelated to the patch.
::: dom/browser-element/BrowserElementPanning.js
@@ +389,3 @@
> let firstScroll = true;
> let target;
> let isScrolling = false;
nit: |isScrolling| is not needed anymore, let's remove it.
Attachment #811407 -
Flags: review?(21) → review+
Comment 43•11 years ago
|
||
Also it may be helpful to have a followup for the |if doc instanceof Ci.nsIDOMWindow| if calling |getComputedStyle().overflow[X/Y] can trigger reflows (?) since this is call for each scrolling actions.
Assignee | ||
Comment 44•11 years ago
|
||
Comment 45•11 years ago
|
||
Follow up bug filed?
Assignee | ||
Comment 46•11 years ago
|
||
Comment 47•11 years ago
|
||
Status: ASSIGNED → RESOLVED
Closed: 11 years ago
Resolution: --- → FIXED
Target Milestone: --- → mozilla27
Comment 48•11 years ago
|
||
status-b2g-v1.2:
--- → fixed
status-firefox25:
--- → wontfix
status-firefox26:
--- → fixed
status-firefox27:
--- → fixed
Depends on: 922438
No longer depends on: 922438
You need to log in
before you can comment on or make changes to this bug.
Description
•