Bug 1372118 - Part5. Support OMTA.
MozReview-Commit-ID: BbYDzZUUluQ
--- a/gfx/layers/wr/WebRenderLayerManager.cpp
+++ b/gfx/layers/wr/WebRenderLayerManager.cpp
@@ -15,23 +15,30 @@
#include "WebRenderCanvasLayer.h"
#include "WebRenderColorLayer.h"
#include "WebRenderContainerLayer.h"
#include "WebRenderImageLayer.h"
#include "WebRenderPaintedLayer.h"
#include "WebRenderPaintedLayerBlob.h"
#include "WebRenderTextLayer.h"
#include "WebRenderDisplayItemLayer.h"
+#include "nsDisplayList.h"
namespace mozilla {
using namespace gfx;
namespace layers {
+void WMCanvasData::EmptyTransaction()
+{
+ MOZ_ASSERT(mLayer);
+ static_cast<WebRenderCanvasLayer*>(WebRenderLayer::ToWebRenderLayer(mLayer))->UpdateCompositableClient();
+}
+
WebRenderLayerManager::WebRenderLayerManager(nsIWidget* aWidget)
: mWidget(aWidget)
, mLatestTransactionId(0)
, mNeedsComposite(false)
, mIsFirstPaint(false)
, mTarget(nullptr)
, mPaintSequenceNumber(0)
{
@@ -140,16 +147,22 @@ WebRenderLayerManager::BeginTransaction(
bool
WebRenderLayerManager::EndEmptyTransaction(EndTransactionFlags aFlags)
{
if (!mRoot) {
return false;
}
+ if (gfxPrefs::WebRenderLayerFree() &&
+ XRE_IsContentProcess()) {
+ WebRenderLayerManager::EndTransactionWithoutLayer(nullptr, nullptr);
+ return true;
+ }
+
// We might used painted layer images so don't delete them yet.
return EndTransactionInternal(nullptr, nullptr, aFlags);
}
/*static*/ int32_t
PopulateScrollData(WebRenderScrollData& aTarget, Layer* aLayer)
{
MOZ_ASSERT(aLayer);
@@ -246,18 +259,38 @@ WebRenderLayerManager::EndTransactionWit
wr::DisplayListBuilder builder(WrBridge()->GetPipeline(), contentSize);
if (aDisplayList && aDisplayListBuilder) {
StackingContextHelper sc;
mParentCommands.Clear();
CreateWebRenderCommandsFromDisplayList(this, aDisplayList, aDisplayListBuilder, sc, builder);
builder.Finalize(contentSize, mBuiltDisplayList);
+ for (auto iter = mLastItemData.Iter(); !iter.Done(); iter.Next()) {
+ auto data = iter.UserData();
+ if (auto animData = data->AsAnimationData()) {
+ // discard animation
+ AddCompositorAnimationsIdForDiscard(animData->GetCompositorAnimationsId());
+ }
+ if (auto imageData = data->AsImageData()) {
+ // discard image
+ //AddImageKeyForDiscard(imageData->mKey.value());
+ }
+ }
+
+ DiscardCompositorAnimations();
+
mLastItemData.SwapElements(mItemData);
} else {
+ for (auto iter = mLastItemData.Iter(); !iter.Done(); iter.Next()) {
+ auto data = iter.UserData();
+ if (auto canvasData = data->AsCanvasData()) {
+ canvasData->EmptyTransaction();
+ }
+ }
}
builder.PushBuiltDisplayList(mBuiltDisplayList);
WrBridge()->AddWebRenderParentCommands(mParentCommands);
WrBridge()->ClearReadLocks();
--- a/gfx/layers/wr/WebRenderLayerManager.h
+++ b/gfx/layers/wr/WebRenderLayerManager.h
@@ -58,16 +58,17 @@ public:
return HashGeneric(aKey->first(), aKey->second());
}
enum { ALLOW_MEMMOVE = true };
FrameDisplayItemKey mKey;
};
+class WMAnimationData;
class WMImageData;
class WMCanvasData;
class WMData
{
public:
NS_INLINE_DECL_REFCOUNTING(WMData)
@@ -82,16 +83,116 @@ public:
};
virtual TYPE GetType() = 0;
protected:
virtual ~WMData() {}
};
+class WMAnimationData : public WMData
+{
+public:
+ virtual WMAnimationData* AsAnimationData() override { return this; }
+ virtual TYPE GetType() override { return TYPE::ANIMATION; }
+ static TYPE Type() { return TYPE::ANIMATION; }
+
+ typedef InfallibleTArray<Animation> AnimationArray;
+
+ WMAnimationData()
+ : mAnimationGeneration(0)
+ , mCompositorAnimationsId(AnimationHelper::GetNextCompositorAnimationsId())
+ {
+ }
+
+ Animation* AddAnimation()
+ {
+ // Here generates a new id when the first animation is added and
+ // this id is used to represent the animations in this layer.
+ //EnsureAnimationsId();
+ if (!mCompositorAnimationsId) {
+ mCompositorAnimationsId = AnimationHelper::GetNextCompositorAnimationsId();
+ }
+
+ MOZ_ASSERT(!mPendingAnimations, "should have called ClearAnimations first");
+
+ Animation* anim = mAnimations.AppendElement();
+
+ //Mutated();
+ return anim;
+ }
+
+ Animation* AddAnimationForNextTransaction()
+ {
+ MOZ_ASSERT(mPendingAnimations,
+ "should have called ClearAnimationsForNextTransaction first");
+
+ Animation* anim = mPendingAnimations->AppendElement();
+
+ return anim;
+ }
+
+ bool HasTransformAnimation() const
+ {
+ for (uint32_t i = 0; i < mAnimations.Length(); i++) {
+ if (mAnimations[i].property() == eCSSProperty_transform) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ AnimationArray& GetAnimations() { return mAnimations; }
+
+ uint64_t GetCompositorAnimationsId() const { return mCompositorAnimationsId; }
+
+ void UpdateTransformDataForAnimation()
+ {
+ for (Animation& animation : mAnimations) {
+ if (animation.property() == eCSSProperty_transform) {
+ // TODO
+ //TransformData& transformData = animation.data().get_TransformData();
+ //transformData.inheritedXScale() = GetInheritedXScale();
+ //transformData.inheritedYScale() = GetInheritedYScale();
+ //transformData.hasPerspectiveParent() =
+ //GetParent() && GetParent()->GetTransformIsPerspective();
+ }
+ }
+ }
+
+ void ResolveStartTime(TimeStamp aAnimationReadyTime)
+ {
+ for (Animation& anim : mAnimations) {
+ // If the animation is play-pending, resolve the start time.
+ // This mirrors the calculation in Animation::StartTimeFromReadyTime.
+ if (anim.startTime().type() == MaybeTimeDuration::Tnull_t &&
+ !anim.originTime().IsNull() &&
+ !anim.isNotPlaying()) {
+ TimeDuration readyTime = aAnimationReadyTime - anim.originTime();
+ anim.startTime() =
+ anim.playbackRate() == 0
+ ? readyTime
+ : readyTime - anim.holdTime().MultDouble(1.0 /
+ anim.playbackRate());
+ }
+ }
+ }
+
+ void SetAnimationGeneration(uint64_t aAnimationGeneration)
+ {
+ mAnimationGeneration = aAnimationGeneration;
+ }
+
+protected:
+ uint64_t mAnimationGeneration;
+ uint64_t mCompositorAnimationsId;
+ AnimationArray mAnimations;
+ nsAutoPtr<AnimationArray> mPendingAnimations;
+};
+
class WMImageData : public WMData
{
public:
virtual WMImageData* AsImageData() override { return this; }
virtual TYPE GetType() override { return TYPE::IMAGE; }
static TYPE Type() { return TYPE::IMAGE; }
wr::MaybeExternalImageId mExternalImageId;
@@ -251,16 +352,47 @@ public:
const std::string& aKey,
const std::string& aValue) {
mApzTestData.LogTestDataForPaint(mPaintSequenceNumber, aScrollId, aKey, aValue);
}
// See equivalent function in ClientLayerManager
const APZTestData& GetAPZTestData() const
{ return mApzTestData; }
+ void SendAnimationData(WMAnimationData* aAnimationData,
+ OptionalOpacity aOpacity,
+ OptionalTransform aTransform);
+ template<class T> already_AddRefed<T>
+ CreateOrRecycleWMData(nsDisplayItem* aItem)
+ {
+ MOZ_ASSERT(aItem);
+ FrameDisplayItemKey key = MakePair(aItem->Frame(), aItem->GetPerFrameKey());
+
+ RefPtr<WMData> data;
+
+ if (mItemData.Contains(key)) {
+ data = mItemData.Get(key);
+ } else if (mLastItemData.Contains(key)) {
+ data = mLastItemData.Get(key);
+ mItemData.Put(key, data);
+ mLastItemData.Remove(key);
+ }
+
+ if (!data || (data->GetType() != T::Type())) {
+ data = new T();
+ mItemData.Put(key, data);
+ }
+
+ MOZ_ASSERT(data);
+ MOZ_ASSERT(data->GetType() == T::Type());
+
+ RefPtr<T> res = static_cast<T*>(data.get());
+ return res.forget();
+ }
+
private:
/**
* Take a snapshot of the parent context, and copy
* it into mTarget.
*/
void MakeSnapshotIfRequired(LayoutDeviceIntSize aSize);
void ClearLayer(Layer* aLayer);
--- a/layout/painting/nsDisplayList.cpp
+++ b/layout/painting/nsDisplayList.cpp
@@ -849,19 +849,314 @@ nsDisplayListBuilder::AddAnimationsAndTr
}
data = TransformData(origin, offsetToTransformOrigin,
bounds, devPixelsToAppUnits,
scaleX, scaleY, hasPerspectiveParent);
} else if (aProperty == eCSSProperty_opacity) {
data = null_t();
}
+ AddAnimationsForProperty(aFrame, aProperty, compositorAnimations,
+ aLayer, data, pending);
+}
+
+static void
+AddAnimationForProperty(nsIFrame* aFrame, const AnimationProperty& aProperty,
+ dom::Animation* aAnimation, WMAnimationData* aAnimationData,
+ AnimationData& aData, bool aPending)
+{
+ //MOZ_ASSERT(aLayer->AsContainerLayer(), "Should only animate ContainerLayer");
+ MOZ_ASSERT(aAnimation->GetEffect(),
+ "Should not be adding an animation without an effect");
+ MOZ_ASSERT(!aAnimation->GetCurrentOrPendingStartTime().IsNull() ||
+ !aAnimation->IsPlaying() ||
+ (aAnimation->GetTimeline() &&
+ aAnimation->GetTimeline()->TracksWallclockTime()),
+ "If the animation has an unresolved start time it should either"
+ " be static (so we don't need a start time) or else have a"
+ " timeline capable of converting TimeStamps (so we can calculate"
+ " one later");
+
+ layers::Animation* animation =
+ aPending ?
+ aAnimationData->AddAnimationForNextTransaction() :
+ aAnimationData->AddAnimation();
+ const TimingParams& timing = aAnimation->GetEffect()->SpecifiedTiming();
+
+ // If we are starting a new transition that replaces an existing transition
+ // running on the compositor, it is possible that the animation on the
+ // compositor will have advanced ahead of the main thread. If we use as
+ // the starting point of the new transition, the current value of the
+ // replaced transition as calculated on the main thread using the refresh
+ // driver time, the new transition will jump when it starts. Instead, we
+ // re-calculate the starting point of the new transition by applying the
+ // current TimeStamp to the parameters of the replaced transition.
+ //
+ // We need to do this here, rather than when we generate the new transition,
+ // since after generating the new transition other requestAnimationFrame
+ // callbacks may run that introduce further lag between the main thread and
+ // the compositor.
+ if (aAnimation->AsCSSTransition() &&
+ aAnimation->GetEffect() &&
+ aAnimation->GetEffect()->AsTransition()) {
+ // We update startValue from the replaced transition only if the effect is
+ // an ElementPropertyTransition.
+ aAnimation->GetEffect()->AsTransition()->
+ UpdateStartValueFromReplacedTransition();
+ }
+
+ animation->originTime() = !aAnimation->GetTimeline()
+ ? TimeStamp()
+ : aAnimation->GetTimeline()->
+ ToTimeStamp(TimeDuration());
+
+ Nullable<TimeDuration> startTime = aAnimation->GetCurrentOrPendingStartTime();
+ if (startTime.IsNull()) {
+ animation->startTime() = null_t();
+ } else {
+ animation->startTime() = startTime.Value();
+ }
+
+ animation->holdTime() = aAnimation->GetCurrentTime().Value();
+
+ const ComputedTiming computedTiming =
+ aAnimation->GetEffect()->GetComputedTiming();
+ animation->delay() = timing.Delay();
+ animation->endDelay() = timing.EndDelay();
+ animation->duration() = computedTiming.mDuration;
+ animation->iterations() = computedTiming.mIterations;
+ animation->iterationStart() = computedTiming.mIterationStart;
+ animation->direction() = static_cast<uint8_t>(timing.Direction());
+ animation->fillMode() = static_cast<uint8_t>(computedTiming.mFill);
+ animation->property() = aProperty.mProperty;
+ animation->playbackRate() = aAnimation->PlaybackRate();
+ animation->data() = aData;
+ animation->easingFunction() = ToTimingFunction(timing.TimingFunction());
+ animation->iterationComposite() =
+ static_cast<uint8_t>(aAnimation->GetEffect()->
+ AsKeyframeEffect()->IterationComposite());
+ animation->isNotPlaying() = !aAnimation->IsPlaying();
+
+ TransformReferenceBox refBox(aFrame);
+
+ // If the animation is additive or accumulates, we need to pass its base value
+ // to the compositor.
+
+ AnimationValue baseStyle =
+ aAnimation->GetEffect()->AsKeyframeEffect()->BaseStyle(aProperty.mProperty);
+ if (!baseStyle.IsNull()) {
+ SetAnimatable(aProperty.mProperty,
+ baseStyle,
+ aFrame, refBox,
+ animation->baseStyle());
+ } else {
+ animation->baseStyle() = null_t();
+ }
+
+ for (uint32_t segIdx = 0; segIdx < aProperty.mSegments.Length(); segIdx++) {
+ const AnimationPropertySegment& segment = aProperty.mSegments[segIdx];
+
+ AnimationSegment* animSegment = animation->segments().AppendElement();
+ SetAnimatable(aProperty.mProperty,
+ segment.mFromValue,
+ aFrame, refBox,
+ animSegment->startState());
+ SetAnimatable(aProperty.mProperty,
+ segment.mToValue,
+ aFrame, refBox,
+ animSegment->endState());
+
+ animSegment->startPortion() = segment.mFromKey;
+ animSegment->endPortion() = segment.mToKey;
+ animSegment->startComposite() =
+ static_cast<uint8_t>(segment.mFromComposite);
+ animSegment->endComposite() =
+ static_cast<uint8_t>(segment.mToComposite);
+ animSegment->sampleFn() = ToTimingFunction(segment.mTimingFunction);
+ }
+}
+
+static void
+AddAnimationsForProperty(nsIFrame* aFrame, nsCSSPropertyID aProperty,
+ nsTArray<RefPtr<dom::Animation>>& aAnimations,
+ WMAnimationData* aAnimationData,
+ AnimationData& aData,
+ bool aPending)
+{
+ MOZ_ASSERT(nsCSSProps::PropHasFlags(aProperty,
+ CSS_PROPERTY_CAN_ANIMATE_ON_COMPOSITOR),
+ "inconsistent property flags");
+
+ EffectSet* effects = EffectSet::GetEffectSet(aFrame);
+ MOZ_ASSERT(effects);
+
+ bool sentAnimations = false;
+ // Add from first to last (since last overrides)
+ for (size_t animIdx = 0; animIdx < aAnimations.Length(); animIdx++) {
+ dom::Animation* anim = aAnimations[animIdx];
+ if (!anim->IsRelevant()) {
+ continue;
+ }
+
+ dom::KeyframeEffectReadOnly* keyframeEffect =
+ anim->GetEffect() ? anim->GetEffect()->AsKeyframeEffect() : nullptr;
+ MOZ_ASSERT(keyframeEffect,
+ "A playing animation should have a keyframe effect");
+ const AnimationProperty* property =
+ keyframeEffect->GetEffectiveAnimationOfProperty(aProperty);
+ if (!property) {
+ continue;
+ }
+
+ // Note that if the property is overridden by !important rules,
+ // GetEffectiveAnimationOfProperty returns null instead.
+ // This is what we want, since if we have animations overridden by
+ // !important rules, we don't want to send them to the compositor.
+ MOZ_ASSERT(anim->CascadeLevel() !=
+ EffectCompositor::CascadeLevel::Animations ||
+ !effects->PropertiesWithImportantRules()
+ .HasProperty(aProperty),
+ "GetEffectiveAnimationOfProperty already tested the property "
+ "is not overridden by !important rules");
+
+ // Don't add animations that are pending if their timeline does not
+ // track wallclock time. This is because any pending animations on layers
+ // will have their start time updated with the current wallclock time.
+ // If we can't convert that wallclock time back to an equivalent timeline
+ // time, we won't be able to update the content animation and it will end
+ // up being out of sync with the layer animation.
+ //
+ // Currently this only happens when the timeline is driven by a refresh
+ // driver under test control. In this case, the next time the refresh
+ // driver is advanced it will trigger any pending animations.
+ if (anim->PlayState() == AnimationPlayState::Pending &&
+ (anim->GetTimeline() &&
+ !anim->GetTimeline()->TracksWallclockTime())) {
+ continue;
+ }
+
+ AddAnimationForProperty(aFrame, *property, anim, aAnimationData, aData, aPending);
+ keyframeEffect->SetIsRunningOnCompositor(aProperty, true);
+ sentAnimations = true;
+ }
+
+ if (sentAnimations && aProperty == eCSSProperty_transform) {
+ TimeStamp now = aFrame->PresContext()->RefreshDriver()->MostRecentRefresh();
+ effects->UpdateLastTransformSyncTime(now);
+ }
+}
+
+/* static */ already_AddRefed<WMAnimationData>
+nsDisplayListBuilder::AddAnimationsAndTransitionsToWRLayerManager(WebRenderLayerManager* aManager,
+ nsDisplayListBuilder* aBuilder,
+ nsDisplayItem* aItem,
+ nsIFrame* aFrame,
+ nsCSSPropertyID aProperty)
+{
+ MOZ_ASSERT(nsCSSProps::PropHasFlags(aProperty,
+ CSS_PROPERTY_CAN_ANIMATE_ON_COMPOSITOR),
+ "inconsistent property flags");
+
+ // This function can be called in two ways: from
+ // nsDisplay*::BuildLayer while constructing a layer (with all
+ // pointers non-null), or from RestyleManager's handling of
+ // UpdateOpacityLayer/UpdateTransformLayer hints.
+ MOZ_ASSERT(!aBuilder == !aItem,
+ "should only be called in two configurations, with both "
+ "aBuilder and aItem, or with neither");
+ MOZ_ASSERT(!aItem || aFrame == aItem->Frame(), "frame mismatch");
+
+ // Only send animations to a layer that is actually using
+ // off-main-thread compositing.
+ //MOZ_ASSERT(backend == layers::LayersBackend::LAYERS_WR);
+
+ bool pending = !aBuilder;
+#if 0
+ if (pending) {
+ aLayer->ClearAnimationsForNextTransaction();
+ } else {
+ aLayer->ClearAnimations();
+ }
+#endif
+ // Update the animation generation on the layer. We need to do this before
+ // any early returns since even if we don't add any animations to the
+ // layer, we still need to mark it as up-to-date with regards to animations.
+ // Otherwise, in RestyleManager we'll notice the discrepancy between the
+ // animation generation numbers and update the layer indefinitely.
+ uint64_t animationGeneration =
+ RestyleManager::GetAnimationGenerationForFrame(aFrame);
+ //aLayer->SetAnimationGeneration(animationGeneration);
+ RefPtr<WMAnimationData> animationData = aManager->CreateOrRecycleWMData<WMAnimationData>(aItem);
+ animationData->SetAnimationGeneration(animationGeneration);
+
+ EffectCompositor::ClearIsRunningOnCompositor(aFrame, aProperty);
+ nsTArray<RefPtr<dom::Animation>> compositorAnimations =
+ EffectCompositor::GetAnimationsForCompositor(aFrame, aProperty);
+ if (compositorAnimations.IsEmpty()) {
+ return nullptr;
+ }
+
+ // If the frame is not prerendered, bail out.
+ // Do this check only during layer construction; during updating the
+ // caller is required to check it appropriately.
+ if (aItem && !aItem->CanUseAsyncAnimations(aBuilder)) {
+ // EffectCompositor needs to know that we refused to run this animation
+ // asynchronously so that it will not throttle the main thread
+ // animation.
+ aFrame->SetProperty(nsIFrame::RefusedAsyncAnimationProperty(), true);
+
+ // We need to schedule another refresh driver run so that EffectCompositor
+ // gets a chance to unthrottle the animation.
+ aFrame->SchedulePaint();
+ return nullptr;
+ }
+
+ AnimationData data;
+ if (aProperty == eCSSProperty_transform) {
+ // XXX Performance here isn't ideal for SVG. We'd prefer to avoid resolving
+ // the dimensions of refBox. That said, we only get here if there are CSS
+ // animations or transitions on this element, and that is likely to be a
+ // lot rarer than transforms on SVG (the frequency of which drives the need
+ // for TransformReferenceBox).
+ TransformReferenceBox refBox(aFrame);
+ nsRect bounds(0, 0, refBox.Width(), refBox.Height());
+ // all data passed directly to the compositor should be in dev pixels
+ int32_t devPixelsToAppUnits = aFrame->PresContext()->AppUnitsPerDevPixel();
+ float scale = devPixelsToAppUnits;
+ Point3D offsetToTransformOrigin =
+ nsDisplayTransform::GetDeltaToTransformOrigin(aFrame, scale, &bounds);
+ nsPoint origin;
+ float scaleX = 1.0f;
+ float scaleY = 1.0f;
+ bool hasPerspectiveParent = false;
+ if (aItem) {
+ // This branch is for display items to leverage the cache of
+ // nsDisplayListBuilder.
+ origin = aItem->ToReferenceFrame();
+ } else {
+ // This branch is running for restyling.
+ // Animations are animated at the coordination of the reference
+ // frame outside, not the given frame itself. The given frame
+ // is also reference frame too, so the parent's reference frame
+ // are used.
+ nsIFrame* referenceFrame =
+ nsLayoutUtils::GetReferenceFrame(nsLayoutUtils::GetCrossDocParentFrame(aFrame));
+ origin = aFrame->GetOffsetToCrossDoc(referenceFrame);
+ }
+
+ data = TransformData(origin, offsetToTransformOrigin,
+ bounds, devPixelsToAppUnits,
+ scaleX, scaleY, hasPerspectiveParent);
+ } else if (aProperty == eCSSProperty_opacity) {
+ data = null_t();
+ }
AddAnimationsForProperty(aFrame, aProperty, compositorAnimations,
- aLayer, data, pending);
+ animationData, data, pending);
+ return animationData.forget();
}
void
nsDisplayListBuilder::AutoCurrentActiveScrolledRootSetter::InsertScrollFrame(nsIScrollableFrame* aScrollableFrame)
{
MOZ_ASSERT(!mUsed);
size_t descendantsEndIndex = mBuilder->mActiveScrolledRoots.Length();
const ActiveScrolledRoot* parentASR = mBuilder->mCurrentActiveScrolledRoot;
--- a/layout/painting/nsDisplayList.h
+++ b/layout/painting/nsDisplayList.h
@@ -59,16 +59,17 @@ class FrameLayerBuilder;
namespace layers {
class Layer;
class ImageLayer;
class ImageContainer;
class StackingContextHelper;
class WebRenderCommand;
class WebRenderParentCommand;
class WebRenderDisplayItemLayer;
+class WMAnimationData;
} // namespace layers
namespace wr {
class DisplayListBuilder;
} // namespace wr
} // namespace mozilla
// A set of blend modes, that never includes OP_OVER (since it's
// considered the default, rather than a specific blend mode).
@@ -790,16 +791,23 @@ public:
* the layer as pending animations.
*/
static void AddAnimationsAndTransitionsToLayer(Layer* aLayer,
nsDisplayListBuilder* aBuilder,
nsDisplayItem* aItem,
nsIFrame* aFrame,
nsCSSPropertyID aProperty);
+ static already_AddRefed<mozilla::layers::WMAnimationData>
+ AddAnimationsAndTransitionsToWRLayerManager(mozilla::layers::WebRenderLayerManager* aManager,
+ nsDisplayListBuilder* aBuilder,
+ nsDisplayItem* aItem,
+ nsIFrame* aFrame,
+ nsCSSPropertyID aProperty);
+
/**
* A helper class to temporarily set the value of
* mIsAtRootOfPseudoStackingContext, and temporarily
* set mCurrentFrame and related state. Also temporarily sets mDirtyRect.
* aDirtyRect is relative to aForChild.
*/
class AutoBuildingDisplayList;
friend class AutoBuildingDisplayList;