Bug 1286445: stylo: Support restyles of non-pseudo content on state change. r=heycam draft
authorEmilio Cobos Álvarez <ecoal95@gmail.com>
Wed, 13 Jul 2016 13:42:47 -0700
changeset 388417 9c38e26a45cb525c64ed8faa3c2adf0bb5387e9e
parent 387919 5a9c26f8bb9d599e80c92f6a7f30ad91bd54a854
child 388418 780c3eca880f911ab46bc573e5addcfe2936169a
push id23164
push userbmo:ealvarez@mozilla.com
push dateFri, 15 Jul 2016 18:56:00 +0000
reviewersheycam
bugs1286445
milestone50.0a1
Bug 1286445: stylo: Support restyles of non-pseudo content on state change. r=heycam This includes, for example :hover. Also removes the call to IsStyledByServo() in the document constructor, it's not only unnecessary, but also we call UpdateStyleBackendType() too early. MozReview-Commit-ID: 4YfCdmLoSxu
dom/base/nsDocument.cpp
dom/xbl/nsXBLBinding.cpp
layout/base/ServoRestyleManager.cpp
layout/base/ServoRestyleManager.h
layout/base/nsCSSFrameConstructor.cpp
layout/base/nsRefreshDriver.h
layout/style/ServoStyleSet.cpp
layout/style/ServoStyleSet.h
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -1454,19 +1454,16 @@ nsIDocument::nsIDocument()
     mGetUserFontSetCalled(false),
     mPostedFlushUserFontSet(false),
     mPartID(0),
     mDidFireDOMContentLoaded(true),
     mHasScrollLinkedEffect(false),
     mUserHasInteracted(false)
 {
   SetIsDocument();
-  if (IsStyledByServo()) {
-    SetFlags(NODE_IS_DIRTY_FOR_SERVO | NODE_HAS_DIRTY_DESCENDANTS_FOR_SERVO);
-  }
 
   PR_INIT_CLIST(&mDOMMediaQueryLists);
 }
 
 // NOTE! nsDocument::operator new() zeroes out all members, so don't
 // bother initializing members to 0.
 
 nsDocument::nsDocument(const char* aContentType)
--- a/dom/xbl/nsXBLBinding.cpp
+++ b/dom/xbl/nsXBLBinding.cpp
@@ -235,17 +235,17 @@ nsXBLBinding::InstallAnonymousContent(ns
     // an element is added to a XUL document), we need to notify the
     // XUL document using its special API.
     nsCOMPtr<nsIXULDocument> xuldoc(do_QueryInterface(doc));
     if (xuldoc)
       xuldoc->AddSubtreeToDocument(child);
 #endif
 
     if (servoStyleSet) {
-      servoStyleSet->RestyleSubtree(child);
+      servoStyleSet->RestyleSubtree(child, /* aForce = */ true);
     }
   }
 }
 
 void
 nsXBLBinding::UninstallAnonymousContent(nsIDocument* aDocument,
                                         nsIContent* aAnonParent)
 {
--- a/layout/base/ServoRestyleManager.cpp
+++ b/layout/base/ServoRestyleManager.cpp
@@ -11,39 +11,63 @@ using namespace mozilla::dom;
 
 namespace mozilla {
 
 ServoRestyleManager::ServoRestyleManager(nsPresContext* aPresContext)
   : RestyleManagerBase(aPresContext)
 {
 }
 
-void
-ServoRestyleManager::Disconnect()
+/* static */ void
+ServoRestyleManager::DirtyTree(nsIContent* aContent)
 {
-  NS_ERROR("stylo: ServoRestyleManager::Disconnect not implemented");
+  if (aContent->IsDirtyForServo()) {
+    return;
+  }
+
+  aContent->SetIsDirtyForServo();
+
+  FlattenedChildIterator it(aContent);
+
+  nsIContent* n = it.GetNextChild();
+  bool hadChildren = bool(n);
+  for ( ; n; n = it.GetNextChild()) {
+    DirtyTree(n);
+  }
+
+  if (hadChildren) {
+    aContent->SetHasDirtyDescendantsForServo();
+  }
 }
 
 void
 ServoRestyleManager::PostRestyleEvent(Element* aElement,
                                       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));
   }
 
