dom/animation/DocumentTimeline.cpp
author Csoregi Natalia <ncsoregi@mozilla.com>
Thu, 01 Mar 2018 16:29:02 +0200
changeset 458530 a416b0a21b1395dfe7dc28577a31be57c075d9b4
parent 458470 55c94c05c57f0bb34ca131c68c79e05a97aa4b6c
child 459020 068c59c7c4ec46802b4a18e98adc227aed6d5da1
permissions -rw-r--r--
Backed out 7 changesets (bug 1193394) for browser-chrome failures on browser_ext_popup_background.js. CLOSED TREE Backed out changeset 9683f24ff8ec (bug 1193394) Backed out changeset 0e7140a7c841 (bug 1193394) Backed out changeset a0e26f6b2784 (bug 1193394) Backed out changeset 29e1fceaf48d (bug 1193394) Backed out changeset b8632bbbd273 (bug 1193394) Backed out changeset a54ef2d8f896 (bug 1193394) Backed out changeset 55c94c05c57f (bug 1193394)

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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/ScopeExit.h"
#include "mozilla/dom/DocumentTimelineBinding.h"
#include "mozilla/dom/Promise.h"
#include "AnimationUtils.h"
#include "nsContentUtils.h"
#include "nsDOMMutationObserver.h"
#include "nsDOMNavigationTiming.h"
#include "nsIPresShell.h"
#include "nsPresContext.h"
#include "nsRefreshDriver.h"

namespace mozilla {
namespace dom {

NS_IMPL_CYCLE_COLLECTION_CLASS(DocumentTimeline)
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(DocumentTimeline,
                                                AnimationTimeline)
  tmp->UnregisterFromRefreshDriver();
  if (tmp->isInList()) {
    tmp->remove();
  }
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocument)
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(DocumentTimeline,
                                                  AnimationTimeline)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocument)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END

NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(DocumentTimeline,
                                               AnimationTimeline)
NS_IMPL_CYCLE_COLLECTION_TRACE_END

NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(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);
}

/* static */ already_AddRefed<DocumentTimeline>
DocumentTimeline::Constructor(const GlobalObject& aGlobal,
                              const DocumentTimelineOptions& aOptions,
                              ErrorResult& aRv)
{
  nsIDocument* doc = AnimationUtils::GetCurrentRealmDocument(aGlobal.Context());
  if (!doc) {
    aRv.Throw(NS_ERROR_FAILURE);
    return nullptr;
  }
  TimeDuration originTime =
    TimeDuration::FromMilliseconds(aOptions.mOriginTime);

  if (originTime == TimeDuration::Forever() ||
      originTime == -TimeDuration::Forever()) {
    aRv.ThrowTypeError<dom::MSG_TIME_VALUE_OUT_OF_RANGE>(
      NS_LITERAL_STRING("Origin time"));
    return nullptr;
  }
  RefPtr<DocumentTimeline> timeline = new DocumentTimeline(doc, originTime);

  return timeline.forget();
}

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()) {
    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;
  }

  nsDOMNavigationTiming* timing = mDocument->GetNavigationTiming();
  if (MOZ_UNLIKELY(!timing)) {
    return result;
  }

  result.SetValue(aTimeStamp
                  - timing->GetNavigationStartTimeStamp()
                  - mOriginTime);
  return result;
}

void
DocumentTimeline::NotifyAnimationUpdated(Animation& aAnimation)
{
  AnimationTimeline::NotifyAnimationUpdated(aAnimation);

  if (!mIsObservingRefreshDriver) {
    nsRefreshDriver* refreshDriver = GetRefreshDriver();
    if (refreshDriver) {
      MOZ_ASSERT(isInList(),
                "We should not register with the refresh driver if we are not"
                " in the document's list of timelines");
      refreshDriver->AddRefreshObserver(this, FlushType::Style);
      mIsObservingRefreshDriver = true;
    }
  }
}

