Bug 1292618: Support basic pseudo-element restyling. r?heycam draft
authorEmilio Cobos Álvarez <ecoal95@gmail.com>
Thu, 11 Aug 2016 19:01:52 -0700
changeset 401336 05ab44ec4ca86b857269000dd37b86c2317f93a7
parent 401332 76fa26b85782b6fc79bd5971c2435025937a9a57
child 401337 408a73703e078035d891efce901a397ffd13925b
push id26436
push userbmo:ealvarez@mozilla.com
push dateTue, 16 Aug 2016 21:55:11 +0000
reviewersheycam
bugs1292618
milestone51.0a1
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 =
       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,