-  aElement->SetIsDirtyForServo();
+  // Propagate the IS_DIRTY flag down the tree.
+  DirtyTree(aElement);
+
+  // 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();
@@ -64,20 +88,69 @@ ServoRestyleManager::RebuildAllStyleData
 
 void
 ServoRestyleManager::PostRebuildAllStyleDataEvent(nsChangeHint aExtraHint,
                                                   nsRestyleHint aRestyleHint)
 {
   MOZ_CRASH("stylo: ServoRestyleManager::PostRebuildAllStyleDataEvent not implemented");
 }
 
+/* static */ void
+ServoRestyleManager::RecreateStyleContexts(nsIContent* aContent,
+                                           nsStyleContext* aParentContext,
+                                           ServoStyleSet* aStyleSet)
+{
+  if (!(aContent->IsDirtyForServo() || aContent->HasDirtyDescendantsForServo())) {
+    return;
+  }
+
+  nsIFrame* primaryFrame = aContent->GetPrimaryFrame();
+
+  // TODO: AFAIK this can happen when we have, let's say, display: none. Here we
+  // should trigger frame construction if the element is actually dirty (I
+  // guess), but we'd better do that once we have all the restyle hints thing
+  // figured out.
+  if (!primaryFrame) {
+    return;
+  }
+
+  RefPtr<ServoComputedValues> computedValues =
+    dont_AddRef(Servo_GetComputedValues(aContent));
+
+  // TODO: Figure out what pseudos does this content have, and do the proper
+  // thing with them.
+  RefPtr<nsStyleContext> context =
+    aStyleSet->GetContext(computedValues.forget(),
+                          aParentContext,
+                          nullptr,
+                          CSSPseudoElementType::NotPseudo);
+
+  // TODO: Compare old and new styles to generate restyle change hints, and
+  // process them.
+  primaryFrame->SetStyleContext(context.get());
+
+  FlattenedChildIterator it(aContent);
+  for (nsIContent* n = it.GetNextChild(); n; n = it.GetNextChild()) {
+    RecreateStyleContexts(n, context.get(), aStyleSet);
+  }
+}
+
 void
 ServoRestyleManager::ProcessPendingRestyles()
 {
-  // XXXheycam Do nothing for now.
+  ServoStyleSet* styleSet = StyleSet();
+
+  nsIDocument* doc = PresContext()->Document();
+  Element* root = doc->GetRootElement();
+
+  if (root) {
+    styleSet->RestyleSubtree(root, /* aForce = */ false);
+    RecreateStyleContexts(root, nullptr, styleSet);
+  }
+
   IncrementRestyleGeneration();
 }
 
 void
 ServoRestyleManager::RestyleForInsertOrChange(Element* aContainer,
                                               nsIContent* aChild)
 {
   NS_ERROR("stylo: ServoRestyleManager::RestyleForInsertOrChange not implemented");
--- a/layout/base/ServoRestyleManager.h
+++ b/layout/base/ServoRestyleManager.h
@@ -26,22 +26,22 @@ class nsIFrame;
 
 namespace mozilla {
 
 /**
  * Restyle manager for a Servo-backed style system.
  */
 class ServoRestyleManager : public RestyleManagerBase
 {
+  friend class ServoStyleSet;
 public:
   NS_INLINE_DECL_REFCOUNTING(ServoRestyleManager)
 
   explicit ServoRestyleManager(nsPresContext* aPresContext);
 
-  void Disconnect();
   void PostRestyleEvent(dom::Element* aElement,
                         nsRestyleHint aRestyleHint,
                         nsChangeHint aMinChangeHint);
   void PostRestyleEventForLazyConstruction();
   void RebuildAllStyleData(nsChangeHint aExtraHint,
                            nsRestyleHint aRestyleHint);
   void PostRebuildAllStyleDataEvent(nsChangeHint aExtraHint,
                                     nsRestyleHint aRestyleHint);
@@ -67,16 +67,34 @@ public:
                         const nsAttrValue* aOldValue);
   nsresult ReparentStyleContext(nsIFrame* aFrame);
   bool HasPendingRestyles();
 
 protected:
   ~ServoRestyleManager() {}
 
 private:
+  /**
+   * 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);
+
   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/base/nsCSSFrameConstructor.cpp
+++ b/layout/base/nsCSSFrameConstructor.cpp
@@ -4257,17 +4257,17 @@ nsCSSFrameConstructor::GetAnonymousConte
       content->UnbindFromTree();
       return rv;
     }
   }
 
   if (ServoStyleSet* styleSet = mPresShell->StyleSet()->GetAsServo()) {
     // Eagerly compute styles for the anonymous content tree.
     for (auto& info : aContent) {
-      styleSet->RestyleSubtree(info.mContent);
+      styleSet->RestyleSubtree(info.mContent, /* aForce = */ true);
     }
   }
 
   return NS_OK;
 }
 
 static
 bool IsXULDisplayType(const nsStyleDisplay* aDisplay)
