Bug 52988 - REGRESSION (r75555): Safari RSS sidebar jiggles when scrolling
Summary: REGRESSION (r75555): Safari RSS sidebar jiggles when scrolling
Status: RESOLVED FIXED
Alias: None
Product: WebKit
Classification: Unclassified
Component: Layout and Rendering (show other bugs)
Version: 528+ (Nightly build)
Hardware: All All
: P1 Major
Assignee: Nobody
URL:
Keywords: InRadar, Regression
Depends on: 53049
Blocks:
  Show dependency treegraph
 
Reported: 2011-01-23 19:48 PST by mitz
Modified: 2011-05-22 18:02 PDT (History)
8 users (show)

See Also:


Attachments
Patch (27.21 KB, patch)
2011-04-14 00:22 PDT, Andy Estes
mjs: review+
Details | Formatted Diff | Diff

Note You need to log in before you can comment on or make changes to this bug.
Description mitz 2011-01-23 19:48:49 PST
<rdar://problem/8905599>

* STEPS TO REPRODUCE
1. Navigate to <feed://images.apple.com/main/rss/hotnews/hotnews.rss>
2. Drag the vertical scroller down

* RESULTS
The sidebar jiggles vertically as the content scrolls.

Caused by r75555 <http://trac.webkit.org/changeset/75555>, which made scroll events asynchronous. WebKit now lets the sidebar draw after scrolling and before the scroll event handler gets a chance to reposition it.

See also bug 52790.
Comment 1 Darin Adler 2011-01-23 19:52:16 PST
Getting the scroll event before repainting happens allows this class of user interface implementation. I hope there’s a way we can restore that.
Comment 2 mitz 2011-01-23 19:53:13 PST
* NOTES
1. Safari RSS is also presenting the “doesn’t scroll far enough” symptoms from bug 52790.
2. The content workaround from bug 52790 is unlikely to help, since handling the scroll event synchronously is vital to avoiding jiggling.
Comment 3 Mihai Parparita 2011-01-23 21:24:59 PST
This is unfortunate.

Is using position: fixed an alternative here? It looks like the code (slipDiffScroll) tries to be clever if the sidebar is taller than the viewport area, so a scroll event handler will be necessary regardless in those cases (where the sidebar needs to scroll).

