Bug 1292618: Support basic pseudo-element restyling. r=heycam
authorEmilio Cobos Álvarez <ecoal95@gmail.com>
Thu, 11 Aug 2016 19:01:52 -0700
changeset 351777 1976d34e92f8c9c7fb9a336db9377e18725f2b4f
parent 351776 70b776aa432ecffa3b29880de7d2c4bf29500461
child 351778 0830c52ff6c7eb449a47620880119884dba25f27
push id6570
push userraliiev@mozilla.com
push dateMon, 14 Nov 2016 12:26:13 +0000
treeherdermozilla-beta@f455459b2ae5 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersheycam
bugs1292618
milestone51.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1292618: Support basic pseudo-element restyling. r=heycam :before and :after only, for now. MozReview-Commit-ID: 9hLFvVhqIrN
layout/base/ServoRestyleManager.cpp
layout/base/ServoRestyleManager.h
layout/style/ServoBindings.cpp
layout/style/ServoBindings.h
layout/style/ServoStyleSet.cpp
--- 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 =
       Servo_GetComputedValues(aContent).Consume();
     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;
+
 #define IMPL_STRONG_REF_TYPE(name, T)           \
   already_AddRefed<T> name::Consume() {         \
     RefPtr<T> result = dont_AddRef(mPtr);       \
     mPtr = nullptr;                             \
     return result.forget();                     \
   };
 
 
@@ -191,25 +195,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,
                           ServoComputedValuesBorrowed aComputedValues)
 {
   MOZ_ASSERT(aOldStyleContext);
   MOZ_ASSERT(aComputedValues);
@@ -723,16 +729,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)
--- a/layout/style/ServoBindings.h
+++ b/layout/style/ServoBindings.h
@@ -209,32 +209,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,
                                        ServoComputedValuesBorrowed 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,