--- a/layout/base/nsRefreshDriver.h
+++ b/layout/base/nsRefreshDriver.h
@@ -150,17 +150,17 @@ public:
   bool AddImageRequest(imgIRequest* aRequest);
   void RemoveImageRequest(imgIRequest* aRequest);
 
   /**
    * Add / remove presshells that we should flush style and layout on
    */
   bool AddStyleFlushObserver(nsIPresShell* aShell) {
     NS_ASSERTION(!mStyleFlushObservers.Contains(aShell),
-		 "Double-adding style flush observer");
+                 "Double-adding style flush observer");
     // We only get the cause for the first observer each frame because capturing
     // a stack is expensive. This is still useful if (1) you're trying to remove
     // all flushes for a particial frame or (2) the costly flush is triggered
     // near the call site where the first observer is triggered.
     if (!mStyleCause) {
       mStyleCause = profiler_get_backtrace();
     }
     bool appended = mStyleFlushObservers.AppendElement(aShell) != nullptr;
@@ -168,17 +168,17 @@ public:
 
     return appended;
   }
   void RemoveStyleFlushObserver(nsIPresShell* aShell) {
     mStyleFlushObservers.RemoveElement(aShell);
   }
   bool AddLayoutFlushObserver(nsIPresShell* aShell) {
     NS_ASSERTION(!IsLayoutFlushObserver(aShell),
-		 "Double-adding layout flush observer");
+                 "Double-adding layout flush observer");
     // We only get the cause for the first observer each frame because capturing
     // a stack is expensive. This is still useful if (1) you're trying to remove
     // all flushes for a particial frame or (2) the costly flush is triggered
     // near the call site where the first observer is triggered.
     if (!mReflowCause) {
       mReflowCause = profiler_get_backtrace();
     }
     bool appended = mLayoutFlushObservers.AppendElement(aShell) != nullptr;
@@ -188,17 +188,17 @@ public:
   void RemoveLayoutFlushObserver(nsIPresShell* aShell) {
     mLayoutFlushObservers.RemoveElement(aShell);
   }
   bool IsLayoutFlushObserver(nsIPresShell* aShell) {
     return mLayoutFlushObservers.Contains(aShell);
   }
   bool AddPresShellToInvalidateIfHidden(nsIPresShell* aShell) {
     NS_ASSERTION(!mPresShellsToInvalidateIfHidden.Contains(aShell),
-		 "Double-adding style flush observer");
+                 "Double-adding style flush observer");
     bool appended = mPresShellsToInvalidateIfHidden.AppendElement(aShell) != nullptr;
     EnsureTimerStarted();
     return appended;
   }
   void RemovePresShellToInvalidateIfHidden(nsIPresShell* aShell) {
     mPresShellsToInvalidateIfHidden.RemoveElement(aShell);
   }
 
