--- a/dom/events/EventStates.h
+++ b/dom/events/EventStates.h
@@ -7,27 +7,30 @@
#ifndef mozilla_EventStates_h_
#define mozilla_EventStates_h_
#include "mozilla/Attributes.h"
#include "nsDebug.h"
namespace mozilla {
+#define NS_EVENT_STATE_HIGHEST_SERVO_BIT 6
+
/**
* EventStates is the class used to represent the event states of nsIContent
* instances. These states are calculated by IntrinsicState() and
* ContentStatesChanged() has to be called when one of them changes thus
* informing the layout/style engine of the change.
* Event states are associated with pseudo-classes.
*/
class EventStates
{
public:
typedef uint64_t InternalType;
+ typedef uint8_t ServoType;
constexpr EventStates()
: mStates(0)
{
}
// NOTE: the ideal scenario would be to have the default constructor public
// setting mStates to 0 and this constructor (without = 0) private.
@@ -150,16 +153,23 @@ public:
}
// We only need that method for inDOMUtils::GetContentState.
// If inDOMUtils::GetContentState is removed, this method should be removed.
InternalType GetInternalValue() const {
return mStates;
}
+ /**
+ * Method used to get the appropriate state representation for Servo.
+ */
+ ServoType ServoValue() const {
+ return mStates & ((1 << (NS_EVENT_STATE_HIGHEST_SERVO_BIT + 1)) - 1);
+ }
+
private:
InternalType mStates;
};
} // namespace mozilla
/**
* The following macros are creating EventStates instance with different
@@ -195,20 +205,21 @@ private:
#define NS_EVENT_STATE_ENABLED NS_DEFINE_EVENT_STATE_MACRO(3)
// Content is disabled.
#define NS_EVENT_STATE_DISABLED NS_DEFINE_EVENT_STATE_MACRO(4)
// Content is checked.
#define NS_EVENT_STATE_CHECKED NS_DEFINE_EVENT_STATE_MACRO(5)
// Content is in the indeterminate state.
#define NS_EVENT_STATE_INDETERMINATE NS_DEFINE_EVENT_STATE_MACRO(6)
-#define NS_EVENT_STATE_HIGHEST_SERVO_BIT 6
-
/*
* Bits below here do not have Servo-related ordering constraints.
+ *
+ * Remember to change NS_EVENT_STATE_HIGHEST_SERVO_BIT at the top of the file if
+ * this changes!
*/
// Drag is hovering over content.
#define NS_EVENT_STATE_DRAGOVER NS_DEFINE_EVENT_STATE_MACRO(7)
// Content is URL's target (ref).
#define NS_EVENT_STATE_URLTARGET NS_DEFINE_EVENT_STATE_MACRO(8)
// Content is required.
#define NS_EVENT_STATE_REQUIRED NS_DEFINE_EVENT_STATE_MACRO(9)
--- a/layout/base/ServoRestyleManager.cpp
+++ b/layout/base/ServoRestyleManager.cpp
@@ -12,30 +12,35 @@ using namespace mozilla::dom;
namespace mozilla {
ServoRestyleManager::ServoRestyleManager(nsPresContext* aPresContext)
: RestyleManagerBase(aPresContext)
{
}
/* static */ void
-ServoRestyleManager::DirtyTree(nsIContent* aContent)
+ServoRestyleManager::DirtyTree(nsIContent* aContent, bool aIncludingRoot)
{
- if (aContent->IsDirtyForServo()) {
- return;
+ if (aIncludingRoot) {
+ // XXX: This can in theory leave nodes not dirty, but in practice this is not
+ // a problem, at least for now, since right now element dirty implies
+ // descendants dirty. Remove this early return if this ever changes.
+ if (aContent->IsDirtyForServo()) {
+ return;
+ }
+
+ aContent->SetIsDirtyForServo();
}
- aContent->SetIsDirtyForServo();
-
FlattenedChildIterator it(aContent);
nsIContent* n = it.GetNextChild();
bool hadChildren = bool(n);
for ( ; n; n = it.GetNextChild()) {
- DirtyTree(n);
+ DirtyTree(n, true);
}
if (hadChildren) {
aContent->SetHasDirtyDescendantsForServo();
}
}
void
@@ -43,39 +48,43 @@ ServoRestyleManager::PostRestyleEvent(El
nsRestyleHint aRestyleHint,
nsChangeHint aMinChangeHint)
{
if (MOZ_UNLIKELY(IsDisconnected()) ||
MOZ_UNLIKELY(PresContext()->PresShell()->IsDestroying())) {
return;
}
- if (aRestyleHint == 0 && !aMinChangeHint) {
- // Nothing to do here
- return;
- }
-
- nsIPresShell* presShell = PresContext()->PresShell();
- if (!ObservingRefreshDriver()) {
- SetObservingRefreshDriver(PresContext()->RefreshDriver()->
- AddStyleFlushObserver(presShell));
+ // NOTE: We defer the processing of restyle/change hints until
+ // ProcessPendingRestyles.
+ bool needsRestyle = false;
+ if (aRestyleHint || aMinChangeHint) {
+ ServoElementSnapshot* snapshot = SnapshotForElement(aElement);
+ snapshot->AddExplicitRestyleHint(aRestyleHint);
+ snapshot->AddExplicitChangeHint(aMinChangeHint);
+ needsRestyle = true;
+ } else {
+ // Note that we could have been called just after adding an element to the
+ // modified elements hashmap, for example.
+ //
+ // Other way to structure this would be to duplicate the logic in
+ // AddElementSnapshot, at the (very minor) cost of possibly doing things
+ // twice.
+ needsRestyle = HasPendingRestyles();
}
- // Propagate the IS_DIRTY flag down the tree.
- DirtyTree(aElement);
+ if (needsRestyle) {
+ nsIPresShell* presShell = PresContext()->PresShell();
+ if (!ObservingRefreshDriver()) {
+ SetObservingRefreshDriver(PresContext()->RefreshDriver()->
+ AddStyleFlushObserver(presShell));
+ }
- // Propagate the HAS_DIRTY_DESCENDANTS flag to the root.
- nsINode* cur = aElement;
- while ((cur = cur->GetParentNode())) {
- if (cur->HasDirtyDescendantsForServo())
- break;
- cur->SetHasDirtyDescendantsForServo();
+ presShell->GetDocument()->SetNeedStyleFlush();
}
-
- presShell->GetDocument()->SetNeedStyleFlush();
}
void
ServoRestyleManager::PostRestyleEventForLazyConstruction()
{
NS_ERROR("stylo: ServoRestyleManager::PostRestyleEventForLazyConstruction not implemented");
}
@@ -132,32 +141,86 @@ ServoRestyleManager::RecreateStyleContex
FlattenedChildIterator it(aContent);
for (nsIContent* n = it.GetNextChild(); n; n = it.GetNextChild()) {
RecreateStyleContexts(n, primaryFrame->StyleContext(), aStyleSet);
}
aContent->UnsetFlags(NODE_HAS_DIRTY_DESCENDANTS_FOR_SERVO);
}
}
+static void
+MarkParentsAsHavingDirtyDescendants(Element* aElement)
+{
+ nsINode* cur = aElement;
+ while ((cur = cur->GetParentNode())) {
+ if (cur->HasDirtyDescendantsForServo()) {
+ break;
+ }
+
+ cur->SetHasDirtyDescendantsForServo();
+ }
+}
+
+void
+ServoRestyleManager::NoteRestyleHint(Element* aElement, nsRestyleHint aHint)
+{
+ if (aHint & eRestyle_Self) {
+ aElement->SetIsDirtyForServo();
+ MarkParentsAsHavingDirtyDescendants(aElement);
+ aHint |= eRestyle_Subtree;
+ }
+
+ if (aHint & eRestyle_Subtree) {
+ DirtyTree(aElement, /* aIncludingRoot = */ false);
+ MarkParentsAsHavingDirtyDescendants(aElement);
+ }
+
+ if (aHint & eRestyle_LaterSiblings) {
+ for (nsINode* cur = aElement->GetNextSibling(); cur; cur = cur->GetNextSibling()) {
+ if (cur->IsContent()) {
+ DirtyTree(cur->AsContent(), /* aIncludingRoot = */ true);
+ }
+ }
+ }
+
+ // TODO: Handle all other nsRestyleHint values.
+}
+
void
ServoRestyleManager::ProcessPendingRestyles()
{
if (!HasPendingRestyles()) {
return;
}
ServoStyleSet* styleSet = StyleSet();
nsIDocument* doc = PresContext()->Document();
Element* root = doc->GetRootElement();
if (root) {
+ for (auto iter = mModifiedElements.Iter(); !iter.Done(); iter.Next()) {
+ ServoElementSnapshot* snapshot = iter.UserData();
+ Element* element = iter.Key();
+
+ // TODO: avoid the ComputeRestyleHint call if we already have the highest
+ // explicit restyle hint?
+ nsRestyleHint hint = styleSet->ComputeRestyleHint(element, snapshot);
+ hint |= snapshot->ExplicitRestyleHint();
+
+ if (hint) {
+ NoteRestyleHint(element, hint);
+ }
+ }
+
styleSet->RestyleSubtree(root, /* aForce = */ false);
RecreateStyleContexts(root, nullptr, styleSet);
}
+ mModifiedElements.Clear();
+
// NB: we restyle from the root element, but the document also gets the
// HAS_DIRTY_DESCENDANTS flag as part of the loop on PostRestyleEvent, and we
// use that to check we have pending restyles.
//
// Thus, they need to get cleared here.
MOZ_ASSERT(!doc->IsDirtyForServo());
doc->UnsetFlags(NODE_HAS_DIRTY_DESCENDANTS_FOR_SERVO);
@@ -183,26 +246,50 @@ ServoRestyleManager::RestyleForRemove(El
nsIContent* aOldChild,
nsIContent* aFollowingSibling)
{
NS_ERROR("stylo: ServoRestyleManager::RestyleForRemove not implemented");
}
nsresult
ServoRestyleManager::ContentStateChanged(nsIContent* aContent,
- EventStates aStateMask)
+ EventStates aChangedBits)
{
if (!aContent->IsElement()) {
return NS_OK;
}
Element* aElement = aContent->AsElement();
nsChangeHint changeHint;
nsRestyleHint restyleHint;
- ContentStateChangedInternal(aElement, aStateMask, &changeHint, &restyleHint);
+
+ // NOTE: restyleHint here is effectively always 0, since that's what
+ // ServoStyleSet::HasStateDependentStyle returns. Servo computes on
+ // ProcessPendingRestyles using the ElementSnapshot, but in theory could
+ // compute it sequentially easily.
+ //
+ // Determine what's the best way to do it, and how much work do we save
+ // processing the restyle hint early (i.e., computing the style hint here
+ // sequentially, potentially saving the snapshot), vs lazily (snapshot
+ // approach).
+ //
+ // If we take the sequential approach we need to specialize Servo's restyle
+ // hints system a bit more, and mesure whether we save something storing the
+ // restyle hint in the table and deferring the dirtiness setting until
+ // ProcessPendingRestyles (that's a requirement if we store snapshots though),
+ // vs processing the restyle hint in-place, dirtying the nodes on
+ // PostRestyleEvent.
+ //
+ // If we definitely take the snapshot approach, we should take rid of
+ // HasStateDependentStyle, etc (though right now they're no-ops).
+ ContentStateChangedInternal(aElement, aChangedBits, &changeHint, &restyleHint);
+
+ EventStates previousState = aElement->StyleState() ^ aChangedBits;
+ ServoElementSnapshot* snapshot = SnapshotForElement(aElement);
+ snapshot->AddState(previousState);
PostRestyleEvent(aElement, restyleHint, changeHint);
return NS_OK;
}
void
ServoRestyleManager::AttributeWillChange(Element* aElement,
int32_t aNameSpaceID,
@@ -224,9 +311,20 @@ ServoRestyleManager::AttributeChanged(El
}
nsresult
ServoRestyleManager::ReparentStyleContext(nsIFrame* aFrame)
{
MOZ_CRASH("stylo: ServoRestyleManager::ReparentStyleContext not implemented");
}
+ServoElementSnapshot*
+ServoRestyleManager::SnapshotForElement(Element* aElement)
+{
+ ServoElementSnapshot* snapshot = mModifiedElements.LookupOrAdd(aElement);
+ if (!snapshot->HasAny(ServoElementSnapshot::Flags::HTMLElementInHTMLDocument)) {
+ snapshot->SetIsHTMLElementInHTMLDocument(
+ aElement->IsHTMLElement() && aElement->OwnerDoc()->IsHTMLDocument());
+ }
+ return snapshot;
+}
+
} // namespace mozilla
--- a/layout/base/ServoRestyleManager.h
+++ b/layout/base/ServoRestyleManager.h
@@ -4,20 +4,22 @@
* 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_ServoRestyleManager_h
#define mozilla_ServoRestyleManager_h
#include "mozilla/EventStates.h"
#include "mozilla/RestyleManagerBase.h"
+#include "mozilla/ServoElementSnapshot.h"
#include "nsChangeHint.h"
#include "nsISupportsImpl.h"
#include "nsPresContext.h"
#include "nsINode.h"
+#include "nsHashKeys.h"
namespace mozilla {
namespace dom {
class Element;
} // namespace dom
} // namespace mozilla
class nsAttrValue;
class nsIAtom;
@@ -63,44 +65,53 @@ public:
void AttributeChanged(dom::Element* aElement,
int32_t aNameSpaceID,
nsIAtom* aAttribute,
int32_t aModType,
const nsAttrValue* aOldValue);
nsresult ReparentStyleContext(nsIFrame* aFrame);
bool HasPendingRestyles() {
- if (MOZ_UNLIKELY(IsDisconnected())) {
- return false;
- }
-
- return PresContext()->PresShell()->GetDocument()->HasDirtyDescendantsForServo();
+ return !mModifiedElements.IsEmpty();
}
protected:
~ServoRestyleManager() {}
private:
+ ServoElementSnapshot* SnapshotForElement(Element* aElement);
+
+ /**
+ * The element-to-element snapshot table to compute restyle hints.
+ */
+ nsClassHashtable<nsRefPtrHashKey<Element>,
+ ServoElementSnapshot> mModifiedElements;
+
/**
* Traverses a tree of content that Servo has just restyled, recreating style
* contexts for their frames with the new style data.
*
* This is just static so ServoStyleSet can mark this class as friend, so we
* can access to the GetContext method without making it available to everyone
* else.
*/
static void RecreateStyleContexts(nsIContent* aContent,
nsStyleContext* aParentContext,
ServoStyleSet* aStyleSet);
/**
* Propagates the IS_DIRTY flag down to the tree, setting
* HAS_DIRTY_DESCENDANTS appropriately.
*/
- static void DirtyTree(nsIContent* aContent);
+ static void DirtyTree(nsIContent* aContent, bool aIncludingRoot = true);
+
+ /**
+ * Marks the tree with the appropriate flags for the given restyle hint.
+ */
+ static void NoteRestyleHint(Element* aElement, nsRestyleHint aRestyleHint);
inline ServoStyleSet* StyleSet() const {
MOZ_ASSERT(PresContext()->StyleSet()->IsServo(),
"ServoRestyleManager should only be used with a Servo-flavored "
"style backend");
return PresContext()->StyleSet()->AsServo();
}
};
--- a/layout/style/ServoBindings.cpp
+++ b/layout/style/ServoBindings.cpp
@@ -19,16 +19,17 @@
#include "nsString.h"
#include "nsStyleStruct.h"
#include "nsTArray.h"
#include "nsStyleUtil.h"
#include "StyleStructContext.h"
#include "mozilla/EventStates.h"
#include "mozilla/dom/Element.h"
+#include "mozilla/ServoElementSnapshot.h"
uint32_t
Gecko_ChildrenCount(RawGeckoNode* aNode)
{
return aNode->GetChildCount();
}
bool
@@ -97,21 +98,20 @@ Gecko_GetNextSiblingElement(RawGeckoElem
}
RawGeckoElement*
Gecko_GetDocumentElement(RawGeckoDocument* aDoc)
{
return aDoc->GetDocumentElement();
}
-uint8_t
+EventStates::ServoType
Gecko_ElementState(RawGeckoElement* aElement)
{
- return aElement->StyleState().GetInternalValue() &
- ((1 << (NS_EVENT_STATE_HIGHEST_SERVO_BIT + 1)) - 1);
+ return aElement->StyleState().ServoValue();
}
bool
Gecko_IsHTMLElementInHTMLDocument(RawGeckoElement* aElement)
{
return aElement->IsHTMLElement() && aElement->OwnerDoc()->IsHTMLDocument();
}
@@ -179,19 +179,39 @@ Gecko_SetNodeFlags(RawGeckoNode* aNode,
}
void
Gecko_UnsetNodeFlags(RawGeckoNode* aNode, uint32_t aFlags)
{
aNode->UnsetFlags(aFlags);
}
-template<class MatchFn>
+ServoDeclarationBlock*
+Gecko_GetServoDeclarationBlock(RawGeckoElement* aElement)
+{
+ const nsAttrValue* attr = aElement->GetParsedAttr(nsGkAtoms::style);
+ if (!attr || attr->Type() != nsAttrValue::eServoCSSDeclaration) {
+ return nullptr;
+ }
+ return attr->GetServoCSSDeclarationValue();
+}
+
+namespace attribute_matching {
+
+template<typename Implementor>
+nsIAtom*
+AtomAttrValue(Implementor* aElement, nsIAtom* aName)
+{
+ const nsAttrValue* attr = aElement->GetParsedAttr(aName);
+ return attr ? attr->GetAtomValue() : nullptr;
+}
+
+template<typename Implementor, typename MatchFn>
bool
-DoMatch(RawGeckoElement* aElement, nsIAtom* aNS, nsIAtom* aName, MatchFn aMatch)
+DoMatch(Implementor* aElement, nsIAtom* aNS, nsIAtom* aName, MatchFn aMatch)
{
if (aNS) {
int32_t ns = nsContentUtils::NameSpaceManager()->GetNameSpaceID(aNS);
NS_ENSURE_TRUE(ns != kNameSpaceID_Unknown, false);
const nsAttrValue* value = aElement->GetParsedAttr(aName, ns);
return value && aMatch(value);
}
// No namespace means any namespace - we have to check them all. :-(
@@ -213,104 +233,124 @@ DoMatch(RawGeckoElement* aElement, nsIAt
// bug 1281935 lands.
template<typename T>
struct FakeRef {
MOZ_IMPLICIT FakeRef(T* aPtr) : mPtr(aPtr) {}
operator T*() const { return mPtr; }
T* mPtr;
};
+template<typename Implementor>
bool
-Gecko_HasAttr(RawGeckoElement* aElement, nsIAtom* aNS, nsIAtom* aName)
+HasAttr(Implementor* aElement, nsIAtom* aNS, nsIAtom* aName)
{
auto match = [](const nsAttrValue* aValue) { return true; };
return DoMatch(aElement, aNS, aName, match);
}
+template<typename Implementor>
bool
-Gecko_AttrEquals(RawGeckoElement* aElement, nsIAtom* aNS, nsIAtom* aName,
- nsIAtom* aStr_, bool aIgnoreCase)
+AttrEquals(Implementor* aElement, nsIAtom* aNS, nsIAtom* aName,
+ nsIAtom* aStr_, bool aIgnoreCase)
{
FakeRef<nsIAtom> aStr(aStr_);
auto match = [aStr, aIgnoreCase](const nsAttrValue* aValue) {
return aValue->Equals(aStr, aIgnoreCase ? eIgnoreCase : eCaseMatters);
};
return DoMatch(aElement, aNS, aName, match);
}
+template<typename Implementor>
bool
-Gecko_AttrDashEquals(RawGeckoElement* aElement, nsIAtom* aNS, nsIAtom* aName,
- nsIAtom* aStr_)
+AttrDashEquals(Implementor* aElement, nsIAtom* aNS, nsIAtom* aName,
+ nsIAtom* aStr_)
{
FakeRef<nsIAtom> aStr(aStr_);
auto match = [aStr](const nsAttrValue* aValue) {
nsAutoString str;
aValue->ToString(str);
const nsDefaultStringComparator c;
return nsStyleUtil::DashMatchCompare(str, nsDependentAtomString(aStr), c);
};
return DoMatch(aElement, aNS, aName, match);
}
+template<typename Implementor>
bool
-Gecko_AttrIncludes(RawGeckoElement* aElement, nsIAtom* aNS, nsIAtom* aName,
- nsIAtom* aStr_)
+AttrIncludes(Implementor* aElement, nsIAtom* aNS, nsIAtom* aName,
+ nsIAtom* aStr_)
{
FakeRef<nsIAtom> aStr(aStr_);
auto match = [aStr](const nsAttrValue* aValue) {
nsAutoString str;
aValue->ToString(str);
const nsDefaultStringComparator c;
return nsStyleUtil::ValueIncludes(str, nsDependentAtomString(aStr), c);
};
return DoMatch(aElement, aNS, aName, match);
}
+template<typename Implementor>
bool
-Gecko_AttrHasSubstring(RawGeckoElement* aElement, nsIAtom* aNS, nsIAtom* aName,
- nsIAtom* aStr_)
+AttrHasSubstring(Implementor* aElement, nsIAtom* aNS, nsIAtom* aName,
+ nsIAtom* aStr_)
{
FakeRef<nsIAtom> aStr(aStr_);
auto match = [aStr](const nsAttrValue* aValue) {
nsAutoString str;
aValue->ToString(str);
return FindInReadable(str, nsDependentAtomString(aStr));
};
return DoMatch(aElement, aNS, aName, match);
}
+template<typename Implementor>
bool
-Gecko_AttrHasPrefix(RawGeckoElement* aElement, nsIAtom* aNS, nsIAtom* aName,
- nsIAtom* aStr_)
+AttrHasPrefix(Implementor* aElement, nsIAtom* aNS, nsIAtom* aName,
+ nsIAtom* aStr_)
{
FakeRef<nsIAtom> aStr(aStr_);
auto match = [aStr](const nsAttrValue* aValue) {
nsAutoString str;
aValue->ToString(str);
return StringBeginsWith(str, nsDependentAtomString(aStr));
};
return DoMatch(aElement, aNS, aName, match);
}
+template<typename Implementor>
bool
-Gecko_AttrHasSuffix(RawGeckoElement* aElement, nsIAtom* aNS, nsIAtom* aName,
- nsIAtom* aStr_)
+AttrHasSuffix(Implementor* aElement, nsIAtom* aNS, nsIAtom* aName,
+ nsIAtom* aStr_)
{
FakeRef<nsIAtom> aStr(aStr_);
auto match = [aStr](const nsAttrValue* aValue) {
nsAutoString str;
aValue->ToString(str);
return StringEndsWith(str, nsDependentAtomString(aStr));
};
return DoMatch(aElement, aNS, aName, match);
}
+/**
+ * Gets the class or class list (if any) of the implementor. The calling
+ * convention here is rather hairy, and is optimized for getting Servo the
+ * information it needs for hot calls.
+ *
+ * The return value indicates the number of classes. If zero, neither outparam
+ * is valid. If one, the class_ outparam is filled with the atom of the class.
+ * If two or more, the classList outparam is set to point to an array of atoms
+ * representing the class list.
+ *
+ * The array is borrowed and the atoms are not addrefed. These values can be
+ * invalidated by any DOM mutation. Use them in a tight scope.
+ */
+template<typename Implementor>
uint32_t
-Gecko_ClassOrClassList(RawGeckoElement* aElement,
- nsIAtom** aClass, nsIAtom*** aClassList)
+ClassOrClassList(Implementor* aElement, nsIAtom** aClass, nsIAtom*** aClassList)
{
const nsAttrValue* attr = aElement->GetParsedAttr(nsGkAtoms::_class);
if (!attr) {
return 0;
}
// For class values with only whitespace, Gecko just stores a string. For the
// purposes of the style system, there is no class in this case.
@@ -352,26 +392,78 @@ Gecko_ClassOrClassList(RawGeckoElement*
static_assert(alignof(nsCOMPtr<nsIAtom>) == alignof(nsIAtom*), "Bad simplification");
nsCOMPtr<nsIAtom>* elements = atomArray->Elements();
nsIAtom** rawElements = reinterpret_cast<nsIAtom**>(elements);
*aClassList = rawElements;
return atomArray->Length();
}
-ServoDeclarationBlock*
-Gecko_GetServoDeclarationBlock(RawGeckoElement* aElement)
-{
- const nsAttrValue* attr = aElement->GetParsedAttr(nsGkAtoms::style);
- if (!attr || attr->Type() != nsAttrValue::eServoCSSDeclaration) {
- return nullptr;
- }
- return attr->GetServoCSSDeclarationValue();
+} // namespace attribute_matching
+
+#define SERVO_IMPL_ELEMENT_ATTR_MATCHING_FUNCTION(prefix_, implementor_) \
+ \
+nsIAtom* \
+prefix_ ## AtomAttrValue(implementor_* aElement, nsIAtom* aName) \
+{ \
+ return attribute_matching::AtomAttrValue(aElement, aName); \
+} \
+ \
+bool \
+prefix_ ## HasAttr(implementor_* aElement, nsIAtom* aNS, nsIAtom* aName) \
+{ \
+ return attribute_matching::HasAttr(aElement, aNS, aName); \
+} \
+ \
+bool prefix_ ## AttrEquals(implementor_* aElement, nsIAtom* aNS, \
+ nsIAtom* aName, nsIAtom* aStr, bool aIgnoreCase) \
+{ \
+ return attribute_matching::AttrEquals(aElement, aNS, aName, aStr, \
+ aIgnoreCase); \
+} \
+ \
+bool prefix_ ## AttrDashEquals(implementor_* aElement, nsIAtom* aNS, \
+ nsIAtom* aName, nsIAtom* aStr) \
+{ \
+ return attribute_matching::AttrDashEquals(aElement, aNS, aName, aStr); \
+} \
+ \
+bool prefix_ ## AttrIncludes(implementor_* aElement, nsIAtom* aNS, \
+ nsIAtom* aName, nsIAtom* aStr) \
+{ \
+ return attribute_matching::AttrIncludes(aElement, aNS, aName, aStr); \
+} \
+ \
+bool prefix_ ## AttrHasSubstring(implementor_* aElement, nsIAtom* aNS, \
+ nsIAtom* aName, nsIAtom* aStr) \
+{ \
+ return attribute_matching::AttrHasSubstring(aElement, aNS, aName, aStr); \
+} \
+ \
+bool prefix_ ## AttrHasPrefix(implementor_* aElement, nsIAtom* aNS, \
+ nsIAtom* aName, nsIAtom* aStr) \
+{ \
+ return attribute_matching::AttrHasPrefix(aElement, aNS, aName, aStr); \
+} \
+ \
+bool prefix_ ## AttrHasSuffix(implementor_* aElement, nsIAtom* aNS, \
+ nsIAtom* aName, nsIAtom* aStr) \
+{ \
+ return attribute_matching::AttrHasSuffix(aElement, aNS, aName, aStr); \
+} \
+ \
+uint32_t prefix_ ## ClassOrClassList(implementor_* aElement, nsIAtom** aClass, \
+ nsIAtom*** aClassList) \
+{ \
+ return attribute_matching::ClassOrClassList(aElement, aClass, aClassList); \
}
+SERVO_IMPL_ELEMENT_ATTR_MATCHING_FUNCTION(Gecko_, RawGeckoElement)
+SERVO_IMPL_ELEMENT_ATTR_MATCHING_FUNCTION(Gecko_Snapshot, ServoElementSnapshot)
+
ServoNodeData*
Gecko_GetNodeData(RawGeckoNode* aNode)
{
return aNode->GetServoNodeData();
}
void
Gecko_SetNodeData(RawGeckoNode* aNode, ServoNodeData* aData)
@@ -777,16 +869,26 @@ Servo_ReleaseComputedValues(ServoCompute
void
Servo_Initialize()
{
MOZ_CRASH("stylo: shouldn't be calling Servo_Initialize in a "
"non-MOZ_STYLO build");
}
+// Restyle hints.
+nsRestyleHint
+Servo_ComputeRestyleHint(RawGeckoElement* element,
+ ServoElementSnapshot* snapshot,
+ RawServoStyleSet* set)
+{
+ MOZ_CRASH("stylo: shouldn't be calling Servo_ComputeRestyleHint in a "
+ "non-MOZ_STYLO build");
+}
+
void
Servo_RestyleDocument(RawGeckoDocument* doc, RawServoStyleSet* set)
{
MOZ_CRASH("stylo: shouldn't be calling Servo_RestyleDocument in a "
"non-MOZ_STYLO build");
}
void Servo_RestyleSubtree(RawGeckoNode* node, RawServoStyleSet* set)
--- a/layout/style/ServoBindings.h
+++ b/layout/style/ServoBindings.h
@@ -6,17 +6,19 @@
#ifndef mozilla_ServoBindings_h
#define mozilla_ServoBindings_h
#include "stdint.h"
#include "nsColor.h"
#include "nsStyleStruct.h"
#include "mozilla/css/SheetParsingMode.h"
+#include "mozilla/ServoElementSnapshot.h"
#include "nsProxyRelease.h"
+#include "nsChangeHint.h"
/*
* API for Servo to access Gecko data structures. This file must compile as valid
* C code in order for the binding generator to parse it.
*
* Functions beginning with Gecko_ are implemented in Gecko and invoked from Servo.
* Functions beginning with Servo_ are implemented in Servo and invoked from Gecko.
*/
@@ -30,16 +32,17 @@ struct nsFont;
namespace mozilla {
class FontFamilyList;
enum FontFamilyType : uint32_t;
namespace dom { class Element; }
}
using mozilla::FontFamilyList;
using mozilla::FontFamilyType;
using mozilla::dom::Element;
+using mozilla::ServoElementSnapshot;
typedef mozilla::dom::Element RawGeckoElement;
class nsIDocument;
typedef nsIDocument RawGeckoDocument;
struct ServoNodeData;
struct ServoComputedValues;
struct RawServoStyleSheet;
struct RawServoStyleSet;
class nsHTMLCSSStyleSheet;
@@ -98,39 +101,45 @@ bool Gecko_IsTextNode(RawGeckoNode* node
bool Gecko_IsVisitedLink(RawGeckoElement* element);
bool Gecko_IsUnvisitedLink(RawGeckoElement* element);
bool Gecko_IsRootElement(RawGeckoElement* element);
nsIAtom* Gecko_LocalName(RawGeckoElement* element);
nsIAtom* Gecko_Namespace(RawGeckoElement* element);
nsIAtom* Gecko_GetElementId(RawGeckoElement* element);
// Attributes.
-bool Gecko_HasAttr(RawGeckoElement* element, nsIAtom* ns, nsIAtom* name);
-bool Gecko_AttrEquals(RawGeckoElement* element, nsIAtom* ns, nsIAtom* name, nsIAtom* str, bool ignoreCase);
-bool Gecko_AttrDashEquals(RawGeckoElement* element, nsIAtom* ns, nsIAtom* name, nsIAtom* str);
-bool Gecko_AttrIncludes(RawGeckoElement* element, nsIAtom* ns, nsIAtom* name, nsIAtom* str);
-bool Gecko_AttrHasSubstring(RawGeckoElement* element, nsIAtom* ns, nsIAtom* name, nsIAtom* str);
-bool Gecko_AttrHasPrefix(RawGeckoElement* element, nsIAtom* ns, nsIAtom* name, nsIAtom* str);
-bool Gecko_AttrHasSuffix(RawGeckoElement* element, nsIAtom* ns, nsIAtom* name, nsIAtom* str);
-
+#define SERVO_DECLARE_ELEMENT_ATTR_MATCHING_FUNCTIONS(prefix_, implementor_) \
+ \
+nsIAtom* prefix_ ## AtomAttrValue(implementor_* element, nsIAtom* attribute); \
+ \
+bool prefix_ ## HasAttr(implementor_* element, nsIAtom* ns, nsIAtom* name); \
+ \
+bool prefix_ ## AttrEquals(implementor_* element, nsIAtom* ns, nsIAtom* name, \
+ nsIAtom* str, bool ignoreCase); \
+ \
+bool prefix_ ## AttrDashEquals(implementor_* element, nsIAtom* ns, \
+ nsIAtom* name, nsIAtom* str); \
+ \
+bool prefix_ ## AttrIncludes(implementor_* element, nsIAtom* ns, \
+ nsIAtom* name, nsIAtom* str); \
+ \
+bool prefix_ ## AttrHasSubstring(implementor_* element, nsIAtom* ns, \
+ nsIAtom* name, nsIAtom* str); \
+ \
+bool prefix_ ## AttrHasPrefix(implementor_* element, nsIAtom* ns, \
+ nsIAtom* name, nsIAtom* str); \
+ \
+bool prefix_ ## AttrHasSuffix(implementor_* element, nsIAtom* ns, \
+ nsIAtom* name, nsIAtom* str); \
+ \
+uint32_t prefix_ ## ClassOrClassList(implementor_* element, nsIAtom** class_, \
+ nsIAtom*** classList);
-// Gets the class or class list (if any) of the Element.
-//
-// The calling convention here is rather hairy, and is optimized for getting
-// Servo the information it needs for hot calls.
-//
-// The return value indicates the number of classes. If zero, neither outparam
-// is valid. If one, the class_ outparam is filled with the atom of the class.
-// If two or more, the classList outparam is set to point to an array of atoms
-// representing the class list.
-//
-// The array is borrowed and the atoms are not addrefed. These values can be
-// invalidated by any DOM mutation. Use them in a tight scope.
-uint32_t Gecko_ClassOrClassList(RawGeckoElement* element,
- nsIAtom** class_, nsIAtom*** classList);
+SERVO_DECLARE_ELEMENT_ATTR_MATCHING_FUNCTIONS(Gecko_, RawGeckoElement)
+SERVO_DECLARE_ELEMENT_ATTR_MATCHING_FUNCTIONS(Gecko_Snapshot, ServoElementSnapshot)
// Style attributes.
ServoDeclarationBlock* Gecko_GetServoDeclarationBlock(RawGeckoElement* element);
// Node data.
ServoNodeData* Gecko_GetNodeData(RawGeckoNode* node);
void Gecko_SetNodeData(RawGeckoNode* node, ServoNodeData* data);
void Servo_DropNodeData(ServoNodeData* data);
@@ -245,16 +254,21 @@ void Servo_ReleaseComputedValues(ServoCo
// Initialize Servo components. Should be called exactly once at startup.
void Servo_Initialize();
// Restyle the given document or subtree.
void Servo_RestyleDocument(RawGeckoDocument* doc, RawServoStyleSet* set);
void Servo_RestyleSubtree(RawGeckoNode* node, RawServoStyleSet* set);
+// Restyle hints.
+nsRestyleHint Servo_ComputeRestyleHint(RawGeckoElement* element,
+ ServoElementSnapshot* snapshot,
+ RawServoStyleSet* set);
+
// Style-struct management.
#define STYLE_STRUCT(name, checkdata_cb) \
struct nsStyle##name; \
void Gecko_Construct_nsStyle##name(nsStyle##name* ptr); \
void Gecko_CopyConstruct_nsStyle##name(nsStyle##name* ptr, const nsStyle##name* other); \
void Gecko_Destroy_nsStyle##name(nsStyle##name* ptr); \
const nsStyle##name* Servo_GetStyle##name(ServoComputedValues* computedValues);
#include "nsStyleStructList.h"
new file mode 100644
--- /dev/null
+++ b/layout/style/ServoElementSnapshot.cpp
@@ -0,0 +1,32 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/ServoElementSnapshot.h"
+#include "mozilla/dom/Element.h"
+
+namespace mozilla {
+
+void
+ServoElementSnapshot::AddAttrs(Element* aElement)
+{
+ MOZ_ASSERT(aElement);
+
+ if (!HasAny(Flags::Attributes)) {
+ return;
+ }
+
+ uint32_t attrCount = aElement->GetAttrCount();
+ const nsAttrName* attrName;
+ for (uint32_t i = 0; i < attrCount; ++i) {
+ attrName = aElement->GetAttrNameAt(i);
+ const nsAttrValue* attrValue =
+ aElement->GetParsedAttr(attrName->LocalName(), attrName->NamespaceID());
+ mAttrs.AppendElement(ServoAttrSnapshot(*attrName, *attrValue));
+ }
+ mContains |= Flags::Attributes;
+}
+
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/layout/style/ServoElementSnapshot.h
@@ -0,0 +1,171 @@
+/* -*- 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_ServoElementSnapshot_h
+#define mozilla_ServoElementSnapshot_h
+
+#include "mozilla/EventStates.h"
+#include "mozilla/TypedEnumBits.h"
+#include "nsAttrName.h"
+#include "nsAttrValue.h"
+#include "nsChangeHint.h"
+#include "nsIAtom.h"
+
+namespace mozilla {
+
+namespace dom {
+class Element;
+} // namespace dom
+
+/**
+ * A structure representing a single attribute name and value.
+ *
+ * This is pretty similar to the private nsAttrAndChildArray::InternalAttr.
+ */
+struct ServoAttrSnapshot {
+ nsAttrName mName;
+ nsAttrValue mValue;
+
+ ServoAttrSnapshot(const nsAttrName& aName,
+ const nsAttrValue& aValue)
+ : mName(aName)
+ , mValue(aValue)
+ {}
+};
+
+/**
+ * A bitflags enum class used to determine what data does a ServoElementSnapshot
+ * contains.
+ */
+enum class ServoElementSnapshotFlags : uint8_t {
+ State = 1 << 0,
+ Attributes = 1 << 1,
+ HTMLElementInHTMLDocument = 1 << 2,
+ All = State | Attributes | HTMLElementInHTMLDocument
+};
+
+MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(ServoElementSnapshotFlags)
+
+/**
+ * This class holds all non-tree-structural state of an element that might be
+ * used for selector matching eventually.
+ *
+ * This means the attributes, and the element state, such as :hover, :active,
+ * etc...
+ */
+class ServoElementSnapshot
+{
+ typedef dom::Element Element;
+ typedef EventStates::ServoType ServoStateType;
+public:
+ typedef ServoElementSnapshotFlags Flags;
+
+ /**
+ * Empty snapshot, with no data at all.
+ */
+ ServoElementSnapshot()
+ : mContains(Flags(0))
+ , mState(0)
+ , mExplicitRestyleHint(nsRestyleHint(0))
+ , mExplicitChangeHint(nsChangeHint(0))
+ , mIsHTMLElementInHTMLDocument(false)
+ {}
+
+ bool HasAttrs() {
+ return HasAny(Flags::Attributes);
+ }
+
+ bool HasState() {
+ return HasAny(Flags::State);
+ }
+
+ /**
+ * Captures the given state (if not previously captured).
+ */
+ void AddState(EventStates aState) {
+ if (!HasAny(Flags::State)) {
+ mState = aState.ServoValue();
+ mContains |= Flags::State;
+ }
+ }
+
+ /**
+ * Captures the given element attributes (if not previously captured).
+ */
+ void AddAttrs(Element* aElement);
+
+ void AddExplicitChangeHint(nsChangeHint aMinChangeHint) {
+ mExplicitChangeHint |= aMinChangeHint;
+ }
+
+ void AddExplicitRestyleHint(nsRestyleHint aRestyleHint) {
+ mExplicitRestyleHint |= aRestyleHint;
+ }
+
+ nsRestyleHint ExplicitRestyleHint() { return mExplicitRestyleHint; }
+
+ nsChangeHint ExplicitChangeHint() { return mExplicitChangeHint; }
+
+ /**
+ * Needed methods for attribute matching.
+ */
+ const nsAttrName* GetAttrNameAt(uint32_t aIndex) const {
+ return &mAttrs[aIndex].mName;
+ }
+
+ const nsAttrValue* GetParsedAttr(nsIAtom* aLocalName) const {
+ return GetParsedAttr(aLocalName, kNameSpaceID_None);
+ }
+
+ const nsAttrValue* GetParsedAttr(nsIAtom* aLocalName, int32_t aNamespaceID) const {
+ uint32_t i, len = mAttrs.Length();
+ if (aNamespaceID == kNameSpaceID_None) {
+ // This should be the common case so lets make an optimized loop
+ for (i = 0; i < len; ++i) {
+ if (mAttrs[i].mName.Equals(aLocalName)) {
+ return &mAttrs[i].mValue;
+ }
+ }
+
+ return nullptr;
+ }
+
+ for (i = 0; i < len; ++i) {
+ if (mAttrs[i].mName.Equals(aLocalName, aNamespaceID)) {
+ return &mAttrs[i].mValue;
+ }
+ }
+
+ return nullptr;
+ }
+
+ void SetIsHTMLElementInHTMLDocument(bool aIs) {
+ MOZ_ASSERT(!HasAny(Flags::HTMLElementInHTMLDocument),
+ "Only expected to be set once!");
+ mContains |= Flags::HTMLElementInHTMLDocument;
+ mIsHTMLElementInHTMLDocument = aIs;
+ }
+
+ bool HasAny(Flags aFlags) {
+ return bool(mContains & aFlags);
+ }
+private:
+
+ // TODO: Profile, a 1 or 2 element AutoTArray could be worth it, given we know
+ // we're dealing with attribute changes when we take snapshots of attributes,
+ // though it can be wasted space if we deal with a lot of state-only
+ // snapshots.
+ Flags mContains;
+ nsTArray<ServoAttrSnapshot> mAttrs;
+ ServoStateType mState;
+ nsRestyleHint mExplicitRestyleHint;
+ nsChangeHint mExplicitChangeHint;
+ bool mIsHTMLElementInHTMLDocument;
+};
+
+} // namespace mozilla
+
+#endif
--- a/layout/style/ServoStyleSet.cpp
+++ b/layout/style/ServoStyleSet.cpp
@@ -376,16 +376,23 @@ ServoStyleSet::HasStateDependentStyle(do
CSSPseudoElementType aPseudoType,
dom::Element* aPseudoElement,
EventStates aStateMask)
{
NS_ERROR("stylo: HasStateDependentStyle not implemented");
return nsRestyleHint(0);
}
+nsRestyleHint
+ServoStyleSet::ComputeRestyleHint(dom::Element* aElement,
+ ServoElementSnapshot* aSnapshot)
+{
+ return Servo_ComputeRestyleHint(aElement, aSnapshot, mRawSet.get());
+}
+
void
ServoStyleSet::RestyleSubtree(nsINode* aNode, bool aForce)
{
if (aForce) {
MOZ_ASSERT(aNode->IsContent());
ServoRestyleManager::DirtyTree(aNode->AsContent());
}
--- a/layout/style/ServoStyleSet.h
+++ b/layout/style/ServoStyleSet.h
@@ -6,16 +6,17 @@
#ifndef mozilla_ServoStyleSet_h
#define mozilla_ServoStyleSet_h
#include "mozilla/EnumeratedArray.h"
#include "mozilla/EventStates.h"
#include "mozilla/RefPtr.h"
#include "mozilla/ServoBindingHelpers.h"
+#include "mozilla/ServoElementSnapshot.h"
#include "mozilla/ServoStyleSheet.h"
#include "mozilla/SheetType.h"
#include "mozilla/UniquePtr.h"
#include "nsChangeHint.h"
#include "nsCSSPseudoElements.h"
#include "nsIAtom.h"
#include "nsTArray.h"
@@ -114,16 +115,22 @@ public:
nsRestyleHint HasStateDependentStyle(dom::Element* aElement,
EventStates aStateMask);
nsRestyleHint HasStateDependentStyle(dom::Element* aElement,
mozilla::CSSPseudoElementType aPseudoType,
dom::Element* aPseudoElement,
EventStates aStateMask);
/**
+ * Computes a restyle hint given a element and a previous element snapshot.
+ */
+ nsRestyleHint ComputeRestyleHint(dom::Element* aElement,
+ ServoElementSnapshot* aSnapshot);
+
+ /**
* Restyles a whole subtree of nodes.
*
* The aForce parameter propagates the dirty bits down the subtree, and when
* used aNode needs to be nsIContent.
*/
void RestyleSubtree(nsINode* aNode, bool aForce);
private:
--- a/layout/style/moz.build
+++ b/layout/style/moz.build
@@ -88,16 +88,17 @@ EXPORTS.mozilla += [
'CSSVariableValues.h',
'HandleRefPtr.h',
'IncrementalClearCOMRuleArray.h',
'LayerAnimationInfo.h',
'RuleNodeCacheConditions.h',
'RuleProcessorCache.h',
'ServoBindingHelpers.h',
'ServoBindings.h',
+ 'ServoElementSnapshot.h',
'ServoStyleSet.h',
'ServoStyleSheet.h',
'SheetType.h',
'StyleAnimationValue.h',
'StyleBackendType.h',
'StyleContextSource.h',
'StyleSetHandle.h',
'StyleSetHandleInlines.h',
@@ -185,16 +186,17 @@ UNIFIED_SOURCES += [
'nsStyleSet.cpp',
'nsStyleStruct.cpp',
'nsStyleTransformMatrix.cpp',
'nsStyleUtil.cpp',
'nsTransitionManager.cpp',
'RuleNodeCacheConditions.cpp',
'RuleProcessorCache.cpp',
'ServoBindings.cpp',
+ 'ServoElementSnapshot.cpp',
'ServoStyleSet.cpp',
'ServoStyleSheet.cpp',
'StyleAnimationValue.cpp',
'StyleRule.cpp',
'StyleSheet.cpp',
'StyleSheetInfo.cpp',
'SVGAttrAnimationRuleProcessor.cpp',
]
--- a/xpcom/glue/nsClassHashtable.h
+++ b/xpcom/glue/nsClassHashtable.h
@@ -61,16 +61,22 @@ public:
*
* Normally, an entry is deleted when it's removed from an nsClassHashtable,
* but this function transfers ownership of the entry back to the caller
* through aOut -- the entry will be deleted when aOut goes out of scope.
*
* @param aKey the key to get and remove from the hashtable
*/
void RemoveAndForget(KeyType aKey, nsAutoPtr<T>& aOut);
+
+ /**
+ * Returns whether this table is empty. Re-exported as public from
+ * nsBaseHashtable, where it's protected.
+ */
+ bool IsEmpty() { return base_type::IsEmpty(); }
};
//
// nsClassHashtable definitions
//
template<class KeyClass, class T>
T*