Nothing Special   »   [go: up one dir, main page]

Page MenuHomePhabricator

Bug 1676791 - Part 3: Implement the computation of timing.
ClosedPublic

Authored by boris on Oct 21 2021, 2:04 AM.

Details

Summary

This patch focus on the timing computation of animation effects. We have
to compute the correct progress based on the scroll offsets. Now we
simplify the implementation only from 0% to 100%. The test cases will be
added in the last patch once we hook the scroll timeline to the CSS
animation.

Diff Detail

Repository
rMOZILLACENTRAL mozilla-central
Lint
Lint Not Applicable
Unit
Tests Not Applicable
Build Status
Buildable 379382
Build 474219: arc lint + arc unit

Event Timeline

There are a very large number of changes, so older changes are hidden. Show Older Changes

Thanks for updating. Now this looks much better. What I was in fact suggesting is, something normalization of scroll timeline's time duration.

So for example, given that there are two different scrollers, one is 100px scrollable height and 10px scroll port, the other is 100px scrollable height and 50px height. With these scrollers, scrollTop=10px in the first one is at 1/10 position, scrollTop=5px in the second one is also at 1/10, can we represent both as a single same value?I believe we can.

Say, if we have a fake TimingParams::mDuration for all scroll timelines, say if the duration is 100, Then GetCurrentTimeAsDuration for scroll timelines for both cases would return same 10? Then GetCurrentTimeAsDouble would return 0.1.

So a question is what the reasonable fake TimingParam::mDuration is. Maybe INT64_MAX - 1, I am less sure though? (Given that INT64_MAX is a special value).

So for example, given that there are two different scrollers, one is 100px scrollable height and 10px scroll port, the other is 100px scrollable height and 50px height. With these scrollers, scrollTop=10px in the first one is at 1/10 position, scrollTop=5px in the second one is also at 1/10, can we represent both as a single same value?I believe we can.

The formula of layout scroll range is: scrollable height - scroll port height, so let me update the first case to use 110px scrollable height.

Let's calculate this again:
Case 1:
scrollable rect height: 110px;
scroll port height: 10px;
so it's the scroll range height is: 110 - 10 = 100px;
With position: 10px, the progress is 10/100 = 0.1

Case 2:
scrollable rect height: 100px;
scroll port height: 50px;
so it's the scroll range height is: 100 - 50 = 50px;
With position: 5px, the progress is 5/50 = 0.1

Say, if we have a fake TimingParams::mDuration for all scroll timelines, say if the duration is 100, Then GetCurrentTimeAsDuration for scroll timelines for both cases would return same 10? Then GetCurrentTimeAsDouble would return 0.1.

So a question is what the reasonable fake TimingParam::mDuration is. Maybe INT64_MAX - 1, I am less sure though? (Given that INT64_MAX is a special value).