@@ -283,17 +283,17 @@ public:
    */
   static void PVsyncActorCreated(mozilla::layout::VsyncChild* aVsyncChild);
 
 #ifdef DEBUG
   /**
    * Check whether the given observer is an observer for the given flush type
    */
   bool IsRefreshObserver(nsARefreshObserver *aObserver,
-			   mozFlushType aFlushType);
+                         mozFlushType aFlushType);
 #endif
 
   /**
    * Default interval the refresh driver uses, in ms.
    */
   static int32_t DefaultInterval();
 
   bool IsInRefresh() { return mInRefresh; }
--- a/layout/style/ServoStyleSet.cpp
+++ b/layout/style/ServoStyleSet.cpp
@@ -1,16 +1,17 @@
 /* -*- 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/ServoStyleSet.h"
 
+#include "mozilla/ServoRestyleManager.h"
 #include "nsCSSAnonBoxes.h"
 #include "nsCSSPseudoElements.h"
 #include "nsIDocumentInlines.h"
 #include "nsStyleContext.h"
 #include "nsStyleSet.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
@@ -374,12 +375,17 @@ ServoStyleSet::HasStateDependentStyle(do
                                       CSSPseudoElementType aPseudoType,
                                      dom::Element* aPseudoElement,
                                      EventStates aStateMask)
 {
   MOZ_CRASH("stylo: not implemented");
 }
 
 void
-ServoStyleSet::RestyleSubtree(nsINode* aNode)
+ServoStyleSet::RestyleSubtree(nsINode* aNode, bool aForce)
 {
+  if (aForce) {
+    MOZ_ASSERT(aNode->IsContent());
+    ServoRestyleManager::DirtyTree(aNode->AsContent());
+  }
+
   Servo_RestyleSubtree(aNode, mRawSet.get());
 }
--- a/layout/style/ServoStyleSet.h
+++ b/layout/style/ServoStyleSet.h
@@ -19,31 +19,33 @@
 #include "nsIAtom.h"
 #include "nsTArray.h"
 
 namespace mozilla {
 namespace dom {
 class Element;
 } // namespace dom
 class CSSStyleSheet;
+class ServoRestyleManager;
 class ServoStyleSheet;
 } // namespace mozilla
 class nsIDocument;
 class nsStyleContext;
 class nsPresContext;
 struct TreeMatchContext;
 
 namespace mozilla {
 
 /**
  * The set of style sheets that apply to a document, backed by a Servo
  * Stylist.  A ServoStyleSet contains ServoStyleSheets.
  */
 class ServoStyleSet
 {
+  friend class ServoRestyleManager;
 public:
   ServoStyleSet();
 
   void Init(nsPresContext* aPresContext);
   void BeginShutdown();
   void Shutdown();
 
   bool GetAuthorStyleDisabled() const;
@@ -111,17 +113,23 @@ public:
   // Test if style is dependent on content state
   nsRestyleHint HasStateDependentStyle(dom::Element* aElement,
                                        EventStates aStateMask);
   nsRestyleHint HasStateDependentStyle(dom::Element* aElement,
                                        mozilla::CSSPseudoElementType aPseudoType,
                                        dom::Element* aPseudoElement,
                                        EventStates aStateMask);
 
-  void RestyleSubtree(nsINode* aNode);
+  /**
+   * 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:
   already_AddRefed<nsStyleContext> GetContext(already_AddRefed<ServoComputedValues>,
                                               nsStyleContext* aParentContext,
                                               nsIAtom* aPseudoTag,
                                               CSSPseudoElementType aPseudoType);
 
   already_AddRefed<nsStyleContext> GetContext(nsIContent* aContent,