2015-05-03 22:32:37 +03:00
|
|
|
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
|
|
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
2015-04-10 04:34:22 +03:00
|
|
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
|
|
|
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
|
|
|
|
#include "DocumentTimeline.h"
|
|
|
|
#include "mozilla/dom/DocumentTimelineBinding.h"
|
|
|
|
#include "AnimationUtils.h"
|
|
|
|
#include "nsContentUtils.h"
|
2015-09-28 06:38:41 +03:00
|
|
|
#include "nsDOMMutationObserver.h"
|
|
|
|
#include "nsDOMNavigationTiming.h"
|
2015-04-10 04:34:22 +03:00
|
|
|
#include "nsIPresShell.h"
|
|
|
|
#include "nsPresContext.h"
|
|
|
|
#include "nsRefreshDriver.h"
|
|
|
|
|
|
|
|
namespace mozilla {
|
|
|
|
namespace dom {
|
|
|
|
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_INHERITED(DocumentTimeline, AnimationTimeline,
|
|
|
|
mDocument)
|
|
|
|
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(DocumentTimeline,
|
|
|
|
AnimationTimeline)
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_TRACE_END
|
|
|
|
|
|
|
|
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(DocumentTimeline)
|
|
|
|
NS_INTERFACE_MAP_END_INHERITING(AnimationTimeline)
|
|
|
|
|
|
|
|
NS_IMPL_ADDREF_INHERITED(DocumentTimeline, AnimationTimeline)
|
|
|
|
NS_IMPL_RELEASE_INHERITED(DocumentTimeline, AnimationTimeline)
|
|
|
|
|
|
|
|
JSObject*
|
|
|
|
DocumentTimeline::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
|
|
|
|
{
|
|
|
|
return DocumentTimelineBinding::Wrap(aCx, this, aGivenProto);
|
|
|
|
}
|
|
|
|
|
|
|
|
Nullable<TimeDuration>
|
|
|
|
DocumentTimeline::GetCurrentTime() const
|
|
|
|
{
|
|
|
|
return ToTimelineTime(GetCurrentTimeStamp());
|
|
|
|
}
|
|
|
|
|
|
|
|
TimeStamp
|
|
|
|
DocumentTimeline::GetCurrentTimeStamp() const
|
|
|
|
{
|
|
|
|
nsRefreshDriver* refreshDriver = GetRefreshDriver();
|
|
|
|
TimeStamp refreshTime = refreshDriver
|
|
|
|
? refreshDriver->MostRecentRefresh()
|
|
|
|
: TimeStamp();
|
|
|
|
|
|
|
|
// Always return the same object to benefit from return-value optimization.
|
|
|
|
TimeStamp result = !refreshTime.IsNull()
|
|
|
|
? refreshTime
|
|
|
|
: mLastRefreshDriverTime;
|
|
|
|
|
|
|
|
// If we don't have a refresh driver and we've never had one use the
|
|
|
|
// timeline's zero time.
|
|
|
|
if (result.IsNull()) {
|
|
|
|
nsRefPtr<nsDOMNavigationTiming> timing = mDocument->GetNavigationTiming();
|
|
|
|
if (timing) {
|
|
|
|
result = timing->GetNavigationStartTimeStamp();
|
|
|
|
// Also, let this time represent the current refresh time. This way
|
|
|
|
// we'll save it as the last refresh time and skip looking up
|
|
|
|
// navigation timing each time.
|
|
|
|
refreshTime = result;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!refreshTime.IsNull()) {
|
|
|
|
mLastRefreshDriverTime = refreshTime;
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
Nullable<TimeDuration>
|
|
|
|
DocumentTimeline::ToTimelineTime(const TimeStamp& aTimeStamp) const
|
|
|
|
{
|
|
|
|
Nullable<TimeDuration> result; // Initializes to null
|
|
|
|
if (aTimeStamp.IsNull()) {
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
nsRefPtr<nsDOMNavigationTiming> timing = mDocument->GetNavigationTiming();
|
|
|
|
if (MOZ_UNLIKELY(!timing)) {
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
result.SetValue(aTimeStamp - timing->GetNavigationStartTimeStamp());
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
Bug 1195180 part 6 - Lazily remove animations from timelines; r=heycam
Now that DocumentTimeline observes the refresh driver we can use regular
ticks to remove unnecessary animations.
We do this because in a subsequent patch, in order to provide deterministic
enumeration order when ticking animations, we will store animations in an array.
Removing an arbitrary element from an nsTArray is O(n) since we have to search
for the array index first, or O(log n) if we keep the array sorted. If we
destroy a subtree containing n animations, the operation effectively becomes
O(n^2), or, if we keep the array sorted, O(n log n). By destroying during a
tick when we are already iterating over the array, however, we will be able
to do this much more efficiently.
Whether an animation is newly associated with a timeline, or is disassociated
from a timeline, or if it merely has its timing updated, the behavior
implemented in this patch is to simply make sure we are observing the refresh
driver and deal with the animation on the next tick.
It might seem that we could be a lot more clever about this and, for example, if
an animation reports NeedsTicks() == false, not start observing the refresh
driver. There are various edge cases however that need to be taken into account.
For example, if a CSS animation is finished (IsRelevant() == false so that
animation will have been removed from the timeline), and paused
(NeedsTicks() == false), and we seek it back to the point where it is relevant
again, we actually need to observe the refresh driver so that it can dispatch an
animationstart event on the next tick. A test case in a subsequent patch tests
this specific situation.
We could possibly add logic to detect if we need to fire events on the next tick
but the complexity does not seem warranted given that even if we unnecessarily
start observing the refresh driver, we will stop watching it on the next tick.
This patch removes some rather lengthy comments from
AnimationTiming::UpdateTiming. This is, in part, because of the behavior
described above that makes these comments no longer relevant. Other parts are
removed because the Web Animations specification has been updated such that a
timeline becoming inactive now pauses the animation[1] so that the issue
regarding detecting timelines becoming active/inactive no longer applies
since animations attached to an inactive timeline remain "relevant".
[1] https://w3c.github.io/web-animations/#responding-to-a-newly-inactive-timeline
2015-09-28 06:38:41 +03:00
|
|
|
void
|
|
|
|
DocumentTimeline::NotifyAnimationUpdated(Animation& aAnimation)
|
|
|
|
{
|
|
|
|
AnimationTimeline::NotifyAnimationUpdated(aAnimation);
|
|
|
|
|
|
|
|
if (!mIsObservingRefreshDriver) {
|
|
|
|
nsRefreshDriver* refreshDriver = GetRefreshDriver();
|
|
|
|
if (refreshDriver) {
|
|
|
|
refreshDriver->AddRefreshObserver(this, Flush_Style);
|
|
|
|
mIsObservingRefreshDriver = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-09-28 06:38:40 +03:00
|
|
|
void
|
|
|
|
DocumentTimeline::WillRefresh(mozilla::TimeStamp aTime)
|
|
|
|
{
|
2015-09-28 06:38:40 +03:00
|
|
|
MOZ_ASSERT(mIsObservingRefreshDriver);
|
2015-09-28 06:38:40 +03:00
|
|
|
|
|
|
|
bool needsTicks = false;
|
2015-09-28 06:38:41 +03:00
|
|
|
AnimationArray animationsToKeep(mAnimationOrder.Length());
|
2015-09-28 06:38:40 +03:00
|
|
|
|
2015-09-28 06:38:41 +03:00
|
|
|
nsAutoAnimationMutationBatch mb(mDocument);
|
|
|
|
|
2015-09-28 06:38:41 +03:00
|
|
|
for (Animation* animation : mAnimationOrder) {
|
2015-09-28 06:38:41 +03:00
|
|
|
// Skip any animations that are longer need associated with this timeline.
|
|
|
|
if (animation->GetTimeline() != this) {
|
2015-09-28 06:38:41 +03:00
|
|
|
mAnimations.RemoveEntry(animation);
|
Bug 1195180 part 6 - Lazily remove animations from timelines; r=heycam
Now that DocumentTimeline observes the refresh driver we can use regular
ticks to remove unnecessary animations.
We do this because in a subsequent patch, in order to provide deterministic
enumeration order when ticking animations, we will store animations in an array.
Removing an arbitrary element from an nsTArray is O(n) since we have to search
for the array index first, or O(log n) if we keep the array sorted. If we
destroy a subtree containing n animations, the operation effectively becomes
O(n^2), or, if we keep the array sorted, O(n log n). By destroying during a
tick when we are already iterating over the array, however, we will be able
to do this much more efficiently.
Whether an animation is newly associated with a timeline, or is disassociated
from a timeline, or if it merely has its timing updated, the behavior
implemented in this patch is to simply make sure we are observing the refresh
driver and deal with the animation on the next tick.
It might seem that we could be a lot more clever about this and, for example, if
an animation reports NeedsTicks() == false, not start observing the refresh
driver. There are various edge cases however that need to be taken into account.
For example, if a CSS animation is finished (IsRelevant() == false so that
animation will have been removed from the timeline), and paused
(NeedsTicks() == false), and we seek it back to the point where it is relevant
again, we actually need to observe the refresh driver so that it can dispatch an
animationstart event on the next tick. A test case in a subsequent patch tests
this specific situation.
We could possibly add logic to detect if we need to fire events on the next tick
but the complexity does not seem warranted given that even if we unnecessarily
start observing the refresh driver, we will stop watching it on the next tick.
This patch removes some rather lengthy comments from
AnimationTiming::UpdateTiming. This is, in part, because of the behavior
described above that makes these comments no longer relevant. Other parts are
removed because the Web Animations specification has been updated such that a
timeline becoming inactive now pauses the animation[1] so that the issue
regarding detecting timelines becoming active/inactive no longer applies
since animations attached to an inactive timeline remain "relevant".
[1] https://w3c.github.io/web-animations/#responding-to-a-newly-inactive-timeline
2015-09-28 06:38:41 +03:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2015-09-28 06:38:40 +03:00
|
|
|
needsTicks |= animation->NeedsTicks();
|
2015-09-28 06:38:41 +03:00
|
|
|
// Even if |animation| doesn't need future ticks, we should still
|
|
|
|
// Tick it this time around since it might just need a one-off tick in
|
|
|
|
// order to dispatch events.
|
|
|
|
animation->Tick();
|
|
|
|
|
|
|
|
if (animation->IsRelevant() || animation->NeedsTicks()) {
|
|
|
|
animationsToKeep.AppendElement(animation);
|
|
|
|
} else {
|
|
|
|
mAnimations.RemoveEntry(animation);
|
|
|
|
}
|
2015-09-28 06:38:40 +03:00
|
|
|
}
|
|
|
|
|
2015-09-28 06:38:41 +03:00
|
|
|
mAnimationOrder.SwapElements(animationsToKeep);
|
|
|
|
|
2015-09-28 06:38:40 +03:00
|
|
|
if (!needsTicks) {
|
|
|
|
// If another refresh driver observer destroys the nsPresContext,
|
|
|
|
// nsRefreshDriver will detect it and we won't be called.
|
|
|
|
MOZ_ASSERT(GetRefreshDriver(),
|
|
|
|
"Refresh driver should still be valid inside WillRefresh");
|
|
|
|
GetRefreshDriver()->RemoveRefreshObserver(this, Flush_Style);
|
|
|
|
mIsObservingRefreshDriver = false;
|
|
|
|
}
|
2015-09-28 06:38:40 +03:00
|
|
|
}
|
|
|
|
|
2015-09-28 06:38:40 +03:00
|
|
|
void
|
|
|
|
DocumentTimeline::NotifyRefreshDriverCreated(nsRefreshDriver* aDriver)
|
|
|
|
{
|
|
|
|
MOZ_ASSERT(!mIsObservingRefreshDriver,
|
|
|
|
"Timeline should not be observing the refresh driver before"
|
|
|
|
" it is created");
|
|
|
|
|
2015-09-28 06:38:41 +03:00
|
|
|
if (!mAnimationOrder.IsEmpty()) {
|
2015-09-28 06:38:40 +03:00
|
|
|
aDriver->AddRefreshObserver(this, Flush_Style);
|
|
|
|
mIsObservingRefreshDriver = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
DocumentTimeline::NotifyRefreshDriverDestroying(nsRefreshDriver* aDriver)
|
|
|
|
{
|
|
|
|
if (!mIsObservingRefreshDriver) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
aDriver->RemoveRefreshObserver(this, Flush_Style);
|
|
|
|
mIsObservingRefreshDriver = false;
|
|
|
|
}
|
|
|
|
|
2015-04-10 04:34:22 +03:00
|
|
|
TimeStamp
|
|
|
|
DocumentTimeline::ToTimeStamp(const TimeDuration& aTimeDuration) const
|
|
|
|
{
|
|
|
|
TimeStamp result;
|
|
|
|
nsRefPtr<nsDOMNavigationTiming> timing = mDocument->GetNavigationTiming();
|
|
|
|
if (MOZ_UNLIKELY(!timing)) {
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
result = timing->GetNavigationStartTimeStamp() + aTimeDuration;
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
nsRefreshDriver*
|
|
|
|
DocumentTimeline::GetRefreshDriver() const
|
|
|
|
{
|
|
|
|
nsIPresShell* presShell = mDocument->GetShell();
|
|
|
|
if (MOZ_UNLIKELY(!presShell)) {
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
nsPresContext* presContext = presShell->GetPresContext();
|
|
|
|
if (MOZ_UNLIKELY(!presContext)) {
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
return presContext->RefreshDriver();
|
|
|
|
}
|
|
|
|
|
|
|
|
} // namespace dom
|
|
|
|
} // namespace mozilla
|