tor-browser

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

ActiveLayerTracker.cpp (15798B)


      1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
      3 /* This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this
      5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 #include "ActiveLayerTracker.h"
      8 
      9 #include "gfx2DGlue.h"
     10 #include "mozilla/AnimationUtils.h"
     11 #include "mozilla/EffectSet.h"
     12 #include "mozilla/MotionPathUtils.h"
     13 #include "mozilla/PodOperations.h"
     14 #include "mozilla/StaticPtr.h"
     15 #include "mozilla/dom/Document.h"
     16 #include "mozilla/gfx/Matrix.h"
     17 #include "mozilla/gfx/gfxVars.h"
     18 #include "nsAnimationManager.h"
     19 #include "nsContainerFrame.h"
     20 #include "nsDOMCSSDeclaration.h"
     21 #include "nsDisplayList.h"
     22 #include "nsExpirationTracker.h"
     23 #include "nsIContent.h"
     24 #include "nsLayoutUtils.h"
     25 #include "nsPIDOMWindow.h"
     26 #include "nsRefreshDriver.h"
     27 #include "nsStyleTransformMatrix.h"
     28 #include "nsTransitionManager.h"
     29 
     30 namespace mozilla {
     31 
     32 using namespace gfx;
     33 
     34 /**
     35 * This tracks the state of a frame that may need active layers due to
     36 * ongoing content changes or style changes that indicate animation.
     37 *
     38 * When no changes of *any* kind are detected after 75-100ms we remove this
     39 * object. Because we only track all kinds of activity with a single
     40 * nsExpirationTracker, it's possible a frame might remain active somewhat
     41 * spuriously if different kinds of changes kept happening, but that almost
     42 * certainly doesn't matter.
     43 */
     44 class LayerActivity {
     45 public:
     46  enum ActivityIndex {
     47    ACTIVITY_OPACITY,
     48    ACTIVITY_TRANSFORM,
     49 
     50    ACTIVITY_SCALE,
     51    ACTIVITY_TRIGGERED_REPAINT,
     52 
     53    // keep as last item
     54    ACTIVITY_COUNT
     55  };
     56 
     57  explicit LayerActivity(nsIFrame* aFrame) : mFrame(aFrame), mContent(nullptr) {
     58    PodArrayZero(mRestyleCounts);
     59  }
     60  ~LayerActivity();
     61  nsExpirationState* GetExpirationState() { return &mState; }
     62  uint8_t& RestyleCountForProperty(NonCustomCSSPropertyId aProperty) {
     63    return mRestyleCounts[GetActivityIndexForProperty(aProperty)];
     64  }
     65 
     66  static ActivityIndex GetActivityIndexForProperty(
     67      NonCustomCSSPropertyId aProperty) {
     68    switch (aProperty) {
     69      case eCSSProperty_opacity:
     70        return ACTIVITY_OPACITY;
     71      case eCSSProperty_transform:
     72      case eCSSProperty_translate:
     73      case eCSSProperty_rotate:
     74      case eCSSProperty_scale:
     75      case eCSSProperty_offset_path:
     76      case eCSSProperty_offset_distance:
     77      case eCSSProperty_offset_rotate:
     78      case eCSSProperty_offset_anchor:
     79      case eCSSProperty_offset_position:
     80        return ACTIVITY_TRANSFORM;
     81      default:
     82        MOZ_ASSERT(false);
     83        return ACTIVITY_OPACITY;
     84    }
     85  }
     86 
     87  static ActivityIndex GetActivityIndexForPropertySet(
     88      const nsCSSPropertyIDSet& aPropertySet) {
     89    if (aPropertySet.IsSubsetOf(
     90            nsCSSPropertyIDSet::TransformLikeProperties())) {
     91      return ACTIVITY_TRANSFORM;
     92    }
     93    MOZ_ASSERT(
     94        aPropertySet.IsSubsetOf(nsCSSPropertyIDSet::OpacityProperties()));
     95    return ACTIVITY_OPACITY;
     96  }
     97 
     98  // While tracked, exactly one of mFrame or mContent is non-null, depending
     99  // on whether this property is stored on a frame or on a content node.
    100  // When this property is expired by the layer activity tracker, both mFrame
    101  // and mContent are nulled-out and the property is deleted.
    102  nsIFrame* mFrame;
    103  nsIContent* mContent;
    104 
    105  nsExpirationState mState;
    106 
    107  // Previous scale due to the CSS transform property.
    108  Maybe<MatrixScales> mPreviousTransformScale;
    109 
    110  // Number of restyle operations detected
    111  uint8_t mRestyleCounts[ACTIVITY_COUNT];
    112 };
    113 
    114 class LayerActivityTracker final
    115    : public nsExpirationTracker<LayerActivity, 4> {
    116 public:
    117  // 75-100ms is a good timeout period. We use 4 generations of 25ms each.
    118  enum { GENERATION_MS = 100 };
    119 
    120  explicit LayerActivityTracker(nsIEventTarget* aEventTarget)
    121      : nsExpirationTracker<LayerActivity, 4>(
    122            GENERATION_MS, "LayerActivityTracker"_ns, aEventTarget) {}
    123  ~LayerActivityTracker() override { AgeAllGenerations(); }
    124 
    125  void NotifyExpired(LayerActivity* aObject) override;
    126 };
    127 
    128 static StaticAutoPtr<LayerActivityTracker> gLayerActivityTracker;
    129 
    130 LayerActivity::~LayerActivity() {
    131  if (mFrame || mContent) {
    132    NS_ASSERTION(gLayerActivityTracker, "Should still have a tracker");
    133    gLayerActivityTracker->RemoveObject(this);
    134  }
    135 }
    136 
    137 // Frames with this property have NS_FRAME_HAS_LAYER_ACTIVITY_PROPERTY set
    138 NS_DECLARE_FRAME_PROPERTY_DELETABLE(LayerActivityProperty, LayerActivity)
    139 
    140 void LayerActivityTracker::NotifyExpired(LayerActivity* aObject) {
    141  RemoveObject(aObject);
    142 
    143  nsIFrame* f = aObject->mFrame;
    144  nsIContent* c = aObject->mContent;
    145  aObject->mFrame = nullptr;
    146  aObject->mContent = nullptr;
    147 
    148  MOZ_ASSERT((f == nullptr) != (c == nullptr),
    149             "A LayerActivity object should always have a reference to either "
    150             "its frame or its content");
    151 
    152  if (f) {
    153    f->RemoveStateBits(NS_FRAME_HAS_LAYER_ACTIVITY_PROPERTY);
    154    f->RemoveProperty(LayerActivityProperty());
    155  } else {
    156    c->RemoveProperty(nsGkAtoms::LayerActivity);
    157  }
    158 }
    159 
    160 static LayerActivity* GetLayerActivity(nsIFrame* aFrame) {
    161  if (!aFrame->HasAnyStateBits(NS_FRAME_HAS_LAYER_ACTIVITY_PROPERTY)) {
    162    return nullptr;
    163  }
    164  return aFrame->GetProperty(LayerActivityProperty());
    165 }
    166 
    167 static LayerActivity* GetLayerActivityForUpdate(nsIFrame* aFrame) {
    168  LayerActivity* layerActivity = GetLayerActivity(aFrame);
    169  if (layerActivity) {
    170    gLayerActivityTracker->MarkUsed(layerActivity);
    171  } else {
    172    if (!gLayerActivityTracker) {
    173      gLayerActivityTracker =
    174          new LayerActivityTracker(GetMainThreadSerialEventTarget());
    175    }
    176    layerActivity = new LayerActivity(aFrame);
    177    gLayerActivityTracker->AddObject(layerActivity);
    178    aFrame->AddStateBits(NS_FRAME_HAS_LAYER_ACTIVITY_PROPERTY);
    179    aFrame->SetProperty(LayerActivityProperty(), layerActivity);
    180  }
    181  return layerActivity;
    182 }
    183 
    184 static void IncrementMutationCount(uint8_t* aCount) {
    185  *aCount = uint8_t(std::min(0xFF, *aCount + 1));
    186 }
    187 
    188 /* static */
    189 void ActiveLayerTracker::TransferActivityToContent(nsIFrame* aFrame,
    190                                                   nsIContent* aContent) {
    191  if (!aFrame->HasAnyStateBits(NS_FRAME_HAS_LAYER_ACTIVITY_PROPERTY)) {
    192    return;
    193  }
    194  LayerActivity* layerActivity = aFrame->TakeProperty(LayerActivityProperty());
    195  aFrame->RemoveStateBits(NS_FRAME_HAS_LAYER_ACTIVITY_PROPERTY);
    196  if (!layerActivity) {
    197    return;
    198  }
    199  layerActivity->mFrame = nullptr;
    200  layerActivity->mContent = aContent;
    201  aContent->SetProperty(nsGkAtoms::LayerActivity, layerActivity,
    202                        nsINode::DeleteProperty<LayerActivity>, true);
    203 }
    204 
    205 /* static */
    206 void ActiveLayerTracker::TransferActivityToFrame(nsIContent* aContent,
    207                                                 nsIFrame* aFrame) {
    208  auto* layerActivity = static_cast<LayerActivity*>(
    209      aContent->TakeProperty(nsGkAtoms::LayerActivity));
    210  if (!layerActivity) {
    211    return;
    212  }
    213  layerActivity->mContent = nullptr;
    214  layerActivity->mFrame = aFrame;
    215  aFrame->AddStateBits(NS_FRAME_HAS_LAYER_ACTIVITY_PROPERTY);
    216  aFrame->SetProperty(LayerActivityProperty(), layerActivity);
    217 }
    218 
    219 static void IncrementScaleRestyleCountIfNeeded(nsIFrame* aFrame,
    220                                               LayerActivity* aActivity) {
    221  // This function is basically a simplified copy of
    222  // nsDisplayTransform::GetResultingTransformMatrixInternal.
    223 
    224  Matrix parentsChildrenOnlyTransform;
    225  const bool parentHasChildrenOnlyTransform =
    226      aFrame->GetParentSVGTransforms(&parentsChildrenOnlyTransform);
    227 
    228  const nsStyleDisplay* display = aFrame->StyleDisplay();
    229  if (!aFrame->HasAnyStateBits(NS_FRAME_MAY_BE_TRANSFORMED) ||
    230      (!display->HasTransformProperty() && !display->HasIndividualTransform() &&
    231       display->mOffsetPath.IsNone() && !parentHasChildrenOnlyTransform)) {
    232    if (aActivity->mPreviousTransformScale.isSome()) {
    233      // The transform was removed.
    234      aActivity->mPreviousTransformScale = Nothing();
    235      IncrementMutationCount(
    236          &aActivity->mRestyleCounts[LayerActivity::ACTIVITY_SCALE]);
    237    }
    238 
    239    return;
    240  }
    241 
    242  Matrix4x4 transform;
    243  if (aFrame->IsCSSTransformed()) {
    244    // Compute the new scale due to the CSS transform property.
    245    // Note: Motion path doesn't contribute to scale factor. (It only has 2d
    246    // translate and 2d rotate, so we use Nothing() for it.)
    247    nsStyleTransformMatrix::TransformReferenceBox refBox(aFrame);
    248    transform = nsStyleTransformMatrix::ReadTransforms(
    249        display->mTranslate, display->mRotate, display->mScale, nullptr,
    250        display->mTransform, refBox, AppUnitsPerCSSPixel());
    251  }
    252 
    253  if (parentHasChildrenOnlyTransform) {
    254    transform *= Matrix4x4::From2D(parentsChildrenOnlyTransform);
    255  }
    256 
    257  Matrix transform2D;
    258  if (!transform.Is2D(&transform2D)) {
    259    // We don't attempt to handle 3D transforms; just assume the scale changed.
    260    aActivity->mPreviousTransformScale = Nothing();
    261    IncrementMutationCount(
    262        &aActivity->mRestyleCounts[LayerActivity::ACTIVITY_SCALE]);
    263    return;
    264  }
    265 
    266  MatrixScales scale = transform2D.ScaleFactors();
    267  if (aActivity->mPreviousTransformScale == Some(scale)) {
    268    return;  // Nothing changed.
    269  }
    270 
    271  aActivity->mPreviousTransformScale = Some(scale);
    272  IncrementMutationCount(
    273      &aActivity->mRestyleCounts[LayerActivity::ACTIVITY_SCALE]);
    274 }
    275 
    276 /* static */
    277 void ActiveLayerTracker::NotifyRestyle(nsIFrame* aFrame,
    278                                       NonCustomCSSPropertyId aProperty) {
    279  LayerActivity* layerActivity = GetLayerActivityForUpdate(aFrame);
    280  uint8_t& mutationCount = layerActivity->RestyleCountForProperty(aProperty);
    281  IncrementMutationCount(&mutationCount);
    282 
    283  if (nsCSSPropertyIDSet::TransformLikeProperties().HasProperty(aProperty)) {
    284    IncrementScaleRestyleCountIfNeeded(aFrame, layerActivity);
    285  }
    286 }
    287 
    288 static bool IsPresContextInScriptAnimationCallback(
    289    nsPresContext* aPresContext) {
    290  if (aPresContext->RefreshDriver()->IsInRefresh()) {
    291    return true;
    292  }
    293  // Treat timeouts/setintervals as scripted animation callbacks for our
    294  // purposes.
    295  nsGlobalWindowInner* win =
    296      nsGlobalWindowInner::Cast(aPresContext->Document()->GetInnerWindow());
    297  return win && win->IsRunningTimeout();
    298 }
    299 
    300 /* static */
    301 void ActiveLayerTracker::NotifyInlineStyleRuleModified(
    302    nsIFrame* aFrame, NonCustomCSSPropertyId aProperty) {
    303  if (IsPresContextInScriptAnimationCallback(aFrame->PresContext())) {
    304    LayerActivity* layerActivity = GetLayerActivityForUpdate(aFrame);
    305    // We know this is animated, so just hack the mutation count.
    306    layerActivity->RestyleCountForProperty(aProperty) = 0xff;
    307  }
    308 }
    309 
    310 /* static */
    311 void ActiveLayerTracker::NotifyNeedsRepaint(nsIFrame* aFrame) {
    312  LayerActivity* layerActivity = GetLayerActivityForUpdate(aFrame);
    313  if (IsPresContextInScriptAnimationCallback(aFrame->PresContext())) {
    314    // This is mirroring NotifyInlineStyleRuleModified's NotifyAnimated logic.
    315    // Just max out the restyle count if we're in an animation callback.
    316    layerActivity->mRestyleCounts[LayerActivity::ACTIVITY_TRIGGERED_REPAINT] =
    317        0xFF;
    318  } else {
    319    IncrementMutationCount(
    320        &layerActivity
    321             ->mRestyleCounts[LayerActivity::ACTIVITY_TRIGGERED_REPAINT]);
    322  }
    323 }
    324 
    325 static bool IsMotionPathAnimated(nsDisplayListBuilder* aBuilder,
    326                                 nsIFrame* aFrame) {
    327  return ActiveLayerTracker::IsStyleAnimated(
    328             aBuilder, aFrame, nsCSSPropertyIDSet{eCSSProperty_offset_path}) ||
    329         (!aFrame->StyleDisplay()->mOffsetPath.IsNone() &&
    330          ActiveLayerTracker::IsStyleAnimated(
    331              aBuilder, aFrame,
    332              nsCSSPropertyIDSet{
    333                  eCSSProperty_offset_distance, eCSSProperty_offset_rotate,
    334                  eCSSProperty_offset_anchor, eCSSProperty_offset_position}));
    335 }
    336 
    337 /* static */
    338 bool ActiveLayerTracker::IsTransformAnimated(nsDisplayListBuilder* aBuilder,
    339                                             nsIFrame* aFrame) {
    340  return IsStyleAnimated(aBuilder, aFrame,
    341                         nsCSSPropertyIDSet::CSSTransformProperties()) ||
    342         IsMotionPathAnimated(aBuilder, aFrame);
    343 }
    344 
    345 /* static */
    346 bool ActiveLayerTracker::IsTransformMaybeAnimated(nsIFrame* aFrame) {
    347  return IsStyleAnimated(nullptr, aFrame,
    348                         nsCSSPropertyIDSet::CSSTransformProperties()) ||
    349         IsMotionPathAnimated(nullptr, aFrame);
    350 }
    351 
    352 /* static */
    353 bool ActiveLayerTracker::IsStyleAnimated(
    354    nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
    355    const nsCSSPropertyIDSet& aPropertySet) {
    356  MOZ_ASSERT(
    357      aPropertySet.IsSubsetOf(nsCSSPropertyIDSet::TransformLikeProperties()) ||
    358          aPropertySet.IsSubsetOf(nsCSSPropertyIDSet::OpacityProperties()),
    359      "Only subset of opacity or transform-like properties set calls this");
    360 
    361  // For display:table content, transforms are applied to the table wrapper
    362  // (primary frame) but their will-change style will be specified on the style
    363  // frame and, unlike other transform properties, not inherited.
    364  // As a result, for transform properties only we need to be careful to look up
    365  // the will-change style on the _style_ frame.
    366  const nsIFrame* styleFrame = nsLayoutUtils::GetStyleFrame(aFrame);
    367  const nsCSSPropertyIDSet transformSet =
    368      nsCSSPropertyIDSet::TransformLikeProperties();
    369  if ((styleFrame && (styleFrame->StyleDisplay()->mWillChange.bits &
    370                      StyleWillChangeBits::TRANSFORM)) &&
    371      aPropertySet.Intersects(transformSet) &&
    372      (!aBuilder ||
    373       aBuilder->IsInWillChangeBudget(aFrame, aFrame->GetSize()))) {
    374    return true;
    375  }
    376  if ((aFrame->StyleDisplay()->mWillChange.bits &
    377       StyleWillChangeBits::OPACITY) &&
    378      aPropertySet.Intersects(nsCSSPropertyIDSet::OpacityProperties()) &&
    379      (!aBuilder ||
    380       aBuilder->IsInWillChangeBudget(aFrame, aFrame->GetSize()))) {
    381    return !StaticPrefs::gfx_will_change_ignore_opacity();
    382  }
    383 
    384  LayerActivity* layerActivity = GetLayerActivity(aFrame);
    385  if (layerActivity) {
    386    LayerActivity::ActivityIndex activityIndex =
    387        LayerActivity::GetActivityIndexForPropertySet(aPropertySet);
    388    if (layerActivity->mRestyleCounts[activityIndex] >= 2) {
    389      // If the frame needs to be repainted frequently, we probably don't get
    390      // much from treating the property as animated, *unless* this frame's
    391      // 'scale' (which includes the bounds changes of a rotation) is changing.
    392      // Marking a scaling transform as animating allows us to avoid resizing
    393      // the texture, even if we have to repaint the contents of that texture.
    394      if (layerActivity
    395                  ->mRestyleCounts[LayerActivity::ACTIVITY_TRIGGERED_REPAINT] <
    396              2 ||
    397          (aPropertySet.Intersects(transformSet) &&
    398           IsScaleSubjectToAnimation(aFrame))) {
    399        return true;
    400      }
    401    }
    402  }
    403 
    404  if (nsLayoutUtils::HasEffectiveAnimation(aFrame, aPropertySet)) {
    405    return true;
    406  }
    407 
    408  if (!aPropertySet.Intersects(transformSet) ||
    409      !aFrame->Combines3DTransformWithAncestors()) {
    410    return false;
    411  }
    412 
    413  // For preserve-3d, we check if there is any transform animation on its parent
    414  // frames in the 3d rendering context. If there is one, this function will
    415  // return true.
    416  return IsStyleAnimated(aBuilder, aFrame->GetParent(), aPropertySet);
    417 }
    418 
    419 /* static */
    420 bool ActiveLayerTracker::IsScaleSubjectToAnimation(nsIFrame* aFrame) {
    421  // Check whether JavaScript is animating this frame's scale.
    422  LayerActivity* layerActivity = GetLayerActivity(aFrame);
    423  if (layerActivity &&
    424      layerActivity->mRestyleCounts[LayerActivity::ACTIVITY_SCALE] >= 2) {
    425    return true;
    426  }
    427 
    428  return AnimationUtils::FrameHasAnimatedScale(aFrame);
    429 }
    430 
    431 /* static */
    432 void ActiveLayerTracker::Shutdown() { gLayerActivityTracker = nullptr; }
    433 
    434 }  // namespace mozilla