void
DocumentTimeline::WillRefresh(mozilla::TimeStamp aTime)
{
  MOZ_ASSERT(mIsObservingRefreshDriver);
  MOZ_ASSERT(GetRefreshDriver(),
             "Should be able to reach refresh driver from within WillRefresh");

  bool needsTicks = false;
  nsTArray<Animation*> animationsToRemove(mAnimations.Count());

  // https://drafts.csswg.org/web-animations-1/#update-animations-and-send-events,
  // step2.
  // FIXME: This needs to be replaced with nsAutoMicroTask.
  // Note that this should be done before nsAutoAnimationMutationBatch.  If
  // PerformMicroTaskCheckpoint was called before nsAutoAnimationMutationBatch
  // is destroyed, some mutation records might not be delivered in this
  // checkpoint.
  auto autoPerformMicrotaskCheckpoint = MakeScopeExit([] {
    Promise::PerformMicroTaskCheckpoint();
  });
  nsAutoAnimationMutationBatch mb(mDocument);

  for (Animation* animation = mAnimationOrder.getFirst(); animation;
       animation = animation->getNext()) {
    // Skip any animations that are longer need associated with this timeline.
    if (animation->GetTimeline() != this) {
      // If animation has some other timeline, it better not be also in the
      // animation list of this timeline object!
      MOZ_ASSERT(!animation->GetTimeline());
      animationsToRemove.AppendElement(animation);
      continue;
    }

    needsTicks |= animation->NeedsTicks();
    // 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()) {
      animationsToRemove.AppendElement(animation);
    }
  }

  for (Animation* animation : animationsToRemove) {
    RemoveAnimation(animation);
  }

  if (!needsTicks) {
    // We already assert that GetRefreshDriver() is non-null at the beginning
    // of this function but we check it again here to be sure that ticking
    // animations does not have any side effects that cause us to lose the
    // connection with the refresh driver, such as triggering the destruction
    // of mDocument's PresShell.
    MOZ_ASSERT(GetRefreshDriver(),
               "Refresh driver should still be valid at end of WillRefresh");
    UnregisterFromRefreshDriver();
  }
}

void
DocumentTimeline::NotifyRefreshDriverCreated(nsRefreshDriver* aDriver)
{
  MOZ_ASSERT(!mIsObservingRefreshDriver,
             "Timeline should not be observing the refresh driver before"
             " it is created");

  if (!mAnimationOrder.isEmpty()) {
    MOZ_ASSERT(isInList(),
               "We should not register with the refresh driver if we are not"
               " in the document's list of timelines");
    aDriver->AddRefreshObserver(this, FlushType::Style);
    mIsObservingRefreshDriver = true;
  }
}

void
DocumentTimeline::NotifyRefreshDriverDestroying(nsRefreshDriver* aDriver)
{
  if (!mIsObservingRefreshDriver) {
    return;
  }

  aDriver->RemoveRefreshObserver(this, FlushType::Style);
  mIsObservingRefreshDriver = false;
}

void
DocumentTimeline::RemoveAnimation(Animation* aAnimation)
{
  AnimationTimeline::RemoveAnimation(aAnimation);

  if (mIsObservingRefreshDriver && mAnimations.IsEmpty()) {
    UnregisterFromRefreshDriver();
  }
}

TimeStamp
DocumentTimeline::ToTimeStamp(const TimeDuration& aTimeDuration) const
{
  TimeStamp result;
  RefPtr<nsDOMNavigationTiming> timing = mDocument->GetNavigationTiming();
  if (MOZ_UNLIKELY(!timing)) {
    return result;
  }

  result =
    timing->GetNavigationStartTimeStamp() + (aTimeDuration + mOriginTime);
  return result;
}

nsRefreshDriver*
DocumentTimeline::GetRefreshDriver() const
{
  nsPresContext* presContext = mDocument->GetPresContext();
  if (MOZ_UNLIKELY(!presContext)) {
    return nullptr;
  }

  return presContext->RefreshDriver();
}

void
DocumentTimeline::UnregisterFromRefreshDriver()
{
  if (!mIsObservingRefreshDriver) {
    return;
  }

  nsRefreshDriver* refreshDriver = GetRefreshDriver();
  if (!refreshDriver) {
    return;
  }

  refreshDriver->RemoveRefreshObserver(this, FlushType::Style);
  mIsObservingRefreshDriver = false;
}

} // namespace dom
} // namespace mozilla