Darin: any ideas how to handle this use-case while still keeping the scroll event async?
Comment 4 Darin Adler 2011-01-24 08:10:59 PST
(In reply to comment #3)
> Darin: any ideas how to handle this use-case while still keeping the scroll event async?

I think that as long as the scroll event comes up before any repainting we’d be OK. It all depends what you mean by “async”. I believe the painting is also async, so the timing is what matters.
Comment 5 Darin Adler 2011-01-24 08:11:22 PST
(In reply to comment #3)
> Is using position: fixed an alternative here?

It’s not.

> It looks like the code (slipDiffScroll) tries to be clever if the sidebar is taller than the viewport area, so a scroll event handler will be necessary regardless in those cases (where the sidebar needs to scroll).

That’s right. That’s why position: fixed is not suitable.
Comment 6 Darin Fisher (:fishd, Google) 2011-01-24 10:10:47 PST
We should be able to fire the "scroll" event at next animation time, which happens just before painting.  (The "animation time" I'm referring to is the time when requestAnimationFrame callbacks run.)
Comment 7 James Robinson 2011-01-24 11:14:54 PST
(In reply to comment #5)
> (In reply to comment #3)
> > Is using position: fixed an alternative here?
> 
> It’s not.
> 
> > It looks like the code (slipDiffScroll) tries to be clever if the sidebar is taller than the viewport area, so a scroll event handler will be necessary regardless in those cases (where the sidebar needs to scroll).
> 
> That’s right. That’s why position: fixed is not suitable.

It looks like this layout is bimodal - either the sidebar takes its static position, or it acts as a position:fixed element.  The page would look a _lot_ better if the scroll listener was used to switch the element from being position:absolute to position:fixed when scrolling past the cutoff point, or when resizing the window.  That way there might be a hitch when switching between the two modes (but that looks jumpy anyway) but the scrolling will be synced up outside of that switch.

Longer-term this is a fairly common UI that CSS should be expanded to express declaratively.  It's just not going to work very well in script.
Comment 8 James Robinson 2011-01-24 11:22:23 PST
See http://code.google.com/p/chromium/issues/detail?id=2 for an example of this.  Sadly the javascript is compressed, but the left-hand bar with id "meta-float" switches between position:relative and position:fixed depending on the scroll offset.  It looks much better than the rss page.
Comment 9 Darin Adler 2011-01-24 11:50:19 PST
Sure, it would be neat to re-implement Safari RSS with a new technique. But note that this code has been working well since Safari 2. I don’t agree that it is “not going to work very well in script”. It has worked fine. Yes, it might be more efficient and more elegant to do it in some modern way.

Lets separate the “it can be done better” issue from the “we incompatibly changed the semantics of the scroll event” issue. If we can do something that keeps the existing Safari RSS working that would be extremely helpful.
Comment 10 James Robinson 2011-01-24 11:51:52 PST
(In reply to comment #9)
> Sure, it would be neat to re-implement Safari RSS with a new technique. But note that this code has been working well since Safari 2. I don’t agree that it is “not going to work very well in script”. It has worked fine. Yes, it might be more efficient and more elegant to do it in some modern way.
> 
> Lets separate the “it can be done better” issue from the “we incompatibly changed the semantics of the scroll event” issue. If we can do something that keeps the existing Safari RSS working that would be extremely helpful.

This may have worked in previous versions of Safari, but not in other browsers.
Comment 11 Darin Adler 2011-01-24 11:58:45 PST
(In reply to comment #10)
> This may have worked in previous versions of Safari, but not in other browsers.

Yes, I understand.

Since it’s been working in WebKit for 8 years there is a chance there is some other content that depends on it.
Comment 12 Darin Fisher (:fishd, Google) 2011-01-24 14:36:30 PST
(In reply to comment #11)
> Since it’s been working in WebKit for 8 years there is a chance there is some other content that depends on it.

That's right, but this is part of the calculated risk of trying to fix the scroll event to be more compatible with other browsers (and the spec).  The hope is that the marketshare of other browsers means that we'll have some insulation here.  So far the only reports of regressions have been with WebKit-specific HTML/JS, which makes sense, and fortunately "we" have the option to change that HTML/JS.
Comment 13 Dave Hyatt 2011-01-24 14:42:03 PST
I think a superior implementation would both be asynchronous and ensure the event fires before a stale repaint.  Allowing stale repaints to happen is just poor behavior, and we shouldn't be using other browsers as an excuse for poor behavior.

Similarly, firing the event synchronously is also poor behavior.

I see no reason why we can't improve the implementation of asyc scrolling to keep these user interfaces working.

Keep in mind that some of the reason you don't see some of these nifty UI systems on the Web is that in other browsers have subpar (albeit async) onscroll implementations.

Let's fix this the right way and get the best of both worlds.
Comment 14 mitz 2011-01-24 15:52:25 PST
From an API standpint, the “asynchronous” nature of scroll events only really manifests itself in programmatic scrolling, i.e. in whether a the scroll event handler is entered before the script that modified the scroll position returned. r75555 decoupled the scroll event dispatch from the scrolling in order to satisfy this asynchronicity requirement, which introduced some issues. I think we should instead consider something else that will satisfy the requirement, which is to make “scrolling” itself decoupled from setting the scroll position by script. What I’m proposing is that setting scrollX, scrollY etc. (or calling scrollTo, scrollBy) will not scroll synchrnously (and thus not trigger a repaint), but just enqueue the scrolling for after return from script. Of course, the DOM will report the new scroll position when asked. It’s just committing to the ScrollView (or RenderLayer), and the subsequent scroll events, that would be deferred.
Comment 15 Mihai Parparita 2011-01-24 16:33:37 PST
(In reply to comment #8)
> See http://code.google.com/p/chromium/issues/detail?id=2 for an example of this.  Sadly the javascript is compressed, but the left-hand bar with id "meta-float" switches between position:relative and position:fixed depending on the scroll offset.  It looks much better than the rss page.

I looked into implementing this approach, but I ran into 53049 (on code.google.com the whole document scrolls, on Safari RSS there's only a div with overflow: overlay).
Comment 16 Mihai Parparita 2011-01-24 16:58:03 PST
(In reply to comment #14)
> I think we should instead consider something else that will satisfy the requirement, which is to make “scrolling” itself decoupled from setting the scroll position by script.

While this would solve this particular problem, it doesn't help in other aspects:
- We would still have to execute scripts before able to paint the results of scrolls, which means that we're more likely to have jerky scrolling.
- We would have divergent behavior from Firefox and IE (the difference in behavior would be visible to pages, WebKit would repaint later when setting scrollTop than when setting other properties).

Perhaps the best compromise for now is to have a mode switch, so that web content gets async events, but other WebKit clients get the legacy behavior (I'm not sure how easy it would be to get Safari RSS on the legacy codepath).
Comment 17 Peter Kasting 2011-01-24 18:18:46 PST
Comment 13 sounds right to me.

For comment 14, consider the case of smooth scrolling by enabling the code in ScrollAnimatorWin, which breaks scrolls into multiple pieces.  These each trigger a separate onscroll event.  We need to ensure that we don't get any scary re-entrancy if we run script synchronously in response to these.  I don't know if we do or not, but Hyatt's comment 13 makes me feel less nervous.
Comment 18 Darin Fisher (:fishd, Google) 2011-01-24 22:07:32 PST
(In reply to comment #13)
> I think a superior implementation would both be asynchronous and ensure the event fires before a stale repaint.

Yeah, this is essentially what I meant by comment #6.

However, James pointed out that if we do that then we are necessarily putting script execution on the critical path for scrolling.  This means that it limits our ability to optimize scrolling (e.g., imagine a background thread performing the scroll animation).  Do we really want to give up this kind of optimization?  Maybe so.

Maybe the better answer is to address this use case with CSS as James mentions in comment #7.  If we had that, then we could make the scroll event less useful in exchange for better opportunities to optimize scrolling.
Comment 19 mitz 2011-02-16 13:02:53 PST
(In reply to comment #11)
> (In reply to comment #10)
> > This may have worked in previous versions of Safari, but not in other browsers.
> 
> Yes, I understand.
> 
> Since it’s been working in WebKit for 8 years there is a chance there is some other content that depends on it.

Here is an example from a major website:

1) Navigate to <http://www.ibm.com/ibm100/us/en/?lnk=ibmhpls1/Corp/events/centennial_launch>
2) Click “A message from Sam Palmisano”
3) Scroll the page

Result: Jiggling

Can we revert r75555 until we come up with a solution that doesn’t break existing content?
Comment 20 Mihai Parparita 2011-02-16 13:08:51 PST
(In reply to comment #19)
> 1) Navigate to <http://www.ibm.com/ibm100/us/en/?lnk=ibmhpls1/Corp/events/centennial_launch>
> 2) Click “A message from Sam Palmisano”
> 3) Scroll the page
> 
> Result: Jiggling
> 
> Can we revert r75555 until we come up with a solution that doesn’t break existing content?

The page has jiggling in Firefox 3.6 and IE 8, so I think the authors just didn't think of this problem (and if they had, they would have used position: fixed).
Comment 21 James Robinson 2011-02-16 13:12:29 PST
(In reply to comment #19)
> (In reply to comment #11)
> > (In reply to comment #10)
> > > This may have worked in previous versions of Safari, but not in other browsers.
> > 
> > Yes, I understand.
> > 
> > Since it’s been working in WebKit for 8 years there is a chance there is some other content that depends on it.
> 
> Here is an example from a major website:
> 
> 1) Navigate to <http://www.ibm.com/ibm100/us/en/?lnk=ibmhpls1/Corp/events/centennial_launch>
> 2) Click “A message from Sam Palmisano”
> 3) Scroll the page
> 
> Result: Jiggling
> 
> Can we revert r75555 until we come up with a solution that doesn’t break existing content?

I tried opening that in the latest Firefox build and it jiggles like crazy.  Seems like a site bug to me, not a WebKit one.
Comment 22 Mihai Parparita 2011-02-16 13:19:30 PST
As far as how to solve this regression with Safari RSS's sidebar, using requestAnimationFrame (http://webstuff.nfshost.com/anim-timing/Overview.html) may be an option. That way you can invoke the custom sidebar sizing/moving logic before painting. In your scroll event handler you'd just set a flag indicating that scrolling is happening, and start requesting callbacks before paintaing (this does mean that the first frame after scrolling starts will not have the new logic, but I doubt that's observable).

Something like:

var isScrolling = false;
var isScrollingTimeout = 0;

// scroller is the div with ID apple-rss-scroller, which currently has slipDiffScroll as its scroll event listener

scroller.onscroll = function() {
  if (!isScrolling) {
    isScrolling = true;
    window.requestAnimationFrame(slipDiffScroll, scroller);
  }
  
  if (isScrollingTimeout) {
    window.clearTimeout(isScrollingTimeout);
  }
  
  // Keep updating for some time after the scroll event was received, in the case of smooth scrolling and other scenarios where we don't fire a scroll event for every frame
  isScrollingTimeout = window.setTimeout(function() {
    isScrolling = false;
  }, 100);
};


function slipDiffScroll() {
  // Current implementation of slipDiffScroll
  
  if (isScrolling) {
    window.requestAnimationFrame(slipDiffScroll, scroller);
  }
}
Comment 23 Andy Estes 2011-04-13 21:04:38 PDT
(In reply to comment #22)
> As far as how to solve this regression with Safari RSS's sidebar, using requestAnimationFrame (http://webstuff.nfshost.com/anim-timing/Overview.html) may be an option. 

Apple's WebKit port doesn't enable REQUEST_ANIMATION_FRAME, and I'm not sure this bug is sufficient justification for taking the risk of enabling it at this point.

The better approach is probably to add a quirk that dispatches ScrollEvent synchronously under certain circumstances.
Comment 24 Andy Estes 2011-04-14 00:22:21 PDT
Created attachment 89544 [details]
Patch
Comment 25 Maciej Stachowiak 2011-04-14 00:44:46 PDT
(In reply to comment #22)
> As far as how to solve this regression with Safari RSS's sidebar, using requestAnimationFrame (http://webstuff.nfshost.com/anim-timing/Overview.html) may be an option. That way you can invoke the custom sidebar sizing/moving logic before painting. 

So here's something I don't get. Why is running a requestAnimationFrame script callback before painting ok, but invoking the scroll event before painting would, per comment #18, be bad? If requestAnimationFrame is an acceptable API, then surely the right long-term fix is Hyatt's suggestion from comment #13.
Comment 26 James Robinson 2011-04-14 00:48:16 PDT
requestAnimationFrame does not provide any assurance that the callback will always run before painting (either before scrolling or after)
Comment 27 Maciej Stachowiak 2011-04-14 00:50:57 PDT
Comment on attachment 89544 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=89544&action=review

> Source/WebCore/dom/EventQueue.cpp:43
> +    return applicationIsSafari() && (document->url().protocolIs("feed") || document->url().protocolIs("feeds"));

Maybe this would make more sense as a preference setting, then it could be customized per view. That would be a cleaner way to do the check, I think, and it would avoid the problem in applicationIsSafari cited below.

> Source/WebCore/platform/RuntimeApplicationChecks.cpp:58
> +    static bool isSafari = mainBundleIsEqualTo("com.apple.Safari") || mainBundleIsEqualTo("com.apple.WebProcess");

This should have a FIXME, because long-term, WebProcess will not be used only by Safari. Or perhaps there is a better way to check specifically for Safari's WebProcess, in which case maybe it would be wise to future-proof this now.
Comment 28 Maciej Stachowiak 2011-04-14 01:02:30 PDT
(In reply to comment #26)
> requestAnimationFrame does not provide any assurance that the callback will always run before painting (either before scrolling or after)

Then I guess using it wouldn't actually solve the problem.
Comment 29 Andy Estes 2011-04-14 01:18:44 PDT
(In reply to comment #27)
> (From update of attachment 89544 [details])
> View in context: https://bugs.webkit.org/attachment.cgi?id=89544&action=review
> 
> > Source/WebCore/dom/EventQueue.cpp:43
> > +    return applicationIsSafari() && (document->url().protocolIs("feed") || document->url().protocolIs("feeds"));
> 
> Maybe this would make more sense as a preference setting, then it could be customized per view. That would be a cleaner way to do the check, I think, and it would avoid the problem in applicationIsSafari cited below.

It looks like Safari RSS doesn't get its own view; the current view is navigated to the RSS page. This means that a Setting flag would have to be toggled on and off per navigation based on the URL scheme, which seems more difficult than the current approach.

> 
> > Source/WebCore/platform/RuntimeApplicationChecks.cpp:58
> > +    static bool isSafari = mainBundleIsEqualTo("com.apple.Safari") || mainBundleIsEqualTo("com.apple.WebProcess");
> 
> This should have a FIXME, because long-term, WebProcess will not be used only by Safari. Or perhaps there is a better way to check specifically for Safari's WebProcess, in which case maybe it would be wise to future-proof this now.

I'd be happy to do this, although I'm not sure I know precisely how to check if it's Safari's WebProcess, at least not via CF API.
Comment 30 Maciej Stachowiak 2011-04-14 01:52:37 PDT
Comment on attachment 89544 [details]
Patch

Since a setting won't work, r=me, but please add the suggested FIXME and follow up on finding a proper way to identify Safari's WebProcess.
Comment 31 Andy Estes 2011-04-14 03:46:41 PDT
Committed r83832
Comment 32 mitz 2011-05-22 17:45:49 PDT
I keep stumbling upon webpages broken by r75555. Here’s another one: <http://www.howtobearetronaut.com/2011/03/stanley-kubricks-chicago-1949/> when you click on a photo and a “lightbox” opens up, it jiggles when you scroll the page behind it.
Comment 33 James Robinson 2011-05-22 18:00:08 PDT
(In reply to comment #32)
> I keep stumbling upon webpages broken by r75555. Here’s another one: <http://www.howtobearetronaut.com/2011/03/stanley-kubricks-chicago-1949/> when you click on a photo and a “lightbox” opens up, it jiggles when you scroll the page behind it.

Are you also testing these pages in firefox and ie?
Comment 34 mitz 2011-05-22 18:02:09 PDT
(In reply to comment #33)
> (In reply to comment #32)
> > I keep stumbling upon webpages broken by r75555. Here’s another one: <http://www.howtobearetronaut.com/2011/03/stanley-kubricks-chicago-1949/> when you click on a photo and a “lightbox” opens up, it jiggles when you scroll the page behind it.
> 
> Are you also testing these pages in firefox and ie?

No, I test them in Safari 5.0.5.