So, we keep TimingParam::mDuration be always a fixed value for scroll-timeline (e.g. let's use INT64_MAX - 1 now),
and let GetCurrentTimeAsDuration() return 0.1 * (INT64_MAX - 1), for Case 1 and Case 2. Therefore, we don't have to update AnimationEffect::GetLocalTime() because GetCurrentTimeAsDuration() work properly for scroll-timeline.

INT64_MAX represents the forever timestamp, so using INT64_MAX - 1 is ok to me now because we don't have the progress which is more than 100% for scroll-timeline. Therefore, we don't let GetCurrentTimeAsDuration() overflow.

Let's try this normalization. Thanks.

Say, if we have a fake TimingParams::mDuration for all scroll timelines, say if the duration is 100, Then GetCurrentTimeAsDuration for scroll timelines for both cases would return same 10? Then GetCurrentTimeAsDouble would return 0.1.

Well, GetCurrentTimeAsDouble() just converts the returned value of GetCurrentTimeAsDuration() into a double value. Why is it 0.1 in this case?

INT64_MAX represents the forever timestamp, so using INT64_MAX - 1 is ok to me now because we don't have the progress which is more than 100% for scroll-timeline. Therefore, we don't let GetCurrentTimeAsDuration() overflow.

Oops. Need to make sure the animation playback rate shouldn't be more than 1.0. Otherwise, we may overflow. It seems only web animation APIs can update the playback rate, so it's fine for now.

Say, if we have a fake TimingParams::mDuration for all scroll timelines, say if the duration is 100, Then GetCurrentTimeAsDuration for scroll timelines for both cases would return same 10? Then GetCurrentTimeAsDouble would return 0.1.

Well, GetCurrentTimeAsDouble() just converts the returned value of GetCurrentTimeAsDuration() into a double value. Why is it 0.1 in this case?

I am sorry for the confusion. I meant 0.1 portion of the entire duration. Anyway my point is it's a normalized duration.

Say, if we have a fake TimingParams::mDuration for all scroll timelines, say if the duration is 100, Then GetCurrentTimeAsDuration for scroll timelines for both cases would return same 10? Then GetCurrentTimeAsDouble would return 0.1.

Well, GetCurrentTimeAsDouble() just converts the returned value of GetCurrentTimeAsDuration() into a double value. Why is it 0.1 in this case?

It seems GetCurrentTimeAsDouble() converts the millisecond to microsecond, so it may be 100000 in this example. I will double-check this. If it's possible to get a very large value, we may use a different fixed duration (e.g. 1000).

Code analysis found 1 defect in the diff 508051:

  • 1 defect found by clang-tidy

You can run this analysis locally with:

  • ./mach static-analysis check --outgoing (C/C++)

For your convenience, here is a patch that fixes all the clang-format defects (use it in your repository with hg import or git apply -p0).


If you see a problem in this automated review, please report it here.

You can view these defects on the code-review frontend and on Treeherder.

I've been assuming if we tweak AnimationEffect::SpecifiedTiming for scroll linked animations, we don't need to tweak in each functions in KeyframeEffect?

dom/animation/ScrollTimeline.cpp
12

Let's use 100 sec as Chrome does.

70–71

Setting vertical case values unconditionally looks odd. If you'd want to optimize the case, MOZ_LIKELY is would optimize the case (I hope).

I've been assuming if we tweak AnimationEffect::SpecifiedTiming for scroll linked animations, we don't need to tweak in each functions in KeyframeEffect?

So something like:

AnimationEffect::SpecifiedTiming() {
  if (mAnimation->UsingScrollTimeline()) {
    return mAnimation->GetTimeline()->GenerateTiming();
  }
  return mTiming;
}

It's worth to try. :)

I've been assuming if we tweak AnimationEffect::SpecifiedTiming for scroll linked animations, we don't need to tweak in each functions in KeyframeEffect?

So something like:

AnimationEffect::SpecifiedTiming() {
  if (mAnimation->UsingScrollTimeline()) {
    return mAnimation->GetTimeline()->GenerateTiming();
  }
  return mTiming;
}

It's worth to try. :)

I am assuming the GenerateTiming() could be a constexpr and the name could be simpler something like ComputedTiming or some such.

boris marked 2 inline comments as done.

Update SpecifiedTiming()

dom/animation/ScrollTimeline.h
109–111

SpecifiedTiming() returns a const reference, so we need a place to store |sTiming|.

Besides, constexpr function cannot use a static variable inside the function, and TimingParams cannot be a const variable with non-zero mDuration. (We use StickyTimeDuration::FromMillisecond() in the constructor, and StickyTimingDuration doesn't support a constructor to accept the non-zero value.) I tried to move the static variable outside this function but got a link error. So I didn't do that in this patch.

dom/animation/ScrollTimeline.h
109–111

I probably know what happens on my wip. I will upload a separate patch to make this constexpr.

Basically this looks good. I'd like to review again with the change that GetCurrentTimeAsDuration returns 1 in unscrollable scroller cases. Thanks!

dom/animation/ScrollTimeline.cpp
61–66

Can we use nsLayoutUtils::FindScrollableFrameFor?

69–71

As I commented in D129100, this check is not necessary.

dom/animation/ScrollTimeline.h
102

I do want to drop Get prefix since in our coding style, Get prefix basically means the function might return null (or fail), and in this case the function should be infallible. That said, I don't have any great idea about the name. so GetTiming is fine for now.

dom/animation/ScrollTimeline.cpp
61–66

We can, but we don't have to because FindScrollableFrameFor() calls GetScrollFrameFromContent() again.

nsIScrollableFrame* nsLayoutUtils::FindScrollableFrameFor(
    nsIContent* aContent) {
  nsIFrame* scrollFrame = GetScrollFrameFromContent(aContent);
  return scrollFrame ? scrollFrame->GetScrollTargetFrame() : nullptr;
}
69–71

We can drop this from D129100, but not here. We have to filter out zero range (so avoid divided by zero) See double progress = position / range; in the following lines.

Maybe return a special value, e.g. TimeDuration::FromMilliseconds(1) or TimeDuration(0)?

dom/animation/ScrollTimeline.cpp
61–66

What I suggested is just using FindScrollableFrameFor instead of the pair of GetScrollFrameFromContent and GetScrollTargetFrame. It also makes the thing worse?

dom/animation/ScrollTimeline.cpp
69–71

right. What I supposed is progress = 1 in such cases. then duration should be SCROLL_TIMELINE_DURATION_MILLISEC.

dom/animation/ScrollTimeline.cpp
69–71

Just FYI, our nsIScrollableFrame is generated if the target element is a scroll container. So it should ensure that if there's a valid nsIScrollableFrame, scroll linked animations tied to the scroll container should be active. So the null check for the nsIScrollableFrame above should work as expected.

An overflow:hidden element should be a scroll container, overflow: visible should NOT be, FWIW. overflow: auto should be, FWIW, IIUC.

dom/animation/ScrollTimeline.cpp
61–66

The reason is: we need to use

  1. nsIFrame* to get the writing mode, and
  2. nsIScrollFrame* to get the scroll offset/range.

So if we use FindScrollableFrameFor(), we can get nsIScrollFrame*. However, we still have to do_QueryFrame(scrollFrame) to get its nsIFrame. I would like to avoid using do_QueryFrame() because it does more things.

dom/animation/ScrollTimeline.cpp
61–66

Now I see what you mean. That's fair, fine for now. (Though I'd prefer do_QueryFrame way, the old fashion way should eventually go way).

dom/animation/ScrollTimeline.cpp
69–71

Returning TimeDuration::FromMilliseconds(SCROLL_TIMELINE_DURATION_MILLISEC); causes some wpt failures:

  1. https://searchfox.org/mozilla-central/source/testing/web-platform/tests/scroll-animations/css/at-scroll-timeline-before-phase.html
  2. some subtests in at-scroll-timeline-element-offsets.html and at-scroll-timeline-offset-invalidation.tentative.html

I'm checking what happened.

dom/animation/ScrollTimeline.cpp
69–71

The former case should fail since we don't yet implement start and end offsets, no?

(I haven't looked into the latter cases)

dom/animation/ScrollTimeline.cpp
69–71

It expects we don't apply scroll-animations (the expected width is 0px). But we make the progress 100%, so get 200px width.

dom/animation/ScrollTimeline.cpp
69–71

But these tests use a specific scroller, so perhaps we don't have to care about them.

dom/animation/ScrollTimeline.cpp
69–71

But these tests use a specific scroller, so perhaps we don't have to care about them.

Right. We don't need to care about them because the root is not scrollable, so it should be 100%. Thanks for checking this behavior in Chrome. :)

dom/animation/ScrollTimeline.cpp
69–71

The former case should fail since we don't yet implement start and end offsets, no?

Yes. we are using 0% to 100% in all cases, so it's impossible to go into before phase.

boris marked 6 inline comments as done.

Addressed comments

boris marked an inline comment as done.

Tweak comment and drop unused include

dom/animation/ScrollTimeline.cpp
69–73

GetAvailableScrollingDirections() compares the range with one device pixel and it looks more reasonable, so I use it now.

An alternative way: just check range below

double progress = range > 0.0 ? position / range : 1.0

Either way is ok to me.

dom/animation/ScrollTimeline.cpp
68

Why did you drop the null check for nsIScrollableFrame*? It is necessary, a valid nsIFrame doesn't mean it has a valid nsIScrollableFrame. (that's the reason why I prefer just using FindScrollableFrameFor).

79–80

It wasn't immediately clear to me why we reduce scrollRange.x (or .y). Can you please elaborate the reason? And it ended up reminding me that scroll position in RTL would be negative. It also reminds me that just using the writing mode for the nsIFrame might be wrong.

Did you ever test in RTL cases? At least for me this scrollOffset.x - scrollRange.x looks incorrect in RTL.

dom/animation/ScrollTimeline.cpp
68

Oops. Missed this update. Sorry about this.

79–80

Thanks for pointing this out. This is definitely wrong.

It wasn't immediately clear to me why we reduce scrollRange.x (or .y)

I subtract scrollRange.x because the formula in the spec is offset - start offset. However, I was wrong because scrollFrame->GetScrollPosition() aligns the returned value to the scrollframe position already, i.e.

nsPoint GetScrollPosition() const {
  return mScrollPort.TopLeft() - mScrolledFrame->GetPosition();
}

So we don't have to subtract scrollRange.x/y.

Did you ever test in RTL cases? At least for me this scrollOffset.x - scrollRange.x looks incorrect in RTL

I just tested it, and I was wrong. Basically, using abs(scrollOffset.{x|y}) is enough for both RTL and LTR.

Fix the computation for right to left writing mode

r+ for now, this change should have at least one RTL test case.

This revision is now accepted and ready to land.Nov 18 2021, 9:52 PM

This revision requires a Testing Policy Project Tag to be set before landing. Please apply one of testing-approved, testing-exception-unchanged, testing-exception-ui, testing-exception-elsewhere, testing-exception-other. Tip: this Firefox add-on makes it easy!

r+ for now, this change should have at least one RTL test case.

I added the test (using vertical-rl) into the last patch already.

boris removed a reviewer: Restricted Project.Nov 18 2021, 11:12 PM

Address final comments and rebased

Move test and ready to land

boris added a commit: Restricted Diffusion Commit.Feb 18 2022, 7:45 PM