gecko-dev/layout/base/RestyleManager.h
Emilio Cobos Álvarez cb56e2c9f3 Bug 1459529: Remove UPDATE_STYLE. r=heycam
Now that BeginUpdate is useless for the UPDATE_STYLE case, we don't need the
update mechanism at all. Just ensure that ApplicableStylesChanged is called on
the pres shell via the relevant RuleChanged, etc. notifications.

There's a big hidden gotcha here. nsIDocument::BeginUpdate does put a script
blocker on the stack for these updates. However it's not needed, since no script
can run during these notifications (only the stylesheet events we post for
devtools, but those use AsyncEventDispatcher and PostDOMEvents, so they don't
try to run immediately).

nsIDocument::BeginUpdate also does XBL binding attached queue stuff, but we
can't change bindings during these notifications anyway, so it also doesn't
matter.

MozReview-Commit-ID: HJvK6zQfloh
2018-05-15 18:44:29 +02:00

569 lines
22 KiB
C++

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_RestyleManager_h
#define mozilla_RestyleManager_h
#include "mozilla/AutoRestore.h"
#include "mozilla/EventStates.h"
#include "mozilla/Maybe.h"
#include "mozilla/OverflowChangedTracker.h"
#include "mozilla/ServoElementSnapshot.h"
#include "mozilla/ServoElementSnapshotTable.h"
#include "nsChangeHint.h"
#include "nsPresContext.h"
#include "nsStringFwd.h"
class nsAttrValue;
class nsCSSFrameConstructor;
class nsAtom;
class nsIContent;
class nsIFrame;
class nsStyleChangeList;
class nsStyleChangeList;
namespace mozilla {
class EventStates;
class ServoStyleSet;
namespace dom {
class Element;
}
/**
* A stack class used to pass some common restyle state in a slightly more
* comfortable way than a bunch of individual arguments, and that also checks
* that the change hint used for optimization is correctly used in debug mode.
*/
class ServoRestyleState
{
public:
ServoRestyleState(ServoStyleSet& aStyleSet,
nsStyleChangeList& aChangeList,
nsTArray<nsIFrame*>& aPendingWrapperRestyles)
: mStyleSet(aStyleSet)
, mChangeList(aChangeList)
, mPendingWrapperRestyles(aPendingWrapperRestyles)
, mPendingWrapperRestyleOffset(aPendingWrapperRestyles.Length())
, mChangesHandled(nsChangeHint(0))
#ifdef DEBUG
// If !mOwner, then we wouldn't have processed our wrapper restyles, because
// we only process those when handling an element with a frame. But that's
// OK, because if we started our traversal at an element with no frame
// (e.g. it's display:contents), that means the wrapper frames in our list
// actually inherit from one of its ancestors, not from it, and hence not
// restyling them is OK.
, mAssertWrapperRestyleLength(false)
#endif // DEBUG
{}
// We shouldn't assume that changes handled from our parent are handled for
// our children too if we're out of flow since they aren't necessarily
// parented in DOM order, and thus a change handled by a DOM ancestor doesn't
// necessarily mean that it's handled for an ancestor frame.
enum class Type
{
InFlow,
OutOfFlow,
};
ServoRestyleState(const nsIFrame& aOwner,
ServoRestyleState& aParentState,
nsChangeHint aHintForThisFrame,
Type aType,
bool aAssertWrapperRestyleLength = true)
: mStyleSet(aParentState.mStyleSet)
, mChangeList(aParentState.mChangeList)
, mPendingWrapperRestyles(aParentState.mPendingWrapperRestyles)
, mPendingWrapperRestyleOffset(aParentState.mPendingWrapperRestyles.Length())
, mChangesHandled(
aType == Type::InFlow
? aParentState.mChangesHandled | aHintForThisFrame
: aHintForThisFrame)
#ifdef DEBUG
, mOwner(&aOwner)
, mAssertWrapperRestyleLength(aAssertWrapperRestyleLength)
#endif
{
if (aType == Type::InFlow) {
AssertOwner(aParentState);
}
}
~ServoRestyleState() {
MOZ_ASSERT(!mAssertWrapperRestyleLength ||
mPendingWrapperRestyles.Length() == mPendingWrapperRestyleOffset,
"Someone forgot to call ProcessWrapperRestyles!");
}
nsStyleChangeList& ChangeList() { return mChangeList; }
ServoStyleSet& StyleSet() { return mStyleSet; }
#ifdef DEBUG
void AssertOwner(const ServoRestyleState& aParentState) const;
nsChangeHint ChangesHandledFor(const nsIFrame&) const;
#else
void AssertOwner(const ServoRestyleState&) const {}
nsChangeHint ChangesHandledFor(const nsIFrame&) const
{
return mChangesHandled;
}
#endif
// Add a pending wrapper restyle. We don't have to do anything if the thing
// being added is already last in the list, but otherwise we do want to add
// it, in order for ProcessWrapperRestyles to work correctly.
void AddPendingWrapperRestyle(nsIFrame* aWrapperFrame);
// Process wrapper restyles for this restyle state. This should be done
// before it comes off the stack.
void ProcessWrapperRestyles(nsIFrame* aParentFrame);
// Get the table-aware parent for the given child. This will walk through
// outer table and cellcontent frames.
static nsIFrame* TableAwareParentFor(const nsIFrame* aChild);
private:
// Process a wrapper restyle at the given index, and restyles for any
// wrappers nested in it. Returns the number of entries from
// mPendingWrapperRestyles that we processed. The return value is always at
// least 1.
size_t ProcessMaybeNestedWrapperRestyle(nsIFrame* aParent, size_t aIndex);
ServoStyleSet& mStyleSet;
nsStyleChangeList& mChangeList;
// A list of pending wrapper restyles. Anonymous box wrapper frames that need
// restyling are added to this list when their non-anonymous kids are
// restyled. This avoids us having to do linear searches along the frame tree
// for these anonymous boxes. The problem then becomes that we can have
// multiple kids all with the same anonymous parent, and we don't want to
// restyle it more than once. We use mPendingWrapperRestyles to track which
// anonymous wrapper boxes we've requested be restyled and which of them have
// already been restyled. We use a single array propagated through
// ServoRestyleStates by reference, because in a situation like this:
//
// <div style="display: table"><span></span></div>
//
// We have multiple wrappers to restyle (cell, row, table-row-group) and we
// want to add them in to the list all at once but restyle them using
// different ServoRestyleStates with different owners. When this situation
// occurs, the relevant frames will be placed in the array with ancestors
// before descendants.
nsTArray<nsIFrame*>& mPendingWrapperRestyles;
// Since we're given a possibly-nonempty mPendingWrapperRestyles to start
// with, we need to keep track of where the part of it we're responsible for
// starts.
size_t mPendingWrapperRestyleOffset;
const nsChangeHint mChangesHandled;
// We track the "owner" frame of this restyle state, that is, the frame that
// generated the last change that is stored in mChangesHandled, in order to
// verify that we only use mChangesHandled for actual descendants of that
// frame (given DOM order isn't always frame order, and that there are a few
// special cases for stuff like wrapper frames, ::backdrop, and so on).
#ifdef DEBUG
const nsIFrame* mOwner { nullptr };
#endif
// Whether we should assert in our destructor that we've processed all of the
// relevant wrapper restyles.
#ifdef DEBUG
const bool mAssertWrapperRestyleLength;
#endif // DEBUG
};
enum class ServoPostTraversalFlags : uint32_t;
class RestyleManager
{
friend class ServoStyleSet;
public:
typedef ServoElementSnapshotTable SnapshotTable;
typedef mozilla::dom::Element Element;
// Get an integer that increments every time we process pending restyles.
// The value is never 0.
uint64_t GetRestyleGeneration() const { return mRestyleGeneration; }
// Unlike GetRestyleGeneration, which means the actual restyling count,
// GetUndisplayedRestyleGeneration represents any possible DOM changes that
// can cause restyling. This is needed for getComputedStyle to work with
// non-styled (e.g. display: none) elements.
uint64_t GetUndisplayedRestyleGeneration() const {
return mUndisplayedRestyleGeneration;
}
void Disconnect() { mPresContext = nullptr; }
~RestyleManager()
{
MOZ_ASSERT(!mAnimationsWithDestroyedFrame,
"leaving dangling pointers from AnimationsWithDestroyedFrame");
MOZ_ASSERT(!mReentrantChanges);
}
static nsCString RestyleHintToString(nsRestyleHint aHint);
#ifdef DEBUG
static nsCString ChangeHintToString(nsChangeHint aHint);
/**
* DEBUG ONLY method to verify integrity of style tree versus frame tree
*/
void DebugVerifyStyleTree(nsIFrame* aFrame);
#endif
void FlushOverflowChangedTracker() {
mOverflowChangedTracker.Flush();
}
// Should be called when a frame is going to be destroyed and
// WillDestroyFrameTree hasn't been called yet.
void NotifyDestroyingFrame(nsIFrame* aFrame) {
mOverflowChangedTracker.RemoveFrame(aFrame);
// If ProcessRestyledFrames is tracking frames which have been
// destroyed (to avoid re-visiting them), add this one to its set.
if (mDestroyedFrames) {
mDestroyedFrames->PutEntry(aFrame);
}
}
// Note: It's the caller's responsibility to make sure to wrap a
// ProcessRestyledFrames call in a view update batch and a script blocker.
// This function does not call ProcessAttachedQueue() on the binding manager.
// If the caller wants that to happen synchronously, it needs to handle that
// itself.
void ProcessRestyledFrames(nsStyleChangeList& aChangeList);
bool IsInStyleRefresh() const { return mInStyleRefresh; }
// AnimationsWithDestroyedFrame is used to stop animations and transitions
// on elements that have no frame at the end of the restyling process.
// It only lives during the restyling process.
class MOZ_STACK_CLASS AnimationsWithDestroyedFrame final {
public:
// Construct a AnimationsWithDestroyedFrame object. The caller must
// ensure that aRestyleManager lives at least as long as the
// object. (This is generally easy since the caller is typically a
// method of RestyleManager.)
explicit AnimationsWithDestroyedFrame(RestyleManager* aRestyleManager);
// This method takes the content node for the generated content for
// animation/transition on ::before and ::after, rather than the
// content node for the real element.
void Put(nsIContent* aContent, ComputedStyle* aComputedStyle) {
MOZ_ASSERT(aContent);
CSSPseudoElementType pseudoType = aComputedStyle->GetPseudoType();
if (pseudoType == CSSPseudoElementType::NotPseudo) {
mContents.AppendElement(aContent);
} else if (pseudoType == CSSPseudoElementType::before) {
MOZ_ASSERT(aContent->NodeInfo()->NameAtom() ==
nsGkAtoms::mozgeneratedcontentbefore);
mBeforeContents.AppendElement(aContent->GetParent());
} else if (pseudoType == CSSPseudoElementType::after) {
MOZ_ASSERT(aContent->NodeInfo()->NameAtom() ==
nsGkAtoms::mozgeneratedcontentafter);
mAfterContents.AppendElement(aContent->GetParent());
}
}
void StopAnimationsForElementsWithoutFrames();
private:
void StopAnimationsWithoutFrame(nsTArray<RefPtr<nsIContent>>& aArray,
CSSPseudoElementType aPseudoType);
RestyleManager* mRestyleManager;
AutoRestore<AnimationsWithDestroyedFrame*> mRestorePointer;
// Below three arrays might include elements that have already had their
// animations or transitions stopped.
//
// mBeforeContents and mAfterContents hold the real element rather than
// the content node for the generated content (which might change during
// a reframe)
nsTArray<RefPtr<nsIContent>> mContents;
nsTArray<RefPtr<nsIContent>> mBeforeContents;
nsTArray<RefPtr<nsIContent>> mAfterContents;
};
/**
* Return the current AnimationsWithDestroyedFrame struct, or null if we're
* not currently in a restyling operation.
*/
AnimationsWithDestroyedFrame* GetAnimationsWithDestroyedFrame() {
return mAnimationsWithDestroyedFrame;
}
void ContentInserted(nsIContent* aChild);
void ContentAppended(nsIContent* aFirstNewContent);
// This would be have the same logic as RestyleForInsertOrChange if we got the
// notification before the removal. However, we get it after, so we need the
// following sibling in addition to the old child.
//
// aFollowingSibling is the sibling that used to come after aOldChild before
// the removal.
void ContentRemoved(nsIContent* aOldChild, nsIContent* aFollowingSibling);
// Restyling for a ContentInserted (notification after insertion) or
// for some CharacterDataChanged.
void RestyleForInsertOrChange(nsIContent* aChild);
// Restyle for a CharacterDataChanged notification. In practice this can only
// affect :empty / :-moz-only-whitespace / :-moz-first-node / :-moz-last-node.
void CharacterDataChanged(nsIContent*, const CharacterDataChangeInfo&);
void PostRestyleEvent(dom::Element*, nsRestyleHint, nsChangeHint aMinChangeHint);
/**
* Posts restyle hints for animations.
* This is only called for the second traversal for CSS animations during
* updating CSS animations in a SequentialTask.
* This function does neither register a refresh observer nor flag that a
* style flush is needed since this function is supposed to be called during
* restyling process and this restyle event will be processed in the second
* traversal of the same restyling process.
*/
void PostRestyleEventForAnimations(dom::Element*,
CSSPseudoElementType,
nsRestyleHint);
void NextRestyleIsForCSSRuleChanges()
{
mRestyleForCSSRuleChanges = true;
}
void RebuildAllStyleData(nsChangeHint aExtraHint, nsRestyleHint aRestyleHint);
void PostRebuildAllStyleDataEvent(nsChangeHint aExtraHint,
nsRestyleHint aRestyleHint);
void ProcessPendingRestyles();
void ProcessAllPendingAttributeAndStateInvalidations();
void ContentStateChanged(nsIContent* aContent, EventStates aStateMask);
void AttributeWillChange(Element* aElement,
int32_t aNameSpaceID,
nsAtom* aAttribute,
int32_t aModType,
const nsAttrValue* aNewValue);
void ClassAttributeWillBeChangedBySMIL(dom::Element* aElement);
void AttributeChanged(dom::Element* aElement,
int32_t aNameSpaceID,
nsAtom* aAttribute,
int32_t aModType,
const nsAttrValue* aOldValue);
// This is only used to reparent things when moving them in/out of the
// ::first-line.
void ReparentComputedStyleForFirstLine(nsIFrame*);
bool HasPendingRestyleAncestor(dom::Element* aElement) const;
/**
* Performs a Servo animation-only traversal to compute style for all nodes
* with the animation-only dirty bit in the document.
*
* This processes just the traversal for animation-only restyles and skips the
* normal traversal for other restyles unrelated to animations.
* This is used to bring throttled animations up-to-date such as when we need
* to get correct position for transform animations that are throttled because
* they are running on the compositor.
*
* This will traverse all of the document's style roots (that is, its document
* element, and the roots of the document-level native anonymous content).
*/
void UpdateOnlyAnimationStyles();
// Get a counter that increments on every style change, that we use to
// track whether off-main-thread animations are up-to-date.
uint64_t GetAnimationGeneration() const { return mAnimationGeneration; }
static uint64_t GetAnimationGenerationForFrame(nsIFrame* aFrame);
// Update the animation generation count to mark that animation state
// has changed.
//
// This is normally performed automatically by ProcessPendingRestyles
// but it is also called when we have out-of-band changes to animations
// such as changes made through the Web Animations API.
void IncrementAnimationGeneration();
static void AddLayerChangesForAnimation(nsIFrame* aFrame,
nsIContent* aContent,
nsStyleChangeList&
aChangeListToProcess);
/**
* Whether to clear all the style data (including the element itself), or just
* the descendants' data.
*/
enum class IncludeRoot {
Yes,
No,
};
/**
* Clears the ServoElementData and HasDirtyDescendants from all elements
* in the subtree rooted at aElement.
*/
static void ClearServoDataFromSubtree(Element*, IncludeRoot = IncludeRoot::Yes);
/**
* Clears HasDirtyDescendants and RestyleData from all elements in the
* subtree rooted at aElement.
*/
static void ClearRestyleStateFromSubtree(Element* aElement);
explicit RestyleManager(nsPresContext* aPresContext);
protected:
/**
* Reparent the descendants of aFrame. This is used by ReparentComputedStyle
* and shouldn't be called by anyone else. aProviderChild, if non-null, is a
* child that was the style parent for aFrame and hence shouldn't be
* reparented.
*/
void ReparentFrameDescendants(nsIFrame* aFrame, nsIFrame* aProviderChild,
ServoStyleSet& aStyleSet);
/**
* Performs post-Servo-traversal processing on this element and its
* descendants.
*
* Returns whether any style did actually change. There may be cases where we
* didn't need to change any style after all, for example, when a content
* attribute changes that happens not to have any effect on the style of that
* element or any descendant or sibling.
*/
bool ProcessPostTraversal(Element* aElement,
ComputedStyle* aParentContext,
ServoRestyleState& aRestyleState,
ServoPostTraversalFlags aFlags);
struct TextPostTraversalState;
bool ProcessPostTraversalForText(nsIContent* aTextNode,
TextPostTraversalState& aState,
ServoRestyleState& aRestyleState,
ServoPostTraversalFlags aFlags);
ServoStyleSet* StyleSet() const { return PresContext()->StyleSet(); }
void RestyleForEmptyChange(Element* aContainer);
void MaybeRestyleForEdgeChildChange(Element* aContainer, nsIContent* aChangedChild);
void ContentStateChangedInternal(Element* aElement,
EventStates aStateMask,
nsChangeHint* aOutChangeHint);
bool IsDisconnected() const { return !mPresContext; }
void IncrementRestyleGeneration() {
if (++mRestyleGeneration == 0) {
// Keep mRestyleGeneration from being 0, since that's what
// nsPresContext::GetRestyleGeneration returns when it no
// longer has a RestyleManager.
++mRestyleGeneration;
}
IncrementUndisplayedRestyleGeneration();
}
void IncrementUndisplayedRestyleGeneration() {
if (++mUndisplayedRestyleGeneration == 0) {
// Ensure mUndisplayedRestyleGeneration > 0, for the same reason as
// IncrementRestyleGeneration.
++mUndisplayedRestyleGeneration;
}
}
nsPresContext* PresContext() const {
MOZ_ASSERT(mPresContext);
return mPresContext;
}
nsCSSFrameConstructor* FrameConstructor() const {
return PresContext()->FrameConstructor();
}
private:
nsPresContext* mPresContext; // weak, can be null after Disconnect().
uint64_t mRestyleGeneration;
uint64_t mUndisplayedRestyleGeneration;
// Used to keep track of frames that have been destroyed during
// ProcessRestyledFrames, so we don't try to touch them again even if
// they're referenced again later in the changelist.
mozilla::UniquePtr<nsTHashtable<nsPtrHashKey<const nsIFrame>>> mDestroyedFrames;
protected:
// True if we're in the middle of a nsRefreshDriver refresh
bool mInStyleRefresh;
// The total number of animation flushes by this frame constructor.
// Used to keep the layer and animation manager in sync.
uint64_t mAnimationGeneration;
OverflowChangedTracker mOverflowChangedTracker;
AnimationsWithDestroyedFrame* mAnimationsWithDestroyedFrame = nullptr;
const SnapshotTable& Snapshots() const { return mSnapshots; }
void ClearSnapshots();
ServoElementSnapshot& SnapshotFor(mozilla::dom::Element* aElement);
void TakeSnapshotForAttributeChange(mozilla::dom::Element* aElement,
int32_t aNameSpaceID,
nsAtom* aAttribute);
void DoProcessPendingRestyles(ServoTraversalFlags aFlags);
// Function to do the actual (recursive) work of
// ReparentComputedStyleForFirstLine, once we have asserted the invariants
// that only hold on the initial call.
void DoReparentComputedStyleForFirstLine(nsIFrame*, ServoStyleSet&);
// We use a separate data structure from nsStyleChangeList because we need a
// frame to create nsStyleChangeList entries, and the primary frame may not be
// attached yet.
struct ReentrantChange {
nsCOMPtr<nsIContent> mContent;
nsChangeHint mHint;
};
typedef AutoTArray<ReentrantChange, 10> ReentrantChangeList;
// Only non-null while processing change hints. See the comment in
// ProcessPendingRestyles.
ReentrantChangeList* mReentrantChanges = nullptr;
// We use this flag to track if the current restyle contains any non-animation
// update, which triggers a normal restyle, and so there might be any new
// transition created later. Therefore, if this flag is true, we need to
// increase mAnimationGeneration before creating new transitions, so their
// creation sequence will be correct.
bool mHaveNonAnimationRestyles = false;
// Set to true when posting restyle events triggered by CSS rule changes.
// This flag is cleared once ProcessPendingRestyles has completed.
// When we process a traversal all descendants elements of the document
// triggered by CSS rule changes, we will need to update all elements with
// CSS animations. We propagate TraversalRestyleBehavior::ForCSSRuleChanges
// to traversal function if this flag is set.
bool mRestyleForCSSRuleChanges = false;
// A hashtable with the elements that have changed state or attributes, in
// order to calculate restyle hints during the traversal.
SnapshotTable mSnapshots;
};
} // namespace mozilla
#endif