tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

ViewTransition.cpp (79968B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this
      3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 #include "ViewTransition.h"
      6 
      7 #include "Units.h"
      8 #include "WindowRenderer.h"
      9 #include "mozilla/AnimationEventDispatcher.h"
     10 #include "mozilla/EffectSet.h"
     11 #include "mozilla/ElementAnimationData.h"
     12 #include "mozilla/FlowMarkers.h"
     13 #include "mozilla/SVGIntegrationUtils.h"
     14 #include "mozilla/ServoStyleConsts.h"
     15 #include "mozilla/WritingModes.h"
     16 #include "mozilla/dom/BindContext.h"
     17 #include "mozilla/dom/DocumentInlines.h"
     18 #include "mozilla/dom/DocumentTimeline.h"
     19 #include "mozilla/dom/Promise-inl.h"
     20 #include "mozilla/dom/ViewTransitionBinding.h"
     21 #include "mozilla/dom/ViewTransitionTypeSet.h"
     22 #include "mozilla/image/WebRenderImageProvider.h"
     23 #include "mozilla/layers/RenderRootStateManager.h"
     24 #include "mozilla/layers/WebRenderBridgeChild.h"
     25 #include "mozilla/layers/WebRenderLayerManager.h"
     26 #include "mozilla/webrender/WebRenderAPI.h"
     27 #include "nsCanvasFrame.h"
     28 #include "nsDisplayList.h"
     29 #include "nsFrameState.h"
     30 #include "nsITimer.h"
     31 #include "nsLayoutUtils.h"
     32 #include "nsPresContext.h"
     33 #include "nsString.h"
     34 
     35 namespace mozilla::dom {
     36 
     37 LazyLogModule gViewTransitionsLog("ViewTransitions");
     38 
     39 NS_DECLARE_FRAME_PROPERTY_RELEASABLE(ViewTransitionCaptureName, nsAtom)
     40 
     41 static void SetCaptured(nsIFrame* aFrame, bool aCaptured,
     42                        nsAtom* aNameIfCaptured) {
     43  aFrame->AddOrRemoveStateBits(NS_FRAME_CAPTURED_IN_VIEW_TRANSITION, aCaptured);
     44  if (aCaptured) {
     45    aFrame->AddProperty(ViewTransitionCaptureName(),
     46                        do_AddRef(aNameIfCaptured).take());
     47  } else {
     48    aFrame->RemoveProperty(ViewTransitionCaptureName());
     49  }
     50  aFrame->InvalidateFrameSubtree();
     51  if (aFrame->Style()->IsRootElementStyle()) {
     52    aFrame->PresShell()->GetRootFrame()->InvalidateFrameSubtree();
     53  }
     54 }
     55 
     56 // Set capture's old transform to a <transform-function> that would map
     57 // element's border box from the snapshot containing block origin to its
     58 // current visual position.
     59 //
     60 // Since we're using viewport as the snapshot origin, we can use
     61 // GetBoundingClientRect() effectively...
     62 //
     63 // TODO(emilio): This might need revision.
     64 static CSSToCSSMatrix4x4Flagged EffectiveTransform(nsIFrame* aFrame) {
     65  if (aFrame->GetSize().IsEmpty() || aFrame->Style()->IsRootElementStyle()) {
     66    return {};
     67  }
     68 
     69  auto matrix = CSSToCSSMatrix4x4Flagged::FromUnknownMatrix(
     70      nsLayoutUtils::GetTransformToAncestor(
     71          RelativeTo{aFrame},
     72          RelativeTo{nsLayoutUtils::GetContainingBlockForClientRect(aFrame)},
     73          nsIFrame::IN_CSS_UNITS, nullptr));
     74 
     75  // Compensate for the default transform-origin of 50% 50% using border box
     76  // dimensions.
     77  auto borderBoxRect = CSSRect::FromAppUnits(aFrame->GetRect());
     78  matrix.ChangeBasis(-borderBoxRect.Width() / 2, -borderBoxRect.Height() / 2,
     79                     0.0f);
     80  return matrix;
     81 }
     82 
     83 enum class CapturedRectType { BorderBox, InkOverflowBox };
     84 
     85 static inline nsRect SnapRect(const nsRect& aRect, nscoord aAppUnitsPerPixel) {
     86  return LayoutDeviceIntRect::ToAppUnits(
     87      LayoutDeviceIntRect::FromUnknownRect(
     88          aRect.ToOutsidePixels(aAppUnitsPerPixel)),
     89      aAppUnitsPerPixel);
     90 }
     91 
     92 static inline nsRect CapturedRect(const nsIFrame* aFrame,
     93                                  const nsSize& aSnapshotContainingBlockSize,
     94                                  CapturedRectType aType) {
     95  if (aFrame->Style()->IsRootElementStyle()) {
     96    return nsRect(nsPoint(), aSnapshotContainingBlockSize);
     97  }
     98 
     99  if (aType == CapturedRectType::BorderBox) {
    100    return aFrame->GetRectRelativeToSelf();
    101  }
    102 
    103  return SnapRect(aFrame->InkOverflowRectRelativeToSelf(),
    104                  aFrame->PresContext()->AppUnitsPerDevPixel());
    105 }
    106 
    107 static StyleViewTransitionClass DocumentScopedClassListFor(
    108    const nsIFrame* aFrame) {
    109  return aFrame->StyleUIReset()->mViewTransitionClass;
    110 }
    111 
    112 static constexpr wr::ImageKey kNoKey{{0}, 0};
    113 struct OldSnapshotData {
    114  wr::ImageKey mImageKey = kNoKey;
    115  // Snapshot size should match the captured element’s InkOverflowBox size,
    116  // snapped.
    117  nsRect mSnapshotRect;
    118  RefPtr<layers::RenderRootStateManager> mManager;
    119  bool mUsed = false;
    120 
    121  OldSnapshotData() = default;
    122 
    123  explicit OldSnapshotData(nsIFrame* aFrame,
    124                           const nsSize& aSnapshotContainingBlockSize)
    125      : mSnapshotRect(CapturedRect(aFrame, aSnapshotContainingBlockSize,
    126                                   CapturedRectType::InkOverflowBox)) {}
    127 
    128  void EnsureKey(layers::RenderRootStateManager* aManager,
    129                 wr::IpcResourceUpdateQueue& aResources) {
    130    if (mImageKey != kNoKey) {
    131      MOZ_ASSERT(mManager == aManager, "Stale manager?");
    132      return;
    133    }
    134    mManager = aManager;
    135    mImageKey = aManager->WrBridge()->GetNextImageKey();
    136    aResources.AddSnapshotImage(wr::SnapshotImageKey{mImageKey});
    137  }
    138 
    139  ~OldSnapshotData() {
    140    if (mManager) {
    141      wr::SnapshotImageKey key = {mImageKey};
    142      if (mUsed) {
    143        mManager->AddSnapshotImageKeyForDiscard(key);
    144      } else {
    145        mManager->AddUnusedSnapshotImageKeyForDiscard(key);
    146      }
    147    }
    148  }
    149 };
    150 
    151 struct CapturedElementOldState {
    152  OldSnapshotData mSnapshot;
    153  // Whether we tried to capture an image. Note we might fail to get a
    154  // snapshot, so this might not be the same as !!mImage.
    155  bool mTriedImage = false;
    156 
    157  nsSize mBorderBoxSize;
    158  CSSToCSSMatrix4x4Flagged mTransform;
    159  StyleWritingModeProperty mWritingMode =
    160      StyleWritingModeProperty::HorizontalTb;
    161  StyleDirection mDirection = StyleDirection::Ltr;
    162  StyleTextOrientation mTextOrientation = StyleTextOrientation::Mixed;
    163  StyleBlend mMixBlendMode = StyleBlend::Normal;
    164  StyleOwnedSlice<StyleFilter> mBackdropFilters;
    165  // Note: it's unfortunate we cannot just store the bits here. color-scheme
    166  // property uses idents for serialization. If the idents and bits are not
    167  // aligned, we assert it in ToCSS.
    168  StyleColorScheme mColorScheme;
    169 
    170  CapturedElementOldState(nsIFrame* aFrame,
    171                          const nsSize& aSnapshotContainingBlockSize)
    172      : mSnapshot(aFrame, aSnapshotContainingBlockSize),
    173        mTriedImage(true),
    174        mBorderBoxSize(CapturedRect(aFrame, aSnapshotContainingBlockSize,
    175                                    CapturedRectType::BorderBox)
    176                           .Size()),
    177        mTransform(EffectiveTransform(aFrame)),
    178        mWritingMode(aFrame->StyleVisibility()->mWritingMode),
    179        mDirection(aFrame->StyleVisibility()->mDirection),
    180        mTextOrientation(aFrame->StyleVisibility()->mTextOrientation),
    181        mMixBlendMode(aFrame->StyleEffects()->mMixBlendMode),
    182        mBackdropFilters(aFrame->StyleEffects()->mBackdropFilters),
    183        mColorScheme(aFrame->StyleUI()->mColorScheme) {}
    184 
    185  CapturedElementOldState() = default;
    186 };
    187 
    188 // https://drafts.csswg.org/css-view-transitions/#captured-element
    189 struct ViewTransition::CapturedElement {
    190  CapturedElementOldState mOldState;
    191  RefPtr<Element> mNewElement;
    192  wr::SnapshotImageKey mNewSnapshotKey{kNoKey};
    193  // Snapshot size + offset, should match the captured element’s InkOverflowBox
    194  // size, snapped.
    195  nsRect mNewSnapshotRect;
    196  nsSize mNewBorderBoxSize;
    197 
    198  CapturedElement() = default;
    199 
    200  CapturedElement(nsIFrame* aFrame, const nsSize& aSnapshotContainingBlockSize,
    201                  StyleViewTransitionClass&& aClassList)
    202      : mOldState(aFrame, aSnapshotContainingBlockSize),
    203        mClassList(std::move(aClassList)) {}
    204 
    205  // https://drafts.csswg.org/css-view-transitions-1/#captured-element-style-definitions
    206  nsTArray<Keyframe> mGroupKeyframes;
    207  // The group animation-name rule and group styles rule, merged into one.
    208  RefPtr<StyleLockedDeclarationBlock> mGroupRule;
    209  // The image pair isolation rule.
    210  RefPtr<StyleLockedDeclarationBlock> mImagePairRule;
    211  // The rules for ::view-transition-old(<name>).
    212  RefPtr<StyleLockedDeclarationBlock> mOldRule;
    213  // The rules for ::view-transition-new(<name>).
    214  RefPtr<StyleLockedDeclarationBlock> mNewRule;
    215 
    216  // The view-transition-class associated with this captured element.
    217  // https://drafts.csswg.org/css-view-transitions-2/#captured-element-class-list
    218  StyleViewTransitionClass mClassList;
    219 
    220  // If snapshots are very large, compute active rects to restrict their
    221  // bounds, based on what's most likely visible during the transition.
    222  Maybe<nsRect> mOldActiveRect;
    223  Maybe<nsRect> mNewActiveRect;
    224 
    225  void CaptureClassList(StyleViewTransitionClass&& aClassList) {
    226    mClassList = std::move(aClassList);
    227  }
    228 
    229  ~CapturedElement() {
    230    if (wr::AsImageKey(mNewSnapshotKey) != kNoKey) {
    231      MOZ_ASSERT(mOldState.mSnapshot.mManager);
    232      mOldState.mSnapshot.mManager->AddSnapshotImageKeyForDiscard(
    233          mNewSnapshotKey);
    234    }
    235  }
    236 };
    237 
    238 static inline void ImplCycleCollectionTraverse(
    239    nsCycleCollectionTraversalCallback& aCb,
    240    const ViewTransition::CapturedElement& aField, const char* aName,
    241    uint32_t aFlags = 0) {
    242  ImplCycleCollectionTraverse(aCb, aField.mNewElement, aName, aFlags);
    243 }
    244 
    245 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(ViewTransition, mDocument,
    246                                      mUpdateCallback,
    247                                      mUpdateCallbackDonePromise, mReadyPromise,
    248                                      mFinishedPromise, mNamedElements, mTypes,
    249                                      mSnapshotContainingBlock)
    250 
    251 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ViewTransition)
    252  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
    253  NS_INTERFACE_MAP_ENTRY(nsISupports)
    254 NS_INTERFACE_MAP_END
    255 
    256 NS_IMPL_CYCLE_COLLECTING_ADDREF(ViewTransition)
    257 NS_IMPL_CYCLE_COLLECTING_RELEASE(ViewTransition)
    258 
    259 ViewTransition::ViewTransition(Document& aDoc,
    260                               ViewTransitionUpdateCallback* aCb,
    261                               TypeList&& aTypeList)
    262    : mDocument(&aDoc), mUpdateCallback(aCb), mTypeList(std::move(aTypeList)) {}
    263 
    264 ViewTransition::~ViewTransition() { ClearTimeoutTimer(); }
    265 
    266 Element* ViewTransition::GetViewTransitionTreeRoot() const {
    267  return mSnapshotContainingBlock
    268             ? mSnapshotContainingBlock->GetFirstElementChild()
    269             : nullptr;
    270 }
    271 
    272 void ViewTransition::GetCapturedFrames(
    273    nsTArray<nsIFrame*>& aCapturedFrames) const {
    274  if (mOldCaptureElements) {
    275    for (const auto& [f, _] : *mOldCaptureElements) {
    276      aCapturedFrames.AppendElement(f);
    277    }
    278  }
    279 
    280  for (const auto& entry : mNamedElements) {
    281    CapturedElement& capturedElement = *entry.GetData();
    282    if (capturedElement.mNewElement &&
    283        capturedElement.mNewElement->GetPrimaryFrame()) {
    284      aCapturedFrames.AppendElement(
    285          capturedElement.mNewElement->GetPrimaryFrame());
    286    }
    287  }
    288 }
    289 
    290 Maybe<nsRect> ViewTransition::GetOldInkOverflowRect(nsAtom* aName) const {
    291  auto* el = mNamedElements.Get(aName);
    292  if (NS_WARN_IF(!el)) {
    293    return {};
    294  }
    295  return Some(el->mOldState.mSnapshot.mSnapshotRect);
    296 }
    297 
    298 Maybe<nsRect> ViewTransition::GetNewInkOverflowRect(nsAtom* aName) const {
    299  auto* el = mNamedElements.Get(aName);
    300  if (NS_WARN_IF(!el)) {
    301    return {};
    302  }
    303  return Some(el->mNewSnapshotRect);
    304 }
    305 
    306 Maybe<nsSize> ViewTransition::GetOldBorderBoxSize(nsAtom* aName) const {
    307  auto* el = mNamedElements.Get(aName);
    308  if (NS_WARN_IF(!el)) {
    309    return {};
    310  }
    311  return Some(el->mOldState.mBorderBoxSize);
    312 }
    313 
    314 Maybe<nsSize> ViewTransition::GetNewBorderBoxSize(nsAtom* aName) const {
    315  auto* el = mNamedElements.Get(aName);
    316  if (NS_WARN_IF(!el)) {
    317    return {};
    318  }
    319  return Some(el->mNewBorderBoxSize);
    320 }
    321 
    322 const wr::ImageKey* ViewTransition::GetOrCreateOldImageKey(
    323    nsAtom* aName, layers::RenderRootStateManager* aManager,
    324    wr::IpcResourceUpdateQueue& aResources) const {
    325  auto* el = mNamedElements.Get(aName);
    326  if (NS_WARN_IF(!el)) {
    327    return nullptr;
    328  }
    329  el->mOldState.mSnapshot.EnsureKey(aManager, aResources);
    330  return &el->mOldState.mSnapshot.mImageKey;
    331 }
    332 
    333 const wr::ImageKey* ViewTransition::ReadOldImageKey(
    334    nsAtom* aName, layers::RenderRootStateManager* aManager,
    335    wr::IpcResourceUpdateQueue& aResources) const {
    336  auto* el = mNamedElements.Get(aName);
    337  if (NS_WARN_IF(!el)) {
    338    return nullptr;
    339  }
    340 
    341  el->mOldState.mSnapshot.mUsed = true;
    342  return &el->mOldState.mSnapshot.mImageKey;
    343 }
    344 
    345 const wr::ImageKey* ViewTransition::GetNewImageKey(nsAtom* aName) const {
    346  auto* el = mNamedElements.Get(aName);
    347  if (NS_WARN_IF(!el)) {
    348    return nullptr;
    349  }
    350  return &el->mNewSnapshotKey._0;
    351 }
    352 
    353 const wr::ImageKey* ViewTransition::GetImageKeyForCapturedFrame(
    354    nsIFrame* aFrame, layers::RenderRootStateManager* aManager,
    355    wr::IpcResourceUpdateQueue& aResources) const {
    356  MOZ_ASSERT(aFrame);
    357  MOZ_ASSERT(aFrame->HasAnyStateBits(NS_FRAME_CAPTURED_IN_VIEW_TRANSITION));
    358 
    359  nsAtom* name = aFrame->GetProperty(ViewTransitionCaptureName());
    360  if (NS_WARN_IF(!name)) {
    361    return nullptr;
    362  }
    363  const bool isOld = mPhase < Phase::Animating;
    364 
    365  VT_LOG("ViewTransition::GetImageKeyForCapturedFrame(%s, old=%d)\n",
    366         nsAtomCString(name).get(), isOld);
    367 
    368  if (isOld) {
    369    const auto* key = GetOrCreateOldImageKey(name, aManager, aResources);
    370    VT_LOG(" > old image is %s", key ? ToString(*key).c_str() : "null");
    371    return key;
    372  }
    373  auto* el = mNamedElements.Get(name);
    374  if (NS_WARN_IF(!el)) {
    375    return nullptr;
    376  }
    377  if (NS_WARN_IF(el->mNewElement != aFrame->GetContent())) {
    378    return nullptr;
    379  }
    380  if (wr::AsImageKey(el->mNewSnapshotKey) == kNoKey) {
    381    MOZ_ASSERT(!el->mOldState.mSnapshot.mManager ||
    382                   el->mOldState.mSnapshot.mManager == aManager,
    383               "Stale manager?");
    384    el->mNewSnapshotKey = {aManager->WrBridge()->GetNextImageKey()};
    385    el->mOldState.mSnapshot.mManager = aManager;
    386    aResources.AddSnapshotImage(el->mNewSnapshotKey);
    387  }
    388  VT_LOG(" > new image is %s", ToString(el->mNewSnapshotKey._0).c_str());
    389  return &el->mNewSnapshotKey._0;
    390 }
    391 
    392 nsIGlobalObject* ViewTransition::GetParentObject() const {
    393  return mDocument ? mDocument->GetParentObject() : nullptr;
    394 }
    395 
    396 Promise* ViewTransition::GetUpdateCallbackDone(ErrorResult& aRv) {
    397  if (!mUpdateCallbackDonePromise) {
    398    mUpdateCallbackDonePromise = Promise::Create(GetParentObject(), aRv);
    399  }
    400  return mUpdateCallbackDonePromise;
    401 }
    402 
    403 Promise* ViewTransition::GetReady(ErrorResult& aRv) {
    404  if (!mReadyPromise) {
    405    mReadyPromise = Promise::Create(GetParentObject(), aRv);
    406  }
    407  return mReadyPromise;
    408 }
    409 
    410 Promise* ViewTransition::GetFinished(ErrorResult& aRv) {
    411  if (!mFinishedPromise) {
    412    mFinishedPromise = Promise::Create(GetParentObject(), aRv);
    413  }
    414  return mFinishedPromise;
    415 }
    416 
    417 // This performs the step 5 in setup view transition.
    418 // https://drafts.csswg.org/css-view-transitions-1/#setup-view-transition
    419 void ViewTransition::MaybeScheduleUpdateCallback() {
    420  AUTO_PROFILER_FLOW_MARKER("ViewTransition::MaybeScheduleUpdateCallback",
    421                            LAYOUT, Flow::FromPointer(this));
    422  // 1. If transition’s phase is "done", then abort these steps.
    423  // Note: This happens if transition was skipped before this point.
    424  if (mPhase == Phase::Done) {
    425    return;
    426  }
    427 
    428  RefPtr doc = mDocument;
    429 
    430  // 2. Schedule the update callback for transition.
    431  doc->ScheduleViewTransitionUpdateCallback(this);
    432 
    433  // 3. Flush the update callback queue.
    434  doc->FlushViewTransitionUpdateCallbackQueue();
    435 }
    436 
    437 // https://drafts.csswg.org/css-view-transitions-1/#call-the-update-callback
    438 void ViewTransition::CallUpdateCallback(ErrorResult& aRv) {
    439  MOZ_ASSERT(mDocument);
    440  // Step 1:  Assert: transition's phase is "done", or before
    441  // "update-callback-called".
    442  MOZ_ASSERT(mPhase == Phase::Done ||
    443             UnderlyingValue(mPhase) <
    444                 UnderlyingValue(Phase::UpdateCallbackCalled));
    445  VT_LOG("ViewTransition::CallUpdateCallback(%d)\n", int(mPhase));
    446  AUTO_PROFILER_FLOW_MARKER("ViewTransition::CallUpdateCallback", LAYOUT,
    447                            Flow::FromPointer(this));
    448 
    449  // Step 5: If transition's phase is not "done", then set transition's phase
    450  // to "update-callback-called".
    451  //
    452  // NOTE(emilio): This is swapped with the spec because the spec is broken,
    453  // see https://github.com/w3c/csswg-drafts/issues/10822
    454  if (mPhase != Phase::Done) {
    455    mPhase = Phase::UpdateCallbackCalled;
    456  }
    457 
    458  // Step 2: Let callbackPromise be null.
    459  RefPtr<Promise> callbackPromise;
    460  if (!mUpdateCallback) {
    461    // Step 3: If transition's update callback is null, then set callbackPromise
    462    // to a promise resolved with undefined, in transition’s relevant Realm.
    463    callbackPromise =
    464        Promise::CreateResolvedWithUndefined(GetParentObject(), aRv);
    465  } else {
    466    // Step 4: Otherwise set callbackPromise to the result of invoking
    467    // transition’s update callback. MOZ_KnownLive because the callback can only
    468    // go away when we get CCd.
    469    callbackPromise = MOZ_KnownLive(mUpdateCallback)->Call(aRv);
    470  }
    471  if (aRv.Failed()) {
    472    // TODO(emilio): Do we need extra error handling here?
    473    return;
    474  }
    475  MOZ_ASSERT(callbackPromise);
    476  // Step 8: React to callbackPromise with fulfillSteps and rejectSteps.
    477  callbackPromise->AddCallbacksWithCycleCollectedArgs(
    478      [](JSContext*, JS::Handle<JS::Value>, ErrorResult& aRv,
    479         ViewTransition* aVt) {
    480        AUTO_PROFILER_FLOW_MARKER("ViewTransition::UpdateCallbackResolve",
    481                                  LAYOUT, Flow::FromPointer(aVt));
    482        // We clear the timeout when we are ready to activate. Otherwise, any
    483        // animations with the duration longer than
    484        // StaticPrefs::dom_viewTransitions_timeout_ms() will be interrupted.
    485        // FIXME: We may need a better solution to tweak the timeout, e.g. reset
    486        // the timeout to a longer value or so on.
    487        aVt->ClearTimeoutTimer();
    488 
    489        // Step 6: Let fulfillSteps be to following steps:
    490        if (Promise* ucd = aVt->GetUpdateCallbackDone(aRv)) {
    491          // 6.1: Resolve transition's update callback done promise with
    492          // undefined.
    493          ucd->MaybeResolveWithUndefined();
    494        }
    495        // Unlike other timings, this is not guaranteed to happen with clean
    496        // layout, and Activate() needs to look at the frame tree to capture the
    497        // new state, so we need to flush frames. Do it here so that we deal
    498        // with other potential script execution skipping the transition or
    499        // what not in a consistent way.
    500        aVt->mDocument->FlushPendingNotifications(FlushType::Layout);
    501        if (aVt->mPhase == Phase::Done) {
    502          // "Skip a transition" step 8. We need to resolve "finished" after
    503          // update-callback-done.
    504          if (Promise* finished = aVt->GetFinished(aRv)) {
    505            finished->MaybeResolveWithUndefined();
    506          }
    507        }
    508        aVt->Activate();
    509      },
    510      [](JSContext*, JS::Handle<JS::Value> aReason, ErrorResult& aRv,
    511         ViewTransition* aVt) {
    512        AUTO_PROFILER_FLOW_MARKER("ViewTransition::UpdateCallbackReject",
    513                                  LAYOUT, Flow::FromPointer(aVt));
    514        // Clear the timeout because we are ready to skip the view transitions.
    515        aVt->ClearTimeoutTimer();
    516 
    517        // Step 7: Let rejectSteps be to following steps:
    518        if (Promise* ucd = aVt->GetUpdateCallbackDone(aRv)) {
    519          // 7.1: Reject transition's update callback done promise with reason.
    520          ucd->MaybeReject(aReason);
    521        }
    522 
    523        // 7.2: If transition's phase is "done", then return.
    524        if (aVt->mPhase == Phase::Done) {
    525          // "Skip a transition" step 8. We need to resolve "finished" after
    526          // update-callback-done.
    527          if (Promise* finished = aVt->GetFinished(aRv)) {
    528            finished->MaybeReject(aReason);
    529          }
    530          return;
    531        }
    532 
    533        // 7.3: Mark as handled transition's ready promise.
    534        if (Promise* ready = aVt->GetReady(aRv)) {
    535          MOZ_ALWAYS_TRUE(ready->SetAnyPromiseIsHandled());
    536        }
    537        aVt->SkipTransition(SkipTransitionReason::UpdateCallbackRejected,
    538                            aReason);
    539      },
    540      RefPtr(this));
    541 
    542  // Step 9: To skip a transition after a timeout, the user agent may perform
    543  // the following steps in parallel:
    544  MOZ_ASSERT(!mTimeoutTimer);
    545  ClearTimeoutTimer();  // Be safe just in case.
    546  mTimeoutTimer = NS_NewTimer();
    547  mTimeoutTimer->InitWithNamedFuncCallback(
    548      TimeoutCallback, this, StaticPrefs::dom_viewTransitions_timeout_ms(),
    549      nsITimer::TYPE_ONE_SHOT, "ViewTransition::TimeoutCallback"_ns);
    550 }
    551 
    552 void ViewTransition::ClearTimeoutTimer() {
    553  if (mTimeoutTimer) {
    554    mTimeoutTimer->Cancel();
    555    mTimeoutTimer = nullptr;
    556  }
    557 }
    558 
    559 void ViewTransition::TimeoutCallback(nsITimer* aTimer, void* aClosure) {
    560  RefPtr vt = static_cast<ViewTransition*>(aClosure);
    561  MOZ_DIAGNOSTIC_ASSERT(aTimer == vt->mTimeoutTimer);
    562  vt->Timeout();
    563 }
    564 
    565 void ViewTransition::Timeout() {
    566  ClearTimeoutTimer();
    567  if (mPhase != Phase::Done && mDocument) {
    568    SkipTransition(SkipTransitionReason::Timeout);
    569  }
    570 }
    571 
    572 static already_AddRefed<Element> MakePseudo(Document& aDoc,
    573                                            PseudoStyleType aType,
    574                                            nsAtom* aName) {
    575  RefPtr<Element> el = aDoc.CreateHTMLElement(nsGkAtoms::div);
    576  if (aType == PseudoStyleType::mozSnapshotContainingBlock) {
    577    el->SetIsNativeAnonymousRoot();
    578  }
    579  el->SetPseudoElementType(aType);
    580  if (aName) {
    581    el->SetAttr(nsGkAtoms::name, nsDependentAtomString(aName), IgnoreErrors());
    582  }
    583  // This is not needed, but useful for debugging.
    584  el->SetAttr(nsGkAtoms::type,
    585              nsDependentAtomString(nsCSSPseudoElements::GetPseudoAtom(aType)),
    586              IgnoreErrors());
    587  return el.forget();
    588 }
    589 
    590 static bool SetProp(StyleLockedDeclarationBlock* aDecls, Document* aDoc,
    591                    NonCustomCSSPropertyId aProp, const nsACString& aValue) {
    592  return Servo_DeclarationBlock_SetPropertyById(
    593      aDecls, aProp, &aValue,
    594      /* is_important = */ false, aDoc->DefaultStyleAttrURLData(),
    595      StyleParsingMode::DEFAULT, eCompatibility_FullStandards,
    596      &aDoc->EnsureCSSLoader(), StyleCssRuleType::Style, {});
    597 }
    598 
    599 static bool SetProp(StyleLockedDeclarationBlock* aDecls, Document*,
    600                    NonCustomCSSPropertyId aProp, float aLength,
    601                    nsCSSUnit aUnit) {
    602  return Servo_DeclarationBlock_SetLengthValue(aDecls, aProp, aLength, aUnit);
    603 }
    604 
    605 static bool SetProp(StyleLockedDeclarationBlock* aDecls, Document*,
    606                    NonCustomCSSPropertyId aProp,
    607                    const CSSToCSSMatrix4x4Flagged& aM) {
    608  MOZ_ASSERT(aProp == eCSSProperty_transform);
    609  AutoTArray<StyleTransformOperation, 1> ops;
    610  ops.AppendElement(
    611      StyleTransformOperation::Matrix3D(StyleGenericMatrix3D<StyleNumber>{
    612          aM._11, aM._12, aM._13, aM._14, aM._21, aM._22, aM._23, aM._24,
    613          aM._31, aM._32, aM._33, aM._34, aM._41, aM._42, aM._43, aM._44}));
    614  return Servo_DeclarationBlock_SetTransform(aDecls, aProp, &ops);
    615 }
    616 
    617 static bool SetProp(StyleLockedDeclarationBlock* aDecls, Document* aDoc,
    618                    NonCustomCSSPropertyId aProp,
    619                    const StyleWritingModeProperty aWM) {
    620  return Servo_DeclarationBlock_SetKeywordValue(aDecls, aProp, (int32_t)aWM);
    621 }
    622 
    623 static bool SetProp(StyleLockedDeclarationBlock* aDecls, Document* aDoc,
    624                    NonCustomCSSPropertyId aProp,
    625                    const StyleDirection aDirection) {
    626  return Servo_DeclarationBlock_SetKeywordValue(aDecls, aProp,
    627                                                (int32_t)aDirection);
    628 }
    629 
    630 static bool SetProp(StyleLockedDeclarationBlock* aDecls, Document* aDoc,
    631                    NonCustomCSSPropertyId aProp,
    632                    const StyleTextOrientation aTextOrientation) {
    633  return Servo_DeclarationBlock_SetKeywordValue(aDecls, aProp,
    634                                                (int32_t)aTextOrientation);
    635 }
    636 
    637 static bool SetProp(StyleLockedDeclarationBlock* aDecls, Document* aDoc,
    638                    NonCustomCSSPropertyId aProp, const StyleBlend aBlend) {
    639  return Servo_DeclarationBlock_SetKeywordValue(aDecls, aProp, (int32_t)aBlend);
    640 }
    641 
    642 static bool SetProp(
    643    StyleLockedDeclarationBlock* aDecls, Document*,
    644    NonCustomCSSPropertyId aProp,
    645    const StyleOwnedSlice<mozilla::StyleFilter>& aBackdropFilters) {
    646  return Servo_DeclarationBlock_SetBackdropFilter(aDecls, aProp,
    647                                                  &aBackdropFilters);
    648 }
    649 
    650 static bool SetProp(StyleLockedDeclarationBlock* aDecls, Document*,
    651                    NonCustomCSSPropertyId aProp,
    652                    const StyleColorScheme& aColorScheme) {
    653  return Servo_DeclarationBlock_SetColorScheme(aDecls, aProp, &aColorScheme);
    654 }
    655 
    656 static StyleLockedDeclarationBlock* EnsureRule(
    657    RefPtr<StyleLockedDeclarationBlock>& aRule) {
    658  if (!aRule) {
    659    aRule = Servo_DeclarationBlock_CreateEmpty().Consume();
    660  }
    661  return aRule.get();
    662 }
    663 
    664 static nsTArray<Keyframe> BuildGroupKeyframes(
    665    Document* aDoc, const CSSToCSSMatrix4x4Flagged& aTransform,
    666    const nsSize& aSize, const StyleOwnedSlice<StyleFilter>& aBackdropFilters) {
    667  Keyframe firstKeyframe;
    668  firstKeyframe.mOffset = Some(0.0);
    669  PropertyValuePair transform{
    670      CSSPropertyId(eCSSProperty_transform),
    671      Servo_DeclarationBlock_CreateEmpty().Consume(),
    672  };
    673  SetProp(transform.mServoDeclarationBlock, aDoc, eCSSProperty_transform,
    674          aTransform);
    675  PropertyValuePair width{
    676      CSSPropertyId(eCSSProperty_width),
    677      Servo_DeclarationBlock_CreateEmpty().Consume(),
    678  };
    679  CSSSize cssSize = CSSSize::FromAppUnits(aSize);
    680  SetProp(width.mServoDeclarationBlock, aDoc, eCSSProperty_width, cssSize.width,
    681          eCSSUnit_Pixel);
    682  PropertyValuePair height{
    683      CSSPropertyId(eCSSProperty_height),
    684      Servo_DeclarationBlock_CreateEmpty().Consume(),
    685  };
    686  SetProp(height.mServoDeclarationBlock, aDoc, eCSSProperty_height,
    687          cssSize.height, eCSSUnit_Pixel);
    688  PropertyValuePair backdropFilters{
    689      CSSPropertyId(eCSSProperty_backdrop_filter),
    690      Servo_DeclarationBlock_CreateEmpty().Consume(),
    691  };
    692  SetProp(backdropFilters.mServoDeclarationBlock, aDoc,
    693          eCSSProperty_backdrop_filter, aBackdropFilters);
    694  firstKeyframe.mPropertyValues.AppendElement(std::move(transform));
    695  firstKeyframe.mPropertyValues.AppendElement(std::move(width));
    696  firstKeyframe.mPropertyValues.AppendElement(std::move(height));
    697  firstKeyframe.mPropertyValues.AppendElement(std::move(backdropFilters));
    698 
    699  Keyframe lastKeyframe;
    700  lastKeyframe.mOffset = Some(1.0);
    701  lastKeyframe.mPropertyValues.AppendElement(
    702      PropertyValuePair{CSSPropertyId(eCSSProperty_transform)});
    703  lastKeyframe.mPropertyValues.AppendElement(
    704      PropertyValuePair{CSSPropertyId(eCSSProperty_width)});
    705  lastKeyframe.mPropertyValues.AppendElement(
    706      PropertyValuePair{CSSPropertyId(eCSSProperty_height)});
    707  lastKeyframe.mPropertyValues.AppendElement(
    708      PropertyValuePair{CSSPropertyId(eCSSProperty_backdrop_filter)});
    709 
    710  nsTArray<Keyframe> result;
    711  result.AppendElement(std::move(firstKeyframe));
    712  result.AppendElement(std::move(lastKeyframe));
    713  return result;
    714 }
    715 
    716 bool ViewTransition::GetGroupKeyframes(
    717    nsAtom* aAnimationName, const StyleComputedTimingFunction& aTimingFunction,
    718    nsTArray<Keyframe>& aResult) {
    719  MOZ_ASSERT(StringBeginsWith(nsDependentAtomString(aAnimationName),
    720                              kGroupAnimPrefix));
    721  RefPtr<nsAtom> transitionName = NS_Atomize(Substring(
    722      nsDependentAtomString(aAnimationName), kGroupAnimPrefix.Length()));
    723  auto* el = mNamedElements.Get(transitionName);
    724  if (NS_WARN_IF(!el) || NS_WARN_IF(el->mGroupKeyframes.IsEmpty())) {
    725    return false;
    726  }
    727  aResult = el->mGroupKeyframes.Clone();
    728  // We assign the timing function always to make sure we don't use the default
    729  // linear timing function.
    730  MOZ_ASSERT(aResult.Length() == 2);
    731  aResult[0].mTimingFunction = Some(aTimingFunction);
    732  aResult[1].mTimingFunction = Some(aTimingFunction);
    733  return true;
    734 }
    735 
    736 // Matches the class list in the captured element.
    737 // https://drafts.csswg.org/css-view-transitions-2/#pseudo-element-class-additions
    738 bool ViewTransition::MatchClassList(
    739    nsAtom* aTransitionName,
    740    const nsTArray<StyleAtom>& aPtNameAndClassSelector) const {
    741  MOZ_ASSERT(aPtNameAndClassSelector.Length() > 1);
    742 
    743  const auto* el = mNamedElements.Get(aTransitionName);
    744  MOZ_ASSERT(el);
    745  const auto& classList = el->mClassList._0.AsSpan();
    746  auto hasClass = [&classList](nsAtom* aClass) {
    747    // LInear search. The css class list shouldn't be very large in most cases.
    748    for (const auto& ident : classList) {
    749      if (ident.AsAtom() == aClass) {
    750        return true;
    751      }
    752    }
    753    return false;
    754  };
    755 
    756  // A named view transition pseudo-element selector which has one or more
    757  // <custom-ident> values in its <pt-class-selector> would only match an
    758  // element if the class list value in named elements for the pseudo-element’s
    759  // view-transition-name contains all of those values.
    760  // i.e. |aPtNameAndClassSelector| should be a subset of |mClassList|.
    761  for (const auto& atom : Span(aPtNameAndClassSelector).From(1)) {
    762    if (!hasClass(atom.AsAtom())) {
    763      return false;
    764    }
    765  }
    766  return true;
    767 }
    768 
    769 // In general, we are trying to generate the following pseudo-elements tree:
    770 // ::-moz-snapshot-containing-block
    771 // └─ ::view-transition
    772 //    ├─ ::view-transition-group(name)
    773 //    │  └─ ::view-transition-image-pair(name)
    774 //    │     ├─ ::view-transition-old(name)
    775 //    │     └─ ::view-transition-new(name)
    776 //    └─ ...other groups...
    777 //
    778 // ::-moz-snapshot-containing-block is the top-layer of the tree. It is the
    779 // wrapper of the view transition pseudo-elements tree for the snapshot
    780 // containing block concept. And it is the child of the document element.
    781 // https://drafts.csswg.org/css-view-transitions-1/#setup-transition-pseudo-elements
    782 void ViewTransition::SetupTransitionPseudoElements() {
    783  MOZ_ASSERT(!mSnapshotContainingBlock);
    784 
    785  nsAutoScriptBlocker scriptBlocker;
    786 
    787  RefPtr docElement = mDocument->GetRootElement();
    788  if (!docElement) {
    789    return;
    790  }
    791 
    792  // We don't need to notify while constructing the tree.
    793  constexpr bool kNotify = false;
    794 
    795  // Step 1 is a declaration.
    796 
    797  // Step 2: Set document's show view transition tree to true.
    798  // (we lazily create this pseudo-element so we don't need the flag for now at
    799  // least).
    800  // Note: Use mSnapshotContainingBlock to wrap the pseudo-element tree.
    801  mSnapshotContainingBlock = MakePseudo(
    802      *mDocument, PseudoStyleType::mozSnapshotContainingBlock, nullptr);
    803  RefPtr<Element> root =
    804      MakePseudo(*mDocument, PseudoStyleType::viewTransition, nullptr);
    805  mSnapshotContainingBlock->AppendChildTo(root, kNotify, IgnoreErrors());
    806 #ifdef DEBUG
    807  // View transition pseudos don't care about frame tree ordering, so can be
    808  // restyled just fine.
    809  mSnapshotContainingBlock->SetProperty(nsGkAtoms::restylableAnonymousNode,
    810                                        reinterpret_cast<void*>(true));
    811 #endif
    812 
    813  MOZ_ASSERT(mNames.Length() == mNamedElements.Count());
    814  // Step 3: For each transitionName -> capturedElement of transition’s named
    815  // elements:
    816  for (nsAtom* transitionName : mNames) {
    817    CapturedElement& capturedElement = *mNamedElements.Get(transitionName);
    818    // Let group be a new ::view-transition-group(), with its view transition
    819    // name set to transitionName.
    820    RefPtr<Element> group = MakePseudo(
    821        *mDocument, PseudoStyleType::viewTransitionGroup, transitionName);
    822    // Append group to transition’s transition root pseudo-element.
    823    root->AppendChildTo(group, kNotify, IgnoreErrors());
    824    // Let imagePair be a new ::view-transition-image-pair(), with its view
    825    // transition name set to transitionName.
    826    RefPtr<Element> imagePair = MakePseudo(
    827        *mDocument, PseudoStyleType::viewTransitionImagePair, transitionName);
    828    // Append imagePair to group.
    829    group->AppendChildTo(imagePair, kNotify, IgnoreErrors());
    830    // If capturedElement's old image is not null, then:
    831    if (capturedElement.mOldState.mTriedImage) {
    832      // Let old be a new ::view-transition-old(), with its view transition
    833      // name set to transitionName, displaying capturedElement's old image as
    834      // its replaced content.
    835      RefPtr<Element> old = MakePseudo(
    836          *mDocument, PseudoStyleType::viewTransitionOld, transitionName);
    837      // Append old to imagePair.
    838      imagePair->AppendChildTo(old, kNotify, IgnoreErrors());
    839    } else {
    840      // Moved around for simplicity. If capturedElement's old image is null,
    841      // then: Assert: capturedElement's new element is not null.
    842      MOZ_ASSERT(capturedElement.mNewElement);
    843      // Set capturedElement's image animation name rule to a new ...
    844      auto* rule = EnsureRule(capturedElement.mNewRule);
    845      SetProp(rule, mDocument, eCSSProperty_animation_name,
    846              "-ua-view-transition-fade-in"_ns);
    847    }
    848    // If capturedElement's new element is not null, then:
    849    if (capturedElement.mNewElement) {
    850      // Let new be a new ::view-transition-new(), with its view transition
    851      // name set to transitionName.
    852      RefPtr<Element> new_ = MakePseudo(
    853          *mDocument, PseudoStyleType::viewTransitionNew, transitionName);
    854      // Append new to imagePair.
    855      imagePair->AppendChildTo(new_, kNotify, IgnoreErrors());
    856    } else {
    857      // Moved around from the next step for simplicity.
    858      // Assert: capturedElement's old image is not null.
    859      // Set capturedElement's image animation name rule to a new CSSStyleRule
    860      // representing the following CSS, and append it to document’s dynamic
    861      // view transition style sheet:
    862      MOZ_ASSERT(capturedElement.mOldState.mTriedImage);
    863      SetProp(EnsureRule(capturedElement.mOldRule), mDocument,
    864              eCSSProperty_animation_name, "-ua-view-transition-fade-out"_ns);
    865 
    866      // Moved around from "update pseudo-element styles" because it's a one
    867      // time operation.
    868      auto* rule = EnsureRule(capturedElement.mGroupRule);
    869      auto oldRect =
    870          CSSPixel::FromAppUnits(capturedElement.mOldState.mBorderBoxSize);
    871      SetProp(rule, mDocument, eCSSProperty_width, oldRect.width,
    872              eCSSUnit_Pixel);
    873      SetProp(rule, mDocument, eCSSProperty_height, oldRect.height,
    874              eCSSUnit_Pixel);
    875      SetProp(rule, mDocument, eCSSProperty_transform,
    876              capturedElement.mOldState.mTransform);
    877      SetProp(rule, mDocument, eCSSProperty_writing_mode,
    878              capturedElement.mOldState.mWritingMode);
    879      SetProp(rule, mDocument, eCSSProperty_direction,
    880              capturedElement.mOldState.mDirection);
    881      SetProp(rule, mDocument, eCSSProperty_text_orientation,
    882              capturedElement.mOldState.mTextOrientation);
    883      SetProp(rule, mDocument, eCSSProperty_mix_blend_mode,
    884              capturedElement.mOldState.mMixBlendMode);
    885      SetProp(rule, mDocument, eCSSProperty_backdrop_filter,
    886              capturedElement.mOldState.mBackdropFilters);
    887      SetProp(rule, mDocument, eCSSProperty_color_scheme,
    888              capturedElement.mOldState.mColorScheme);
    889    }
    890    // If both of capturedElement's old image and new element are not null,
    891    // then:
    892    if (capturedElement.mOldState.mTriedImage && capturedElement.mNewElement) {
    893      nsAutoCString dynamicAnimationName;
    894      nsStyleUtil::AppendQuotedCSSString(
    895          NS_ConvertUTF16toUTF8(kGroupAnimPrefix +
    896                                nsDependentAtomString(transitionName)),
    897          dynamicAnimationName);
    898      capturedElement.mGroupKeyframes =
    899          BuildGroupKeyframes(mDocument, capturedElement.mOldState.mTransform,
    900                              capturedElement.mOldState.mBorderBoxSize,
    901                              capturedElement.mOldState.mBackdropFilters);
    902      // Set capturedElement's group animation name rule to ...
    903      SetProp(EnsureRule(capturedElement.mGroupRule), mDocument,
    904              eCSSProperty_animation_name, dynamicAnimationName);
    905 
    906      // Set capturedElement's image pair isolation rule to ...
    907      SetProp(EnsureRule(capturedElement.mImagePairRule), mDocument,
    908              eCSSProperty_isolation, "isolate"_ns);
    909 
    910      // Set capturedElement's image animation name rule to ...
    911      SetProp(
    912          EnsureRule(capturedElement.mOldRule), mDocument,
    913          eCSSProperty_animation_name,
    914          "-ua-view-transition-fade-out, -ua-mix-blend-mode-plus-lighter"_ns);
    915      SetProp(
    916          EnsureRule(capturedElement.mNewRule), mDocument,
    917          eCSSProperty_animation_name,
    918          "-ua-view-transition-fade-in, -ua-mix-blend-mode-plus-lighter"_ns);
    919    }
    920  }
    921  BindContext context(*docElement, BindContext::ForNativeAnonymous);
    922  if (NS_FAILED(mSnapshotContainingBlock->BindToTree(context, *docElement))) {
    923    mSnapshotContainingBlock->UnbindFromTree();
    924    mSnapshotContainingBlock = nullptr;
    925    return;
    926  }
    927  if (mDocument->DevToolsAnonymousAndShadowEventsEnabled()) {
    928    mSnapshotContainingBlock->QueueDevtoolsAnonymousEvent(
    929        /* aIsRemove = */ false);
    930  }
    931  if (PresShell* ps = mDocument->GetPresShell()) {
    932    ps->ContentAppended(mSnapshotContainingBlock, {});
    933  }
    934 }
    935 
    936 // https://drafts.csswg.org/css-view-transitions-1/#style-transition-pseudo-elements-algorithm
    937 bool ViewTransition::UpdatePseudoElementStyles(bool aNeedsInvalidation) {
    938  // 1. For each transitionName -> capturedElement of transition's "named
    939  // elements".
    940  for (auto& entry : mNamedElements) {
    941    nsAtom* transitionName = entry.GetKey();
    942    CapturedElement& capturedElement = *entry.GetData();
    943    // If capturedElement's new element is null, then:
    944    // We already did this in SetupTransitionPseudoElements().
    945    if (!capturedElement.mNewElement) {
    946      continue;
    947    }
    948    // Otherwise.
    949    // Return failure if any of the following conditions is true:
    950    //  * capturedElement's new element has a flat tree ancestor that skips its
    951    //    contents.
    952    //  * capturedElement's new element is not rendered.
    953    //  * capturedElement has more than one box fragment.
    954    nsIFrame* frame = capturedElement.mNewElement->GetPrimaryFrame();
    955    if (!frame || frame->IsHiddenByContentVisibilityOnAnyAncestor() ||
    956        frame->GetPrevContinuation() || frame->GetNextContinuation()) {
    957      return false;
    958    }
    959    auto* rule = EnsureRule(capturedElement.mGroupRule);
    960    // Note: mInitialSnapshotContainingBlockSize should be the same as the
    961    // current snapshot containing block size because the caller checks it
    962    // before calling us.
    963    const auto newBorderBoxSize =
    964        CapturedRect(frame, mInitialSnapshotContainingBlockSize,
    965                     CapturedRectType::BorderBox)
    966            .Size();
    967    auto size = CSSPixel::FromAppUnits(newBorderBoxSize);
    968    // NOTE(emilio): Intentionally not short-circuiting. Int cast is needed to
    969    // silence warning.
    970    bool groupStyleChanged =
    971        int(SetProp(rule, mDocument, eCSSProperty_width, size.width,
    972                    eCSSUnit_Pixel)) |
    973        SetProp(rule, mDocument, eCSSProperty_height, size.height,
    974                eCSSUnit_Pixel) |
    975        SetProp(rule, mDocument, eCSSProperty_transform,
    976                EffectiveTransform(frame)) |
    977        SetProp(rule, mDocument, eCSSProperty_writing_mode,
    978                frame->StyleVisibility()->mWritingMode) |
    979        SetProp(rule, mDocument, eCSSProperty_direction,
    980                frame->StyleVisibility()->mDirection) |
    981        SetProp(rule, mDocument, eCSSProperty_text_orientation,
    982                frame->StyleVisibility()->mTextOrientation) |
    983        SetProp(rule, mDocument, eCSSProperty_mix_blend_mode,
    984                frame->StyleEffects()->mMixBlendMode) |
    985        SetProp(rule, mDocument, eCSSProperty_backdrop_filter,
    986                frame->StyleEffects()->mBackdropFilters) |
    987        SetProp(rule, mDocument, eCSSProperty_color_scheme,
    988                frame->StyleUI()->mColorScheme);
    989    if (groupStyleChanged && aNeedsInvalidation) {
    990      auto* pseudo = FindPseudo(PseudoStyleRequest(
    991          PseudoStyleType::viewTransitionGroup, transitionName));
    992      MOZ_ASSERT(pseudo);
    993      // TODO(emilio): Maybe we need something more than recascade? But I don't
    994      // see how off-hand.
    995      nsLayoutUtils::PostRestyleEvent(pseudo, RestyleHint::RECASCADE_SELF,
    996                                      nsChangeHint(0));
    997    }
    998 
    999    // 5. Live capturing (nothing to do here regarding the capture itself, but
   1000    // if the size has changed, then we need to invalidate the new frame).
   1001    const auto newSnapshotRect =
   1002        CapturedRect(frame, mInitialSnapshotContainingBlockSize,
   1003                     CapturedRectType::InkOverflowBox);
   1004    auto oldRect = capturedElement.mNewSnapshotRect;
   1005    capturedElement.mNewSnapshotRect = newSnapshotRect;
   1006    capturedElement.mNewBorderBoxSize = newBorderBoxSize;
   1007    if (!oldRect.IsEqualEdges(capturedElement.mNewSnapshotRect) &&
   1008        aNeedsInvalidation) {
   1009      frame->PresShell()->FrameNeedsReflow(
   1010          frame, IntrinsicDirty::FrameAndAncestors, NS_FRAME_IS_DIRTY);
   1011    }
   1012  }
   1013  return true;
   1014 }
   1015 
   1016 // https://drafts.csswg.org/css-view-transitions-1/#activate-view-transition
   1017 void ViewTransition::Activate() {
   1018  AUTO_PROFILER_FLOW_MARKER("ViewTransition::Activate", LAYOUT,
   1019                            Flow::FromPointer(this));
   1020  // Step 1: If transition's phase is "done", then return.
   1021  if (mPhase == Phase::Done) {
   1022    return;
   1023  }
   1024 
   1025  // Step 2: Set transition’s relevant global object’s associated document’s
   1026  // rendering suppression for view transitions to false.
   1027  mDocument->SetRenderingSuppressedForViewTransitions(false);
   1028 
   1029  // Step 3: If transition's initial snapshot containing block size is not
   1030  // equal to the snapshot containing block size, then skip the view transition
   1031  // for transition, and return.
   1032  if (mInitialSnapshotContainingBlockSize !=
   1033      SnapshotContainingBlockRect().Size()) {
   1034    return SkipTransition(SkipTransitionReason::Resize);
   1035  }
   1036 
   1037  // Step 4: Capture the new state for transition.
   1038  // Step 5 is done along step 4 for performance.
   1039  if (auto skipReason = CaptureNewState()) {
   1040    // We clear named elements to not leave lingering "captured in a view
   1041    // transition" state.
   1042    ClearNamedElements();
   1043    // If failure is returned, then skip the view transition for transition...
   1044    return SkipTransition(*skipReason);
   1045  }
   1046 
   1047  // Step 6: Setup transition pseudo-elements for transition.
   1048  SetupTransitionPseudoElements();
   1049 
   1050  // Step 7: Update pseudo-element styles for transition.
   1051  // We don't need to invalidate the pseudo-element styles since we just
   1052  // generated them.
   1053  if (!UpdatePseudoElementStyles(/* aNeedsInvalidation = */ false)) {
   1054    // If failure is returned, then skip the view transition for transition
   1055    // with an "InvalidStateError" DOMException in transition's relevant Realm,
   1056    // and return.
   1057    return SkipTransition(SkipTransitionReason::PseudoUpdateFailure);
   1058  }
   1059 
   1060  // Step 8: Set transition's phase to "animating".
   1061  mPhase = Phase::Animating;
   1062  // Step 9: Resolve transition's ready promise.
   1063  if (Promise* ready = GetReady(IgnoreErrors())) {
   1064    ready->MaybeResolveWithUndefined();
   1065  }
   1066 
   1067  // Once this view transition is activated, we have to perform the pending
   1068  // operations periodically.
   1069  MOZ_ASSERT(mDocument);
   1070  mDocument->EnsureViewTransitionOperationsHappen();
   1071 }
   1072 
   1073 // https://drafts.csswg.org/css-view-transitions/#perform-pending-transition-operations
   1074 void ViewTransition::PerformPendingOperations() {
   1075  MOZ_ASSERT(mDocument);
   1076  MOZ_ASSERT(mDocument->GetActiveViewTransition() == this);
   1077  AUTO_PROFILER_FLOW_MARKER("ViewTransition::PerformPendingOperations", LAYOUT,
   1078                            Flow::FromPointer(this));
   1079 
   1080  // Flush the update callback queue.
   1081  // Note: this ensures that any changes to the DOM scheduled by other skipped
   1082  // transitions are done before the old state for this transition is captured.
   1083  // https://github.com/w3c/csswg-drafts/issues/11943
   1084  RefPtr doc = mDocument;
   1085  doc->FlushViewTransitionUpdateCallbackQueue();
   1086 
   1087  switch (mPhase) {
   1088    case Phase::PendingCapture:
   1089      return Setup();
   1090    case Phase::Animating:
   1091      return HandleFrame();
   1092    default:
   1093      break;
   1094  }
   1095 }
   1096 
   1097 // https://drafts.csswg.org/css-view-transitions/#snapshot-containing-block
   1098 nsRect ViewTransition::SnapshotContainingBlockRect(nsPresContext* aPc) {
   1099  return aPc ? nsRect(aPc->GetVisibleArea().TopLeft(),
   1100                      aPc->GetSizeForViewportUnits())
   1101             : nsRect();
   1102 }
   1103 
   1104 // https://drafts.csswg.org/css-view-transitions/#snapshot-containing-block
   1105 nsRect ViewTransition::SnapshotContainingBlockRect() const {
   1106  nsPresContext* pc = mDocument->GetPresContext();
   1107  return SnapshotContainingBlockRect(pc);
   1108 }
   1109 
   1110 nsRect ViewTransition::CapturedInkOverflowRectForFrame(nsIFrame* aFrame,
   1111                                                       bool aIsRoot) {
   1112  auto snapshotCb = SnapshotContainingBlockRect(aFrame->PresContext());
   1113  if (aIsRoot) {
   1114    return snapshotCb;
   1115  }
   1116  return CapturedRect(aFrame, snapshotCb.Size(),
   1117                      CapturedRectType::InkOverflowBox);
   1118 }
   1119 
   1120 Element* ViewTransition::FindPseudo(const PseudoStyleRequest& aRequest) const {
   1121  Element* root = GetViewTransitionTreeRoot();
   1122  if (!root) {
   1123    return nullptr;
   1124  }
   1125  MOZ_ASSERT(root->GetPseudoElementType() == PseudoStyleType::viewTransition);
   1126 
   1127  if (aRequest.mType == PseudoStyleType::viewTransition) {
   1128    return root;
   1129  }
   1130 
   1131  // Linear search ::view-transition-group by |aRequest.mIdentifier|.
   1132  // Note: perhaps we can add a hashtable to improve the performance if it's
   1133  // common that there are a lot of view-transition-names.
   1134  Element* group = root->GetFirstElementChild();
   1135  for (; group; group = group->GetNextElementSibling()) {
   1136    MOZ_ASSERT(group->HasName(),
   1137               "The generated ::view-transition-group() should have a name");
   1138    nsAtom* name = group->GetParsedAttr(nsGkAtoms::name)->GetAtomValue();
   1139    if (name == aRequest.mIdentifier) {
   1140      break;
   1141    }
   1142  }
   1143 
   1144  // No one specifies view-transition-name or we mismatch all names.
   1145  if (!group) {
   1146    return nullptr;
   1147  }
   1148 
   1149  if (aRequest.mType == PseudoStyleType::viewTransitionGroup) {
   1150    return group;
   1151  }
   1152 
   1153  Element* imagePair = group->GetFirstElementChild();
   1154  MOZ_ASSERT(imagePair, "::view-transition-image-pair() should exist always");
   1155  if (aRequest.mType == PseudoStyleType::viewTransitionImagePair) {
   1156    return imagePair;
   1157  }
   1158 
   1159  Element* child = imagePair->GetFirstElementChild();
   1160  // Neither ::view-transition-old() nor ::view-transition-new() doesn't exist.
   1161  if (!child) {
   1162    return nullptr;
   1163  }
   1164 
   1165  // Check if the first element matches our request.
   1166  const PseudoStyleType type = child->GetPseudoElementType();
   1167  if (type == aRequest.mType) {
   1168    return child;
   1169  }
   1170 
   1171  // Since the second child is either ::view-transition-new() or nullptr, so we
   1172  // can reject viewTransitionOld request here.
   1173  if (aRequest.mType == PseudoStyleType::viewTransitionOld) {
   1174    return nullptr;
   1175  }
   1176 
   1177  child = child->GetNextElementSibling();
   1178  MOZ_ASSERT(aRequest.mType == PseudoStyleType::viewTransitionNew);
   1179  MOZ_ASSERT(!child || !child->GetNextElementSibling(),
   1180             "No more psuedo elements in this subtree");
   1181  return child;
   1182 }
   1183 
   1184 const StyleLockedDeclarationBlock* ViewTransition::GetDynamicRuleFor(
   1185    const Element& aElement) const {
   1186  if (!aElement.HasName()) {
   1187    return nullptr;
   1188  }
   1189  nsAtom* name = aElement.GetParsedAttr(nsGkAtoms::name)->GetAtomValue();
   1190  auto* capture = mNamedElements.Get(name);
   1191  if (!capture) {
   1192    return nullptr;
   1193  }
   1194 
   1195  switch (aElement.GetPseudoElementType()) {
   1196    case PseudoStyleType::viewTransitionNew:
   1197      return capture->mNewRule.get();
   1198    case PseudoStyleType::viewTransitionOld:
   1199      return capture->mOldRule.get();
   1200    case PseudoStyleType::viewTransitionImagePair:
   1201      return capture->mImagePairRule.get();
   1202    case PseudoStyleType::viewTransitionGroup:
   1203      return capture->mGroupRule.get();
   1204    default:
   1205      return nullptr;
   1206  }
   1207 }
   1208 
   1209 // This function collects frames in the same stacking context. We only put
   1210 // the frames which may create a new create stacking context in the list because
   1211 // they (and their descendants) are candidates for captured elements (i.e. with
   1212 // a valid view-transition-name).
   1213 static void CollectDescendantStackingContexts(nsIFrame* aStackingContextRoot,
   1214                                              nsTArray<nsIFrame*>& aList) {
   1215  for (auto& [list, id] : aStackingContextRoot->ChildLists()) {
   1216    for (nsIFrame* f : list) {
   1217      // FIXME: We probably can skip more frames, e.g. scrollbar or scrollcorner
   1218      // to save some time.
   1219 
   1220      // We only want to sort the frames form a new stacking context in the
   1221      // current stacking context (including the root stacking context). If it
   1222      // creates a new stacking context, its descendants should be traversed
   1223      // (and sorted) independently. Also, if a frame has view-transition-name,
   1224      // it should create a stacking context as well, so this check must include
   1225      // frames with view-transition-name.
   1226      // Note: the root frame may not be the root element, so we still have to
   1227      // check if |f| is the root element.
   1228      if (f->Style()->IsRootElementStyle() || f->IsStackingContext()) {
   1229        aList.AppendElement(f);
   1230        // We will continue to traverse its descendants after we sort |aList|.
   1231        continue;
   1232      }
   1233 
   1234      // If any flat tree ancestor of this element skips its contents, then
   1235      // continue.
   1236      if (f->IsHiddenByContentVisibilityOnAnyAncestor()) {
   1237        continue;
   1238      }
   1239 
   1240      // If |insertionFrame| doesn't create stacking context, we have to check
   1241      // its descendants because they are still in the current stacking context.
   1242      CollectDescendantStackingContexts(f, aList);
   1243    }
   1244  }
   1245 }
   1246 
   1247 struct ZOrderComparator {
   1248  bool LessThan(const nsIFrame* aLeft, const nsIFrame* aRight) const {
   1249    return aLeft->ZIndex().valueOr(0) < aRight->ZIndex().valueOr(0);
   1250  }
   1251 };
   1252 
   1253 template <typename Callback>
   1254 static bool ForEachDescendantWithViewTransitionNameInPaintOrder(
   1255    nsIFrame* aFrame, const Callback& aCb) {
   1256  // Call the callback if it specifies view-transition-name.
   1257  if (!aFrame->StyleUIReset()->mViewTransitionName.IsNone() && !aCb(aFrame)) {
   1258    return false;
   1259  }
   1260 
   1261  nsTArray<nsIFrame*> descendantStackingContexts;
   1262  CollectDescendantStackingContexts(aFrame, descendantStackingContexts);
   1263  // Sort by z-index to make sure we call the callback in paint order.
   1264  descendantStackingContexts.StableSort(ZOrderComparator());
   1265 
   1266  for (nsIFrame* f : descendantStackingContexts) {
   1267    if (!ForEachDescendantWithViewTransitionNameInPaintOrder(f, aCb)) {
   1268      return false;
   1269    }
   1270  }
   1271  return true;
   1272 }
   1273 
   1274 template <typename Callback>
   1275 static void ForEachFrameWithViewTransitionName(Document* aDoc,
   1276                                               const Callback& aCb) {
   1277  PresShell* ps = aDoc->GetPresShell();
   1278  if (!ps) {
   1279    return;
   1280  }
   1281  nsIFrame* root = ps->GetRootFrame();
   1282  if (!root) {
   1283    return;
   1284  }
   1285  ForEachDescendantWithViewTransitionNameInPaintOrder(root, aCb);
   1286 }
   1287 
   1288 // https://drafts.csswg.org/css-view-transitions/#capture-the-old-state
   1289 Maybe<SkipTransitionReason> ViewTransition::CaptureOldState() {
   1290  MOZ_ASSERT(mNamedElements.IsEmpty());
   1291 
   1292  // Steps 1/2 are variable declarations.
   1293  // Step 3: Let usedTransitionNames be a new set of strings.
   1294  nsTHashSet<nsAtom*> usedTransitionNames;
   1295  // Step 4: Let captureElements be a new list of elements.
   1296  OldCaptureFramesArray captureElements;
   1297 
   1298  // Step 5: If the snapshot containing block size exceeds an
   1299  // implementation-defined maximum, then return failure.
   1300  // TODO(emilio): Implement a maximum if we deem it needed.
   1301  //
   1302  // Step 6: Set transition's initial snapshot containing block size to the
   1303  // snapshot containing block size.
   1304  mInitialSnapshotContainingBlockSize = SnapshotContainingBlockRect().Size();
   1305 
   1306  // Step 7: For each element of every element that is connected, and has a node
   1307  // document equal to document, in paint order:
   1308  Maybe<SkipTransitionReason> result;
   1309  ForEachFrameWithViewTransitionName(mDocument, [&](nsIFrame* aFrame) {
   1310    RefPtr<nsAtom> name = DocumentScopedTransitionNameFor(aFrame);
   1311    if (!name) {
   1312      // As a fast path we check for v-t-n first.
   1313      // If transitionName is none, or element is not rendered, then continue.
   1314      return true;
   1315    }
   1316    if (aFrame->GetPrevContinuation() || aFrame->GetNextContinuation()) {
   1317      // If element has more than one box fragment, then continue.
   1318      return true;
   1319    }
   1320    if (!usedTransitionNames.EnsureInserted(name)) {
   1321      // We don't expect to see a duplicate transition name when using
   1322      // match-element.
   1323      MOZ_ASSERT(!aFrame->StyleUIReset()->mViewTransitionName.IsMatchElement());
   1324 
   1325      // If usedTransitionNames contains transitionName, then return failure.
   1326      result.emplace(
   1327          SkipTransitionReason::DuplicateTransitionNameCapturingOldState);
   1328      return false;
   1329    }
   1330    SetCaptured(aFrame, true, name.get());
   1331    captureElements.AppendElement(std::make_pair(aFrame, std::move(name)));
   1332    return true;
   1333  });
   1334 
   1335  if (result) {
   1336    for (auto& [f, name] : captureElements) {
   1337      SetCaptured(f, false, nullptr);
   1338    }
   1339    return result;
   1340  }
   1341 
   1342  // Step 8: For each element in captureElements:
   1343  // Step 9: For each element in captureElements, set element's captured
   1344  // in a view transition to false.
   1345  for (auto& [f, name] : captureElements) {
   1346    MOZ_ASSERT(f);
   1347    MOZ_ASSERT(f->GetContent()->IsElement());
   1348    // Capture the view-transition-class.
   1349    // https://drafts.csswg.org/css-view-transitions-2/#vt-class-algorithms
   1350    auto capture = MakeUnique<CapturedElement>(
   1351        f, mInitialSnapshotContainingBlockSize, DocumentScopedClassListFor(f));
   1352    mNamedElements.InsertOrUpdate(name, std::move(capture));
   1353    mNames.AppendElement(name);
   1354  }
   1355 
   1356  if (!captureElements.IsEmpty()) {
   1357    AutoRestore guard{mOldCaptureElements};
   1358    mOldCaptureElements = &captureElements;
   1359    // When snapshotting an iframe, we need to paint from the root subdoc.
   1360    if (RefPtr<PresShell> ps =
   1361            nsContentUtils::GetInProcessSubtreeRootDocument(mDocument)
   1362                ->GetPresShell()) {
   1363      // Build a display list and send it to WR in order to perform the
   1364      // capturing of old content.
   1365      if (RefPtr widget = ps->GetRootWidget()) {
   1366        VT_LOG("ViewTransitions::CaptureOldState(), requesting composite");
   1367        ps->PaintAndRequestComposite(ps->GetRootFrame(),
   1368                                     widget->GetWindowRenderer(),
   1369                                     PaintFlags::PaintCompositeOffscreen);
   1370        VT_LOG("ViewTransitions::CaptureOldState(), requesting composite end");
   1371      }
   1372    }
   1373  }
   1374 
   1375  for (auto& [f, name] : captureElements) {
   1376    SetCaptured(f, false, nullptr);
   1377  }
   1378  return result;
   1379 }
   1380 
   1381 // https://drafts.csswg.org/css-view-transitions-1/#capture-the-new-state
   1382 Maybe<SkipTransitionReason> ViewTransition::CaptureNewState() {
   1383  nsTHashSet<nsAtom*> usedTransitionNames;
   1384  Maybe<SkipTransitionReason> result;
   1385  ForEachFrameWithViewTransitionName(mDocument, [&](nsIFrame* aFrame) {
   1386    // As a fast path we check for v-t-n first.
   1387    RefPtr<nsAtom> name = DocumentScopedTransitionNameFor(aFrame);
   1388    if (!name) {
   1389      return true;
   1390    }
   1391    if (aFrame->GetPrevContinuation() || aFrame->GetNextContinuation()) {
   1392      // If element has more than one box fragment, then continue.
   1393      return true;
   1394    }
   1395    if (!usedTransitionNames.EnsureInserted(name)) {
   1396      // We don't expect to see a duplicate transition name when using
   1397      // match-element.
   1398      MOZ_ASSERT(!aFrame->StyleUIReset()->mViewTransitionName.IsMatchElement());
   1399      result.emplace(
   1400          SkipTransitionReason::DuplicateTransitionNameCapturingNewState);
   1401      return false;
   1402    }
   1403    bool wasPresent = true;
   1404    auto& capturedElement = mNamedElements.LookupOrInsertWith(name, [&] {
   1405      wasPresent = false;
   1406      return MakeUnique<CapturedElement>();
   1407    });
   1408    if (!wasPresent) {
   1409      mNames.AppendElement(name);
   1410    }
   1411    capturedElement->mNewElement = aFrame->GetContent()->AsElement();
   1412    auto capturedRect =
   1413        CapturedRect(aFrame, mInitialSnapshotContainingBlockSize,
   1414                     CapturedRectType::InkOverflowBox);
   1415    // Note: mInitialSnapshotContainingBlockSize should be the same as the
   1416    // current snapshot containing block size at this moment because the caller
   1417    // checks it before calling us.
   1418    capturedElement->mNewSnapshotRect = capturedRect;
   1419    capturedElement->mNewBorderBoxSize =
   1420        CapturedRect(aFrame, mInitialSnapshotContainingBlockSize,
   1421                     CapturedRectType::BorderBox)
   1422            .Size();
   1423    // Update its class list. This may override the existing class list because
   1424    // the users may change view-transition-class in the callback function. We
   1425    // have to use the latest one.
   1426    // https://drafts.csswg.org/css-view-transitions-2/#vt-class-algorithms
   1427    capturedElement->CaptureClassList(DocumentScopedClassListFor(aFrame));
   1428    SetCaptured(aFrame, true, name);
   1429    return true;
   1430  });
   1431  return result;
   1432 }
   1433 
   1434 // https://drafts.csswg.org/css-view-transitions/#setup-view-transition
   1435 void ViewTransition::Setup() {
   1436  AUTO_PROFILER_FLOW_MARKER("ViewTransition::Setup", LAYOUT,
   1437                            Flow::FromPointer(this));
   1438  // Step 2: Capture the old state for transition.
   1439  if (auto skipReason = CaptureOldState()) {
   1440    // If failure is returned, then skip the view transition for transition
   1441    // with an "InvalidStateError" DOMException in transition’s relevant Realm,
   1442    // and return.
   1443    return SkipTransition(*skipReason);
   1444  }
   1445 
   1446  // Step 3: Set document’s rendering suppression for view transitions to true.
   1447  mDocument->SetRenderingSuppressedForViewTransitions(true);
   1448 
   1449  // Step 4: Queue a global task on the DOM manipulation task source, given
   1450  // transition's relevant global object, to perform the following steps:
   1451  //   4.1: If transition's phase is "done", then abort these steps.
   1452  //   4.2: Schedule the update callback for transition.
   1453  //   4.3: Flush the update callback queue.
   1454  mDocument->Dispatch(
   1455      NewRunnableMethod("ViewTransition::MaybeScheduleUpdateCallback", this,
   1456                        &ViewTransition::MaybeScheduleUpdateCallback));
   1457 }
   1458 
   1459 // https://drafts.csswg.org/css-view-transitions-1/#handle-transition-frame
   1460 void ViewTransition::HandleFrame() {
   1461  // Steps 1-3: Steps 1-3: Compute active animations.
   1462  const bool hasActiveAnimations = CheckForActiveAnimations();
   1463 
   1464  // Step 4: If hasActiveAnimations is false:
   1465  if (!hasActiveAnimations) {
   1466    AUTO_PROFILER_TERMINATING_FLOW_MARKER("ViewTransition::HandleFrameFinish",
   1467                                          LAYOUT, Flow::FromPointer(this));
   1468    // 4.1: Set transition's phase to "done".
   1469    mPhase = Phase::Done;
   1470    // 4.2: Clear view transition transition.
   1471    ClearActiveTransition(false);
   1472    // 4.3: Resolve transition's finished promise.
   1473    if (Promise* finished = GetFinished(IgnoreErrors())) {
   1474      finished->MaybeResolveWithUndefined();
   1475    }
   1476    return;
   1477  }
   1478 
   1479  AUTO_PROFILER_FLOW_MARKER("ViewTransition::HandleFrame", LAYOUT,
   1480                            Flow::FromPointer(this));
   1481 
   1482  // Step 5: If transition’s initial snapshot containing block size is not equal
   1483  // to the snapshot containing block size, then skip the view transition for
   1484  // transition with an "InvalidStateError" DOMException in transition’s
   1485  // relevant Realm, and return.
   1486  if (SnapshotContainingBlockRect().Size() !=
   1487      mInitialSnapshotContainingBlockSize) {
   1488    SkipTransition(SkipTransitionReason::Resize);
   1489    return;
   1490  }
   1491 
   1492  // Step 6: Update pseudo-element styles for transition.
   1493  if (!UpdatePseudoElementStyles(/* aNeedsInvalidation= */ true)) {
   1494    // If failure is returned, then skip the view transition for transition
   1495    // with an "InvalidStateError" DOMException in transition's relevant Realm,
   1496    // and return.
   1497    return SkipTransition(SkipTransitionReason::PseudoUpdateFailure);
   1498  }
   1499 
   1500  // If the view transition is still animating after HandleFrame(), we have to
   1501  // periodically perform operations to check if it is still animating in the
   1502  // following ticks.
   1503  mDocument->EnsureViewTransitionOperationsHappen();
   1504 }
   1505 
   1506 static bool CheckForActiveAnimationsForEachPseudo(
   1507    const Element& aRoot, const AnimationTimeline& aDocTimeline,
   1508    const AnimationEventDispatcher& aDispatcher,
   1509    PseudoStyleRequest&& aRequest) {
   1510  // Check EffectSet because an Animation (either a CSS Animations or a
   1511  // script animation) is associated with a KeyframeEffect. If the animation
   1512  // doesn't have an associated effect, we can skip it per spec.
   1513  // If the effect target is not the element we request, it shouldn't be in
   1514  // |effects| either.
   1515  EffectSet* effects = EffectSet::Get(&aRoot, aRequest);
   1516  if (!effects) {
   1517    return false;
   1518  }
   1519 
   1520  for (const auto* effect : *effects) {
   1521    // 3.1: For each animation whose timeline is a document timeline associated
   1522    // with document, and contains at least one associated effect whose effect
   1523    // target is element, set hasActiveAnimations to true if any of the
   1524    // following conditions is true:
   1525    //   * animation’s play state is paused or running.
   1526    //   * document’s pending animation event queue has any events associated
   1527    //     with animation.
   1528 
   1529    MOZ_ASSERT(effect && effect->GetAnimation(),
   1530               "Only effects associated with an animation should be "
   1531               "added to an element's effect set");
   1532    const Animation* anim = effect->GetAnimation();
   1533 
   1534    // The animation's timeline is not the document timeline.
   1535    if (anim->GetTimeline() != &aDocTimeline) {
   1536      continue;
   1537    }
   1538 
   1539    // Return true if any of the following conditions is true:
   1540    // 1. animation’s play state is paused or running.
   1541    // 2. document’s pending animation event queue has any events associated
   1542    //    with animation.
   1543    const auto playState = anim->PlayState();
   1544    if (playState != AnimationPlayState::Paused &&
   1545        playState != AnimationPlayState::Running &&
   1546        !aDispatcher.HasQueuedEventsFor(anim)) {
   1547      continue;
   1548    }
   1549    return true;
   1550  }
   1551  return false;
   1552 }
   1553 
   1554 // This is the implementation of step 3 in HandleFrame(). For each element of
   1555 // transition’s transition root pseudo-element’s inclusive descendants, we check
   1556 // if it has active animations.
   1557 bool ViewTransition::CheckForActiveAnimations() const {
   1558  MOZ_ASSERT(mDocument);
   1559 
   1560  if (StaticPrefs::dom_viewTransitions_remain_active()) {
   1561    return true;
   1562  }
   1563 
   1564  const Element* root = mDocument->GetRootElement();
   1565  if (!root) {
   1566    // The documentElement could be removed during animating via script.
   1567    return false;
   1568  }
   1569 
   1570  const AnimationTimeline* timeline = mDocument->Timeline();
   1571  if (!timeline) {
   1572    return false;
   1573  }
   1574 
   1575  nsPresContext* presContext = mDocument->GetPresContext();
   1576  if (!presContext) {
   1577    return false;
   1578  }
   1579 
   1580  const AnimationEventDispatcher* dispatcher =
   1581      presContext->AnimationEventDispatcher();
   1582  MOZ_ASSERT(dispatcher);
   1583 
   1584  auto checkForEachPseudo = [&](PseudoStyleRequest&& aRequest) {
   1585    return CheckForActiveAnimationsForEachPseudo(*root, *timeline, *dispatcher,
   1586                                                 std::move(aRequest));
   1587  };
   1588 
   1589  bool hasActiveAnimations =
   1590      checkForEachPseudo(PseudoStyleRequest(PseudoStyleType::viewTransition));
   1591  for (nsAtom* name : mNamedElements.Keys()) {
   1592    if (hasActiveAnimations) {
   1593      break;
   1594    }
   1595 
   1596    hasActiveAnimations =
   1597        checkForEachPseudo({PseudoStyleType::viewTransitionGroup, name}) ||
   1598        checkForEachPseudo({PseudoStyleType::viewTransitionImagePair, name}) ||
   1599        checkForEachPseudo({PseudoStyleType::viewTransitionOld, name}) ||
   1600        checkForEachPseudo({PseudoStyleType::viewTransitionNew, name});
   1601  }
   1602  return hasActiveAnimations;
   1603 }
   1604 
   1605 void ViewTransition::ClearNamedElements() {
   1606  for (auto& entry : mNamedElements) {
   1607    if (auto* element = entry.GetData()->mNewElement.get()) {
   1608      if (nsIFrame* f = element->GetPrimaryFrame()) {
   1609        SetCaptured(f, false, nullptr);
   1610      }
   1611    }
   1612  }
   1613  mNamedElements.Clear();
   1614  mNames.Clear();
   1615 }
   1616 
   1617 static void ClearViewTransitionsAnimationData(Element* aRoot) {
   1618  if (!aRoot) {
   1619    return;
   1620  }
   1621 
   1622  auto* data = aRoot->GetAnimationData();
   1623  if (!data) {
   1624    return;
   1625  }
   1626  data->ClearViewTransitionPseudos();
   1627 }
   1628 
   1629 // https://drafts.csswg.org/css-view-transitions-1/#clear-view-transition
   1630 void ViewTransition::ClearActiveTransition(bool aIsDocumentHidden) {
   1631  // Steps 1-2
   1632  MOZ_ASSERT(mDocument);
   1633  MOZ_ASSERT(mDocument->GetActiveViewTransition() == this);
   1634 
   1635  // Ensure that any styles associated with :active-view-transition no longer
   1636  // apply.
   1637  if (auto* root = mDocument->GetRootElement()) {
   1638    root->RemoveStates(ElementState::ACTIVE_VIEW_TRANSITION);
   1639  }
   1640 
   1641  // Step 3
   1642  ClearNamedElements();
   1643 
   1644  // Step 4: Clear show transition tree flag (we just destroy the pseudo tree,
   1645  // see SetupTransitionPseudoElements).
   1646  if (mSnapshotContainingBlock) {
   1647    nsAutoScriptBlocker scriptBlocker;
   1648    if (mDocument->DevToolsAnonymousAndShadowEventsEnabled()) {
   1649      mSnapshotContainingBlock->QueueDevtoolsAnonymousEvent(
   1650          /* aIsRemove = */ true);
   1651    }
   1652    if (PresShell* ps = mDocument->GetPresShell()) {
   1653      ps->ContentWillBeRemoved(mSnapshotContainingBlock, {});
   1654    }
   1655    mSnapshotContainingBlock->UnbindFromTree();
   1656    mSnapshotContainingBlock = nullptr;
   1657 
   1658    // If the document is being destroyed, we cannot get the animation data
   1659    // (e.g. it may crash when using nsINode::GetBoolFlag()), so we have to skip
   1660    // this case. It's fine because those animations should still be stopped and
   1661    // removed if no frame there.
   1662    //
   1663    // Another case is that the document is hidden. In that case, we don't setup
   1664    // the pseudo elements, so it's fine to skip it as well.
   1665    if (!aIsDocumentHidden) {
   1666      ClearViewTransitionsAnimationData(mDocument->GetRootElement());
   1667    }
   1668  }
   1669  mDocument->ClearActiveViewTransition();
   1670 }
   1671 
   1672 void ViewTransition::SkipTransition(SkipTransitionReason aReason) {
   1673  SkipTransition(aReason, JS::UndefinedHandleValue);
   1674 }
   1675 
   1676 // https://drafts.csswg.org/css-view-transitions-1/#skip-the-view-transition
   1677 // https://drafts.csswg.org/css-view-transitions-1/#dom-viewtransition-skiptransition
   1678 void ViewTransition::SkipTransition(
   1679    SkipTransitionReason aReason,
   1680    JS::Handle<JS::Value> aUpdateCallbackRejectReason) {
   1681  MOZ_ASSERT(mDocument);
   1682  MOZ_ASSERT_IF(aReason != SkipTransitionReason::JS, mPhase != Phase::Done);
   1683  MOZ_ASSERT_IF(aReason != SkipTransitionReason::UpdateCallbackRejected,
   1684                aUpdateCallbackRejectReason == JS::UndefinedHandleValue);
   1685  VT_LOG("ViewTransition::SkipTransition(%d, %d)\n", int(mPhase), int(aReason));
   1686  AUTO_PROFILER_TERMINATING_FLOW_MARKER("ViewTransition::SkipTransition",
   1687                                        LAYOUT, Flow::FromPointer(this));
   1688  if (mPhase == Phase::Done) {
   1689    return;
   1690  }
   1691  // Step 3: If transition’s phase is before "update-callback-called", then
   1692  // schedule the update callback for transition.
   1693  if (UnderlyingValue(mPhase) < UnderlyingValue(Phase::UpdateCallbackCalled)) {
   1694    mDocument->ScheduleViewTransitionUpdateCallback(this);
   1695  }
   1696 
   1697  // Step 4: Set rendering suppression for view transitions to false.
   1698  mDocument->SetRenderingSuppressedForViewTransitions(false);
   1699 
   1700  // Step 5: If document's active view transition is transition, Clear view
   1701  // transition transition.
   1702  if (mDocument->GetActiveViewTransition() == this) {
   1703    ClearActiveTransition(aReason == SkipTransitionReason::DocumentHidden);
   1704  }
   1705 
   1706  // Step 6: Set transition's phase to "done".
   1707  mPhase = Phase::Done;
   1708 
   1709  // Step 7: Reject transition's ready promise with reason.
   1710  Promise* ucd = GetUpdateCallbackDone(IgnoreErrors());
   1711  if (Promise* readyPromise = GetReady(IgnoreErrors())) {
   1712    switch (aReason) {
   1713      case SkipTransitionReason::JS:
   1714        readyPromise->MaybeRejectWithAbortError(
   1715            "Skipped ViewTransition due to skipTransition() call");
   1716        break;
   1717      case SkipTransitionReason::ClobberedActiveTransition:
   1718        readyPromise->MaybeRejectWithAbortError(
   1719            "Skipped ViewTransition due to another transition starting");
   1720        break;
   1721      case SkipTransitionReason::DocumentHidden:
   1722        readyPromise->MaybeRejectWithInvalidStateError(
   1723            "Skipped ViewTransition due to document being hidden");
   1724        break;
   1725      case SkipTransitionReason::Timeout:
   1726        readyPromise->MaybeRejectWithTimeoutError(
   1727            "Skipped ViewTransition due to timeout");
   1728        break;
   1729      case SkipTransitionReason::DuplicateTransitionNameCapturingOldState:
   1730        readyPromise->MaybeRejectWithInvalidStateError(
   1731            "Duplicate view-transition-name value while capturing old state");
   1732        break;
   1733      case SkipTransitionReason::DuplicateTransitionNameCapturingNewState:
   1734        readyPromise->MaybeRejectWithInvalidStateError(
   1735            "Duplicate view-transition-name value while capturing new state");
   1736        break;
   1737      case SkipTransitionReason::RootRemoved:
   1738        readyPromise->MaybeRejectWithInvalidStateError(
   1739            "Skipped view transition due to root element going away");
   1740        break;
   1741      case SkipTransitionReason::PageSwap:
   1742        readyPromise->MaybeRejectWithInvalidStateError(
   1743            "Skipped view transition due to page swap");
   1744        break;
   1745      case SkipTransitionReason::Resize:
   1746        readyPromise->MaybeRejectWithInvalidStateError(
   1747            "Skipped view transition due to viewport resize");
   1748        break;
   1749      case SkipTransitionReason::PseudoUpdateFailure:
   1750        readyPromise->MaybeRejectWithInvalidStateError(
   1751            "Skipped view transition due to hidden new element");
   1752        break;
   1753      case SkipTransitionReason::ResetRendering:
   1754        readyPromise->MaybeRejectWithInvalidStateError(
   1755            "Skipped view transition due to graphics process or device reset");
   1756        break;
   1757      case SkipTransitionReason::UpdateCallbackRejected:
   1758        readyPromise->MaybeReject(aUpdateCallbackRejectReason);
   1759 
   1760        // Step 8, The case we have to reject the finished promise. Do this here
   1761        // to make sure it reacts to UpdateCallbackRejected.
   1762        //
   1763        // Note: we intentionally reject the finished promise after the ready
   1764        // promise to make sure the order of promise callbacks is correct in
   1765        // script.
   1766        if (ucd) {
   1767          MOZ_ASSERT(ucd->State() == Promise::PromiseState::Rejected);
   1768          if (Promise* finished = GetFinished(IgnoreErrors())) {
   1769            // Since the rejection of transition’s update callback done promise
   1770            // isn’t explicitly handled here, if transition’s update callback
   1771            // done promise rejects, then transition’s finished promise will
   1772            // reject with the same reason.
   1773            finished->MaybeReject(aUpdateCallbackRejectReason);
   1774          }
   1775        }
   1776        break;
   1777    }
   1778  }
   1779 
   1780  // Step 8: Resolve transition's finished promise with the result of reacting
   1781  // to transition's update callback done promise:
   1782  // Note: It is not guaranteed that |mPhase| is Done in CallUpdateCallback().
   1783  // There are two possible cases:
   1784  // 1. If we skip the view transitions before updateCallbackDone callback
   1785  //    is dispatched, we come here first. In this case we don't have to resolve
   1786  //    the finsihed promise because CallUpdateCallback() will do it.
   1787  // 2. If we skip the view transitions after updateCallbackDone callback, the
   1788  //    finished promise hasn't been resolved because |mPhase| is not Done (i.e.
   1789  //    |mPhase| is UpdateCallbackCalled) when we handle updateCallbackDone
   1790  //    callback. Therefore, we have to resolve the finished promise based on
   1791  //    the PromiseState of |mUpdateCallbackDone|.
   1792  if (ucd && ucd->State() == Promise::PromiseState::Resolved) {
   1793    if (Promise* finished = GetFinished(IgnoreErrors())) {
   1794      // If the promise was fulfilled, then return undefined.
   1795      finished->MaybeResolveWithUndefined();
   1796    }
   1797  }
   1798 }
   1799 
   1800 Maybe<uint64_t> ViewTransition::GetElementIdentifier(Element* aElement) const {
   1801  return mElementIdentifiers.MaybeGet(aElement);
   1802 }
   1803 
   1804 uint64_t ViewTransition::EnsureElementIdentifier(Element* aElement) {
   1805  static uint64_t sLastIdentifier = 0;
   1806  return mElementIdentifiers.WithEntryHandle(aElement, [&](auto&& entry) {
   1807    return entry.OrInsertWith([&]() { return sLastIdentifier++; });
   1808  });
   1809 }
   1810 
   1811 already_AddRefed<nsAtom> ViewTransition::DocumentScopedTransitionNameFor(
   1812    nsIFrame* aFrame) {
   1813  // TODO(emilio): Bug 1970954. These aren't quite correct, per spec we're
   1814  // supposed to only honor names and classes coming from the document, but
   1815  // that's quite some magic, and it's getting actively discussed, see:
   1816  // https://github.com/w3c/csswg-drafts/issues/10808 and related
   1817  // https://drafts.csswg.org/css-view-transitions-1/#document-scoped-view-transition-name
   1818  // https://drafts.csswg.org/css-view-transitions-2/#additions-to-vt-name
   1819  // 1. Let computed be the computed value of view-transition-name.
   1820  const auto& computed = aFrame->StyleUIReset()->mViewTransitionName;
   1821 
   1822  // 2. If computed is none, return null.
   1823  if (computed.IsNone()) {
   1824    return nullptr;
   1825  }
   1826 
   1827  // As a special case, if we're a <table> element, the table wrapper is what's
   1828  // captured.
   1829  if (aFrame->IsTableFrame()) {
   1830    return nullptr;
   1831  }
   1832 
   1833  // 3. If computed is a <custom-ident>, return computed.
   1834  if (computed.IsIdent()) {
   1835    return RefPtr<nsAtom>{computed.AsIdent().AsAtom()}.forget();
   1836  }
   1837 
   1838  // 4. Assert: computed is auto or match-element.
   1839  // TODO: Bug 1918218. Implement auto or others, depending on the spec issue.
   1840  // https://github.com/w3c/csswg-drafts/issues/12091
   1841  MOZ_ASSERT(computed.IsMatchElement());
   1842 
   1843  // 5. If computed is auto, element has an associated id, and computed is
   1844  // associated with the same root as element’s root, then return a unique
   1845  // string starting with "-ua-". Two elements with the same id must return the
   1846  // same string, regardless of their node document.
   1847  // TODO: Bug 1918218. auto keyword may be changed. See the spec issue
   1848  // mentioned above..
   1849 
   1850  // 6. Return a unique string starting with "-ua-". The string should remain
   1851  // consistent and unique for this element and Document, at least for the
   1852  // lifetime of element’s node document’s active view transition.
   1853  nsIContent* content = aFrame->GetContent();
   1854  if (MOZ_UNLIKELY(!content || !content->IsElement())) {
   1855    return nullptr;
   1856  }
   1857 
   1858  uint64_t id = EnsureElementIdentifier(content->AsElement());
   1859 
   1860  // FIXME: We may have to revist here when working on cross document because we
   1861  // may have to return a warning and nullptr, per the comment in the design
   1862  // review.
   1863  // https://github.com/w3ctag/design-reviews/issues/1001#issuecomment-2750966335
   1864  nsCString name;
   1865  // Note: Add the "view-transition-name" in the prefix so we know this is for
   1866  // auto-generated view-transition-name.
   1867  name.AppendLiteral("-ua-view-transition-name-");
   1868  name.AppendInt(id);
   1869  return NS_Atomize(name);
   1870 }
   1871 
   1872 JSObject* ViewTransition::WrapObject(JSContext* aCx,
   1873                                     JS::Handle<JSObject*> aGivenProto) {
   1874  return ViewTransition_Binding::Wrap(aCx, this, aGivenProto);
   1875 }
   1876 
   1877 static void ComputeActiveRect1D(nscoord aViewMin, nscoord aViewSize,
   1878                                nscoord& aCaptureMin, nscoord& aCaptureSize) {
   1879  nscoord captureMax = aCaptureMin + aCaptureSize;
   1880  nscoord viewMax = aViewMin + aViewSize;
   1881 
   1882  nscoord min;
   1883  nscoord max;
   1884 
   1885  if (aCaptureSize < aViewSize) {
   1886    // The snapshot area is small enough on this axis, don't clip it.
   1887    min = aCaptureMin;
   1888    max = min + aCaptureSize;
   1889  } else if (aViewMin < aCaptureMin) {
   1890    // The view is before the capture area. Restrict the cpature size while
   1891    // snaping it to the beginning of its range.
   1892    min = aCaptureMin;
   1893    max = min + aViewSize;
   1894  } else if (viewMax > captureMax) {
   1895    // The view is after the capture area. Restrict the cpature size while
   1896    // snaping it to the end of its range.
   1897    max = captureMax;
   1898    min = max - aViewSize;
   1899  } else {
   1900    // The snapshot area extends beyond the viewport on both sides,
   1901    // set it to the viewport.
   1902    min = aViewMin;
   1903    max = viewMax;
   1904  }
   1905 
   1906  aCaptureMin = min;
   1907  aCaptureSize = max - min;
   1908 }
   1909 
   1910 void ViewTransition::UpdateActiveRectForCapturedFrame(
   1911    nsIFrame* aCapturedFrame, const gfx::MatrixScales& aInheritedScale,
   1912    nsRect& aOutCaptureRect) {
   1913  nsAtom* name = aCapturedFrame->GetProperty(ViewTransitionCaptureName());
   1914  if (NS_WARN_IF(!name)) {
   1915    return;
   1916  }
   1917 
   1918  auto* el = mNamedElements.Get(name);
   1919  if (NS_WARN_IF(!el)) {
   1920    return;
   1921  }
   1922 
   1923  const bool isOld = mPhase < Phase::Animating;
   1924 
   1925  // The active rect to update (old or new).
   1926  Maybe<nsRect>* activeRect;
   1927  if (isOld) {
   1928    activeRect = &el->mOldActiveRect;
   1929    // We don't rely on it, but as a sanity check, since we only capture
   1930    // the old state once so we aren't expecting it to already contain an
   1931    // active rect.
   1932    MOZ_ASSERT(activeRect->isNothing());
   1933  } else {
   1934    activeRect = &el->mNewActiveRect;
   1935  }
   1936 
   1937  // Reset the active rect in case we early out and had previously
   1938  // updated it.
   1939  activeRect->reset();
   1940 
   1941  auto presShell = aCapturedFrame->PresShell();
   1942  if (!presShell->IsVisualViewportSizeSet()) {
   1943    return;
   1944  }
   1945 
   1946  nsPresContext* pc = aCapturedFrame->PresContext();
   1947 
   1948  auto rootViewportSize = presShell->GetVisualViewportSize();
   1949  auto auPerDevPx = pc->AppUnitsPerDevPixel();
   1950  auto vvpSize = LayoutDeviceSize::FromAppUnits(rootViewportSize, auPerDevPx);
   1951  auto capSize =
   1952      LayoutDeviceSize::FromAppUnits(aOutCaptureRect.Size(), auPerDevPx);
   1953  capSize.width *= aInheritedScale.xScale;
   1954  capSize.height *= aInheritedScale.yScale;
   1955 
   1956  // If the capture size is smaller than the visual viewport, it's a good
   1957  // indication that it is not unreasonably large, so we can early out.
   1958  if (capSize.width < vvpSize.width && capSize.height < vvpSize.height) {
   1959    return;
   1960  }
   1961 
   1962  // viewport is relative to the root frame.
   1963  // auto rootViewportOrigin = presShell->GetVisualViewportOffset();
   1964  auto rootViewportOrigin = nsPoint(0, 0);
   1965  nsRect viewport = nsRect(rootViewportOrigin, rootViewportSize);
   1966 
   1967  // Inflate the viewport rect to give a bit of extra headroom in case the user
   1968  // scrolls a bit during the transition or the transition has some motion to
   1969  // it. But at the same time we want to avoid the margin pushing the snapshot
   1970  // size over 4k pixels since that will cause WebRender to render it
   1971  // downscaled.
   1972  float scale = std::max(aInheritedScale.xScale, aInheritedScale.yScale);
   1973  nscoord margin = NSFloatPixelsToAppUnits(512.0 / scale, auPerDevPx);
   1974  nscoord maxSize = NSFloatPixelsToAppUnits(4096.0 / scale, auPerDevPx);
   1975  margin = std::min(
   1976      margin,
   1977      std::max(0, maxSize - std::max(viewport.width, viewport.height)) / 2);
   1978 
   1979  viewport.Inflate(margin);
   1980 
   1981  nsIFrame* rootFrame = pc->GetPresShell()->GetRootFrame();
   1982 
   1983  const auto SUCCESS = nsLayoutUtils::TransformResult::TRANSFORM_SUCCEEDED;
   1984  if (!rootFrame || nsLayoutUtils::TransformRect(rootFrame, aCapturedFrame,
   1985                                                 viewport) != SUCCESS) {
   1986    return;
   1987  }
   1988  // viewport is now relative to aCapturedFrame.
   1989 
   1990  ComputeActiveRect1D(viewport.x, viewport.width, aOutCaptureRect.x,
   1991                      aOutCaptureRect.width);
   1992  ComputeActiveRect1D(viewport.y, viewport.height, aOutCaptureRect.y,
   1993                      aOutCaptureRect.height);
   1994 
   1995  // Store the active rect for later when we create the image items.
   1996  *activeRect = Some(aOutCaptureRect);
   1997 }
   1998 
   1999 Maybe<nsRect> ViewTransition::GetOldActiveRect(nsAtom* aName) const {
   2000  auto* el = mNamedElements.Get(aName);
   2001  if (NS_WARN_IF(!el)) {
   2002    return Nothing();
   2003  }
   2004 
   2005  return el->mOldActiveRect;
   2006 }
   2007 
   2008 Maybe<nsRect> ViewTransition::GetNewActiveRect(nsAtom* aName) const {
   2009  auto* el = mNamedElements.Get(aName);
   2010  if (NS_WARN_IF(!el)) {
   2011    return Nothing();
   2012  }
   2013 
   2014  return el->mNewActiveRect;
   2015 }
   2016 
   2017 ViewTransitionTypeSet* ViewTransition::Types() {
   2018  if (!mTypes) {
   2019    mTypes = new ViewTransitionTypeSet(*this);
   2020    for (const auto& type : mTypeList) {
   2021      ViewTransitionTypeSet_Binding::SetlikeHelpers::Add(
   2022          mTypes, nsDependentAtomString(type), IgnoreErrors());
   2023    }
   2024  }
   2025  return mTypes;
   2026 }
   2027 
   2028 };  // namespace mozilla::dom