Bug 1292618: Support basic pseudo-element restyling. r?heycam
:before and :after only, for now.
MozReview-Commit-ID: 9hLFvVhqIrN
--- a/layout/base/ServoRestyleManager.cpp
+++ b/layout/base/ServoRestyleManager.cpp
@@ -1,16 +1,19 @@
/* -*- 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/ServoRestyleManager.h"
#include "mozilla/ServoStyleSet.h"
+#include "mozilla/dom/ChildIterator.h"
+#include "nsContentUtils.h"
+#include "nsStyleChangeList.h"
using namespace mozilla::dom;
namespace mozilla {
ServoRestyleManager::ServoRestyleManager(nsPresContext* aPresContext)
: RestyleManagerBase(aPresContext)
{
@@ -74,20 +77,20 @@ ServoRestyleManager::RecreateStyleContex
return;
}
if (aContent->IsDirtyForServo()) {
RefPtr<ServoComputedValues> computedValues =
dont_AddRef(Servo_GetComputedValues(aContent));
MOZ_ASSERT(computedValues);
+ nsChangeHint changeHint = nsChangeHint(0);
// NB: Change hint processing only applies to elements, at least until we
// support display: contents.
if (aContent->IsElement()) {
- nsChangeHint changeHint = nsChangeHint(0);
Element* element = aContent->AsElement();
// Add an explicit change hint if appropriate.
ServoElementSnapshot* snapshot;
if (mModifiedElements.Get(element, &snapshot)) {
changeHint |= snapshot->ExplicitChangeHint();
}
@@ -135,16 +138,46 @@ ServoRestyleManager::RecreateStyleContex
// XXX This could not always work as expected: there are kinds of content
// with the first split and the last sharing style, but others not. We
// should handle those properly.
for (nsIFrame* f = primaryFrame; f;
f = GetNextContinuationWithSameStyle(f, oldStyleContext)) {
f->SetStyleContext(newContext);
}
+ // Update pseudo-elements state if appropriate.
+ if (aContent->IsElement()) {
+ Element* aElement = aContent->AsElement();
+ const static CSSPseudoElementType pseudosToRestyle[] = {
+ CSSPseudoElementType::before, CSSPseudoElementType::after,
+ };
+
+ for (CSSPseudoElementType pseudoType : pseudosToRestyle) {
+ nsIAtom* pseudoTag =
+ nsCSSPseudoElements::GetPseudoAtom(pseudoType);
+ if (nsIFrame* pseudoFrame =
+ FrameForPseudoElement(aElement, pseudoTag)) {
+ // TODO: we could maybe make this more performant via calling into
+ // Servo just once to know which pseudo-elements we've got to restyle?
+ RefPtr<nsStyleContext> pseudoContext =
+ aStyleSet->ProbePseudoElementStyle(aElement, pseudoType,
+ newContext);
+
+ // If pseudoContext is null here, it means the frame is going away, so
+ // our change hint computation should have already indicated we need
+ // to reframe.
+ MOZ_ASSERT_IF(!pseudoContext,
+ changeHint & nsChangeHint_ReconstructFrame);
+ if (pseudoContext) {
+ pseudoFrame->SetStyleContext(pseudoContext);
+ }
+ }
+ }
+ }
+
// TODO: There are other continuations we still haven't restyled, mostly
// pseudo-elements. We have to deal with those, and with anonymous boxes.
aContent->UnsetFlags(NODE_IS_DIRTY_FOR_SERVO);
}
if (aContent->HasDirtyDescendantsForServo()) {
MOZ_ASSERT(primaryFrame,
"Frame construction should be scheduled, and it takes the "
@@ -182,17 +215,47 @@ MarkChildrenAsDirtyForServo(nsIContent*
n->SetIsDirtyForServo();
}
if (hadChildren) {
aContent->SetHasDirtyDescendantsForServo();
}
}
-void
+/* static */ nsIFrame*
+ServoRestyleManager::FrameForPseudoElement(nsIContent* aContent,
+ nsIAtom* aPseudoTagOrNull)
+{
+ MOZ_ASSERT_IF(aPseudoTagOrNull, aContent->IsElement());
+ nsIFrame* primaryFrame = aContent->GetPrimaryFrame();
+
+ if (!aPseudoTagOrNull) {
+ return primaryFrame;
+ }
+
+ if (!primaryFrame) {
+ return nullptr;
+ }
+
+ // NOTE: we probably need to special-case display: contents here. Gecko's
+ // RestyleManager passes the primary frame of the parent instead.
+ if (aPseudoTagOrNull == nsCSSPseudoElements::before) {
+ return nsLayoutUtils::GetBeforeFrameForContent(primaryFrame, aContent);
+ }
+
+ if (aPseudoTagOrNull == nsCSSPseudoElements::after) {
+ return nsLayoutUtils::GetAfterFrameForContent(primaryFrame, aContent);
+ }
+
+ MOZ_CRASH("Unkown pseudo-element given to "
+ "ServoRestyleManager::FrameForPseudoElement");
+ return nullptr;
+}
+
+/* static */ void
ServoRestyleManager::NoteRestyleHint(Element* aElement, nsRestyleHint aHint)
{
const nsRestyleHint HANDLED_RESTYLE_HINTS = eRestyle_Self |
eRestyle_Subtree |
eRestyle_LaterSiblings |
eRestyle_SomeDescendants;
// NB: For Servo, at least for now, restyling and running selector-matching
// against the subtree is necessary as part of restyling the element, so
--- a/layout/base/ServoRestyleManager.h
+++ b/layout/base/ServoRestyleManager.h
@@ -72,16 +72,25 @@ public:
{
MOZ_ASSERT(SnapshotForElement(aElement)->HasAttrs());
}
nsresult ReparentStyleContext(nsIFrame* aFrame);
bool HasPendingRestyles() { return !mModifiedElements.IsEmpty(); }
+ /**
+ * Gets the appropriate frame given a content and a pseudo-element tag.
+ *
+ * Right now only supports a null tag, before or after. If the pseudo-element
+ * is not null, the content needs to be an element.
+ */
+ static nsIFrame* FrameForPseudoElement(nsIContent* aContent,
+ nsIAtom* aPseudoTagOrNull);
+
protected:
~ServoRestyleManager() {}
private:
ServoElementSnapshot* SnapshotForElement(Element* aElement);
/**
* The element-to-element snapshot table to compute restyle hints.
--- a/layout/style/ServoBindings.cpp
+++ b/layout/style/ServoBindings.cpp
@@ -9,28 +9,32 @@
#include "StyleStructContext.h"
#include "gfxFontFamilyList.h"
#include "nsAttrValueInlines.h"
#include "nsCSSRuleProcessor.h"
#include "nsContentUtils.h"
#include "nsDOMTokenList.h"
#include "nsIDOMNode.h"
#include "nsIDocument.h"
+#include "nsIFrame.h"
#include "nsINode.h"
#include "nsIPrincipal.h"
#include "nsNameSpaceManager.h"
#include "nsString.h"
#include "nsStyleStruct.h"
#include "nsStyleUtil.h"
#include "nsTArray.h"
#include "mozilla/EventStates.h"
#include "mozilla/ServoElementSnapshot.h"
+#include "mozilla/ServoRestyleManager.h"
#include "mozilla/dom/Element.h"
+using namespace mozilla;
+
uint32_t
Gecko_ChildrenCount(RawGeckoNode* aNode)
{
return aNode->GetChildCount();
}
bool
Gecko_NodeIsElement(RawGeckoNode* aNode)
@@ -180,25 +184,27 @@ Gecko_SetNodeFlags(RawGeckoNode* aNode,
void
Gecko_UnsetNodeFlags(RawGeckoNode* aNode, uint32_t aFlags)
{
aNode->UnsetFlags(aFlags);
}
nsStyleContext*
-Gecko_GetStyleContext(RawGeckoNode* aNode)
+Gecko_GetStyleContext(RawGeckoNode* aNode, nsIAtom* aPseudoTagOrNull)
{
MOZ_ASSERT(aNode->IsContent());
- nsIFrame* primaryFrame = aNode->AsContent()->GetPrimaryFrame();
- if (!primaryFrame) {
+ nsIFrame* relevantFrame =
+ ServoRestyleManager::FrameForPseudoElement(aNode->AsContent(),
+ aPseudoTagOrNull);
+ if (!relevantFrame) {
return nullptr;
}
- return primaryFrame->StyleContext();
+ return relevantFrame->StyleContext();
}
nsChangeHint
Gecko_CalcStyleDifference(nsStyleContext* aOldStyleContext,
ServoComputedValues* aComputedValues)
{
MOZ_ASSERT(aOldStyleContext);
MOZ_ASSERT(aComputedValues);
@@ -712,16 +718,22 @@ Gecko_ClearPODTArray(void* aArray, size_
reinterpret_cast<nsTArray_base<nsTArrayInfallibleAllocator,
nsTArray_CopyWithMemutils> *>(aArray);
base->template ShiftData<nsTArrayInfallibleAllocator>(0, base->Length(), 0,
aElementSize, aElementAlign);
}
void
+Gecko_ClearStyleContents(nsStyleContent* aContent)
+{
+ aContent->AllocateContents(0);
+}
+
+void
Gecko_EnsureImageLayersLength(nsStyleImageLayers* aLayers, size_t aLen)
{
aLayers->mLayers.EnsureLengthAtLeast(aLen);
}
void
Gecko_InitializeImageLayer(nsStyleImageLayers::Layer* aLayer,
nsStyleImageLayers::LayerType aLayerType)
@@ -781,17 +793,17 @@ void
Servo_DropNodeData(ServoNodeData* data)
{
MOZ_CRASH("stylo: shouldn't be calling Servo_DropNodeData in a "
"non-MOZ_STYLO build");
}
RawServoStyleSheet*
Servo_StylesheetFromUTF8Bytes(const uint8_t* bytes, uint32_t length,
- mozilla::css::SheetParsingMode mode,
+ css::SheetParsingMode mode,
const uint8_t* base_bytes, uint32_t base_length,
ThreadSafeURIHolder* base,
ThreadSafeURIHolder* referrer,
ThreadSafePrincipalHolder* principal)
{
MOZ_CRASH("stylo: shouldn't be calling Servo_StylesheetFromUTF8Bytes in a "
"non-MOZ_STYLO build");
}
--- a/layout/style/ServoBindings.h
+++ b/layout/style/ServoBindings.h
@@ -188,32 +188,37 @@ void Gecko_UnsetNodeFlags(RawGeckoNode*
// Incremental restyle.
// TODO: We would avoid a few ffi calls if we decide to make an API like the
// former CalcAndStoreStyleDifference, but that would effectively mean breaking
// some safety guarantees in the servo side.
//
// Also, we might want a ComputedValues to ComputedValues API for animations?
// Not if we do them in Gecko...
-nsStyleContext* Gecko_GetStyleContext(RawGeckoNode* node);
+nsStyleContext* Gecko_GetStyleContext(RawGeckoNode* node,
+ nsIAtom* aPseudoTagOrNull);
nsChangeHint Gecko_CalcStyleDifference(nsStyleContext* oldstyle,
ServoComputedValues* newstyle);
void Gecko_StoreStyleDifference(RawGeckoNode* node, nsChangeHint change);
// `array` must be an nsTArray
// If changing this signature, please update the
// friend function declaration in nsTArray.h
void Gecko_EnsureTArrayCapacity(void* array, size_t capacity, size_t elem_size);
// Same here, `array` must be an nsTArray<T>, for some T.
//
// Important note: Only valid for POD types, since destructors won't be run
// otherwise. This is ensured with rust traits for the relevant structs.
void Gecko_ClearPODTArray(void* array, size_t elem_size, size_t elem_align);
+// Clear the mContents field in nsStyleContent. This is needed to run the
+// destructors, otherwise we'd leak the images (though we still don't support
+// those), strings, and whatnot.
+void Gecko_ClearStyleContents(nsStyleContent* content);
void Gecko_EnsureImageLayersLength(nsStyleImageLayers* layers, size_t len);
void Gecko_InitializeImageLayer(nsStyleImageLayers::Layer* layer,
nsStyleImageLayers::LayerType layer_type);
// Clean up pointer-based coordinates
void Gecko_ResetStyleCoord(nsStyleUnit* unit, nsStyleUnion* value);
--- a/layout/style/ServoStyleSet.cpp
+++ b/layout/style/ServoStyleSet.cpp
@@ -109,18 +109,17 @@ ServoStyleSet::GetContext(already_AddRef
{
// XXXbholley: nsStyleSet does visited handling here.
// XXXbholley: Figure out the correct thing to pass here. Does this fixup
// duplicate something that servo already does?
bool skipFixup = false;
return NS_NewStyleContext(aParentContext, mPresContext, aPseudoTag,
- aPseudoType,
- Move(aComputedValues), skipFixup);
+ aPseudoType, Move(aComputedValues), skipFixup);
}
already_AddRefed<nsStyleContext>
ServoStyleSet::ResolveStyleFor(Element* aElement,
nsStyleContext* aParentContext,
TreeMatchContext& aTreeMatchContext)
{
// aTreeMatchContext is used to speed up selector matching,