Bug 1409672: Hook in the invalidator stuff. r?xidorn draft
authorEmilio Cobos Álvarez <emilio@crisal.io>
Tue, 16 Jan 2018 15:14:39 +0100
changeset 720918 7978192f79bf10d81c0d971c32f5586759915c47
parent 720917 1a2ea56c3051b9edc0f3c3491d80ef6bf17dbe83
child 720996 77bb2f29166c150cc823c02c03e20da91e47dada
child 721005 285fc05f28a00f4c8fad3d16c5b4af0f18707cf2
push id95683
push userbmo:emilio@crisal.io
push dateTue, 16 Jan 2018 14:29:53 +0000
reviewersxidorn
bugs1409672
milestone59.0a1
Bug 1409672: Hook in the invalidator stuff. r?xidorn MozReview-Commit-ID: EoSMrYPS7dl
dom/xbl/nsBindingManager.cpp
dom/xbl/nsBindingManager.h
layout/base/PresShell.cpp
layout/style/ServoBindingList.h
layout/style/ServoStyleSet.cpp
layout/style/ServoStyleSet.h
servo/components/style/invalidation/element/document_state.rs
servo/components/style/invalidation/element/invalidator.rs
servo/ports/geckolib/glue.rs
--- a/dom/xbl/nsBindingManager.cpp
+++ b/dom/xbl/nsBindingManager.cpp
@@ -1139,25 +1139,8 @@ nsBindingManager::FindNestedSingleInsert
     if (newParent == parent) {
       break;
     }
     parent = newParent;
   }
 
   return parent;
 }
-
-bool
-nsBindingManager::AnyBindingHasDocumentStateDependency(EventStates aStateMask)
-{
-  MOZ_ASSERT(mDocument->IsStyledByServo());
-
-  bool result = false;
-  EnumerateBoundContentBindings([&](nsXBLBinding* aBinding) {
-    ServoStyleSet* styleSet = aBinding->PrototypeBinding()->GetServoStyleSet();
-    if (styleSet && styleSet->HasDocumentStateDependency(aStateMask)) {
-      result = true;
-      return false;
-    }
-    return true;
-  });
-  return result;
-}
--- a/dom/xbl/nsBindingManager.h
+++ b/dom/xbl/nsBindingManager.h
@@ -168,17 +168,21 @@ public:
   // points and their insertion parents.
   void ClearInsertionPointsRecursively(nsIContent* aContent);
 
   // Called when the document is going away
   void DropDocumentReference();
 
   nsIContent* FindNestedSingleInsertionPoint(nsIContent* aContainer, bool* aMulti);
 
-  bool AnyBindingHasDocumentStateDependency(mozilla::EventStates aStateMask);
+  // Enumerate each bound content's bindings (including its base bindings)
+  // in mBoundContentSet. Return false from the callback to stop enumeration.
+  using BoundContentBindingCallback = std::function<bool (nsXBLBinding*)>;
+  bool EnumerateBoundContentBindings(
+    const BoundContentBindingCallback& aCallback) const;
 
 protected:
   nsIXPConnectWrappedJS* GetWrappedJS(nsIContent* aContent);
   nsresult SetWrappedJS(nsIContent* aContent, nsIXPConnectWrappedJS* aResult);
 
   // Called by ContentAppended and ContentInserted to handle a single child
   // insertion.  aChild must not be null.  aContainer may be null.
   // aAppend is true if this child is being appended, not inserted.
@@ -190,24 +194,17 @@ protected:
   void DoProcessAttachedQueue();
 
   // Post an event to process the attached queue.
   void PostProcessAttachedQueueEvent();
 
   // Call PostProcessAttachedQueueEvent() on a timer.
   static void PostPAQEventCallback(nsITimer* aTimer, void* aClosure);
 
-  // Enumerate each bound content's bindings (including its base bindings)
-  // in mBoundContentSet. Return false from the callback to stop enumeration.
-  using BoundContentBindingCallback = std::function<bool (nsXBLBinding*)>;
-  bool EnumerateBoundContentBindings(
-    const BoundContentBindingCallback& aCallback) const;
-
 // MEMBER VARIABLES
-protected:
   // A set of nsIContent that currently have a binding installed.
   nsAutoPtr<nsTHashtable<nsRefPtrHashKey<nsIContent> > > mBoundContentSet;
 
   // A mapping from nsIContent* to nsIXPWrappedJS* (an XPConnect
   // wrapper for JS objects).  For XBL bindings that implement XPIDL
   // interfaces, and that get referred to from C++, this table caches
   // the XPConnect wrapper for the binding.  By caching it, I control
   // its lifetime, and I prevent a re-wrap of the same script object
--- a/layout/base/PresShell.cpp
+++ b/layout/base/PresShell.cpp
@@ -4333,45 +4333,40 @@ PresShell::ContentStateChanged(nsIDocume
   if (mDidInitialize) {
     nsAutoCauseReflowNotifier crNotifier(this);
     mPresContext->RestyleManager()->ContentStateChanged(aContent, aStateMask);
     VERIFY_STYLE_TREE;
   }
 }
 
 void
-PresShell::DocumentStatesChanged(nsIDocument* aDocument,
-                                 EventStates aStateMask)
+PresShell::DocumentStatesChanged(nsIDocument* aDocument, EventStates aStateMask)
 {
   NS_PRECONDITION(!mIsDocumentGone, "Unexpected DocumentStatesChanged");
   NS_PRECONDITION(aDocument == mDocument, "Unexpected aDocument");
+  MOZ_ASSERT(!aStateMask.IsEmpty());
 
   if (mDidInitialize) {
-    Element* rootElement = aDocument->GetRootElement();
-    bool needRestyle = false;
     if (mStyleSet->IsServo()) {
-      needRestyle = rootElement &&
-        (mStyleSet->AsServo()->HasDocumentStateDependency(aStateMask) ||
-         aDocument->BindingManager()->
-           AnyBindingHasDocumentStateDependency(aStateMask));
-    } else {
-      needRestyle = mStyleSet->AsGecko()->
-        HasDocumentStateDependentStyle(rootElement, aStateMask);
-    }
-    if (needRestyle) {
-      mPresContext->RestyleManager()->PostRestyleEvent(rootElement,
-                                                       eRestyle_Subtree,
-                                                       nsChangeHint(0));
-      VERIFY_STYLE_TREE;
+      mStyleSet->AsServo()->InvalidateStyleForDocumentStateChanges(aStateMask);
+    } else if (Element* rootElement = aDocument->GetRootElement()) {
+      const bool needRestyle =
+        mStyleSet->AsGecko()->HasDocumentStateDependentStyle(
+          rootElement, aStateMask);
+      if (needRestyle) {
+        mPresContext->RestyleManager()->PostRestyleEvent(rootElement,
+                                                         eRestyle_Subtree,
+                                                         nsChangeHint(0));
+        VERIFY_STYLE_TREE;
+      }
     }
   }
 
   if (aStateMask.HasState(NS_DOCUMENT_STATE_WINDOW_INACTIVE)) {
-    nsIFrame* root = mFrameConstructor->GetRootFrame();
-    if (root) {
+    if (nsIFrame* root = mFrameConstructor->GetRootFrame()) {
       root->SchedulePaint();
     }
   }
 }
 
 void
 PresShell::AttributeWillChange(nsIDocument* aDocument,
                                Element*     aElement,
--- a/layout/style/ServoBindingList.h
+++ b/layout/style/ServoBindingList.h
@@ -35,16 +35,21 @@ SERVO_BINDING_FUNC(Servo_Element_GetPseu
                    ServoStyleContextStrong,
                    RawGeckoElementBorrowed node, size_t index)
 SERVO_BINDING_FUNC(Servo_Element_IsDisplayNone,
                    bool,
                    RawGeckoElementBorrowed element)
 SERVO_BINDING_FUNC(Servo_Element_IsPrimaryStyleReusedViaRuleNode,
                    bool,
                    RawGeckoElementBorrowed element)
+SERVO_BINDING_FUNC(Servo_InvalidateStyleForDocStateChanges,
+                   void,
+                   RawGeckoElementBorrowed root,
+                   const nsTArray<RawServoStyleSetBorrowed>* sets,
+                   uint64_t aStatesChanged)
 
 // Styleset and Stylesheet management
 SERVO_BINDING_FUNC(Servo_StyleSheet_FromUTF8Bytes,
                    RawServoStyleSheetContentsStrong,
                    mozilla::css::Loader* loader,
                    mozilla::ServoStyleSheet* gecko_stylesheet,
                    const uint8_t* data,
                    size_t data_len,
--- a/layout/style/ServoStyleSet.cpp
+++ b/layout/style/ServoStyleSet.cpp
@@ -28,16 +28,17 @@
 #include "nsHTMLStyleSheet.h"
 #include "nsIAnonymousContentCreator.h"
 #include "nsIDocumentInlines.h"
 #include "nsMediaFeatures.h"
 #include "nsPrintfCString.h"
 #include "nsSMILAnimationController.h"
 #include "nsStyleContext.h"
 #include "nsStyleSet.h"
+#include "nsXBLPrototypeBinding.h"
 #include "gfxUserFontSet.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
 ServoStyleSet* ServoStyleSet::sInServoTraversal = nullptr;
 
 #ifdef DEBUG
@@ -219,16 +220,52 @@ ServoStyleSet::SetPresContext(nsPresCont
   if (rulesChanged != OriginFlags(0)) {
     MarkOriginsDirty(rulesChanged);
     return true;
   }
 
   return false;
 }
 
+void
+ServoStyleSet::InvalidateStyleForDocumentStateChanges(EventStates aStatesChanged)
+{
+  MOZ_ASSERT(IsMaster());
+  MOZ_ASSERT(mDocument);
+  MOZ_ASSERT(!aStatesChanged.IsEmpty());
+
+  nsPresContext* pc = GetPresContext();
+  if (!pc) {
+    return;
+  }
+
+  Element* root = mDocument->GetRootElement();
+  if (!root) {
+    return;
+  }
+
+  // TODO(emilio): It may be nicer to just invalidate stuff in a given subtree
+  // for XBL sheets / shadow DOM. Consider just enumerating bound content
+  // instead and run invalidation individually, passing mRawSet for the UA /
+  // User sheets.
+  AutoTArray<RawServoStyleSetBorrowed, 20> styleSets;
+  styleSets.AppendElement(mRawSet.get());
+  // FIXME(emilio): When bug 1425759 is fixed we need to enumerate ShadowRoots
+  // too.
+  mDocument->BindingManager()->EnumerateBoundContentBindings([&](nsXBLBinding* aBinding) {
+    if (ServoStyleSet* set = aBinding->PrototypeBinding()->GetServoStyleSet()) {
+      styleSets.AppendElement(set->RawSet());
+    }
+    return true;
+  });
+
+  Servo_InvalidateStyleForDocStateChanges(
+    root, &styleSets, aStatesChanged.ServoValue());
+}
+
 nsRestyleHint
 ServoStyleSet::MediumFeaturesChanged(bool aViewportChanged)
 {
   bool viewportUnitsUsed = false;
   bool rulesChanged = MediumFeaturesChangedRules(&viewportUnitsUsed);
 
   if (nsPresContext* pc = GetPresContext()) {
     if (mDocument->BindingManager()->MediumFeaturesChanged(pc)) {
--- a/layout/style/ServoStyleSet.h
+++ b/layout/style/ServoStyleSet.h
@@ -141,16 +141,19 @@ public:
   void RuleAdded(ServoStyleSheet&, css::Rule&);
   void RuleRemoved(ServoStyleSheet&, css::Rule&);
   void RuleChanged(ServoStyleSheet& aSheet, css::Rule* aRule);
 
   // All the relevant changes are handled in RuleAdded / RuleRemoved / etc, and
   // the relevant AppendSheet / RemoveSheet...
   void RecordStyleSheetChange(ServoStyleSheet*, StyleSheet::ChangeType) {}
 
+  // Runs style invalidation due to document state changes.
+  void InvalidateStyleForDocumentStateChanges(EventStates aStatesChanged);
+
   void RecordShadowStyleChange(dom::ShadowRoot* aShadowRoot) {
     // FIXME(emilio): When we properly support shadow dom we'll need to do
     // better.
     MarkOriginsDirty(OriginFlags::All);
   }
 
   bool StyleSheetsHaveChanged() const
   {
--- a/servo/components/style/invalidation/element/document_state.rs
+++ b/servo/components/style/invalidation/element/document_state.rs
@@ -25,30 +25,30 @@ impl Default for InvalidationMatchingDat
         Self {
             document_state: DocumentState::empty(),
         }
     }
 }
 
 /// An invalidation processor for style changes due to state and attribute
 /// changes.
-pub struct DocumentStateInvalidationProcessor<'a, E: TElement> {
+pub struct DocumentStateInvalidationProcessor<'a, E: TElement, I> {
     // TODO(emilio): We might want to just run everything for every possible
     // binding along with the document data, or just apply the XBL stuff to the
     // bound subtrees.
-    rules: &'a CascadeData,
+    rules: I,
     matching_context: MatchingContext<'a, E::Impl>,
     document_states_changed: DocumentState,
 }
 
-impl<'a, E: TElement> DocumentStateInvalidationProcessor<'a, E> {
+impl<'a, E: TElement, I> DocumentStateInvalidationProcessor<'a, E, I> {
     /// Creates a new DocumentStateInvalidationProcessor.
     #[inline]
     pub fn new(
-        rules: &'a CascadeData,
+        rules: I,
         document_states_changed: DocumentState,
         quirks_mode: QuirksMode,
     ) -> Self {
         let mut matching_context = MatchingContext::new_for_visited(
             MatchingMode::Normal,
             None,
             None,
             VisitedHandlingMode::AllLinksVisitedAndUnvisited,
@@ -58,32 +58,37 @@ impl<'a, E: TElement> DocumentStateInval
         matching_context.extra_data = InvalidationMatchingData {
             document_state: document_states_changed,
         };
 
         Self { rules, document_states_changed, matching_context }
     }
 }
 
-impl<'a, E: TElement> InvalidationProcessor<'a, E> for DocumentStateInvalidationProcessor<'a, E> {
+impl<'a, E, I> InvalidationProcessor<'a, E> for DocumentStateInvalidationProcessor<'a, E, I>
+where
+    E: TElement,
+    I: Iterator<Item = &'a CascadeData>,
+{
     fn collect_invalidations(
         &mut self,
         _element: E,
         self_invalidations: &mut InvalidationVector<'a>,
         _descendant_invalidations: &mut DescendantInvalidationLists<'a>,
         _sibling_invalidations: &mut InvalidationVector<'a>,
     ) -> bool {
-        let map = self.rules.invalidation_map();
+        for cascade_data in &mut self.rules {
+            let map = cascade_data.invalidation_map();
+            for dependency in &map.document_state_selectors {
+                if !dependency.state.intersects(self.document_states_changed) {
+                    continue;
+                }
 
-        for dependency in &map.document_state_selectors {
-            if !dependency.state.intersects(self.document_states_changed) {
-                continue;
+                self_invalidations.push(Invalidation::new(&dependency.selector, 0));
             }
-
-            self_invalidations.push(Invalidation::new(&dependency.selector, 0));
         }
 
         false
     }
 
     fn matching_context(&mut self) -> &mut MatchingContext<'a, E::Impl> {
         &mut self.matching_context
     }
--- a/servo/components/style/invalidation/element/invalidator.rs
+++ b/servo/components/style/invalidation/element/invalidator.rs
@@ -529,16 +529,30 @@ where
 
         if self.processor.light_tree_only() {
             let node = self.element.as_node();
             return self.invalidate_dom_descendants_of(node, invalidations);
         }
 
         let mut any_descendant = false;
 
+        // NOTE(emilio): This should not not be needed for Shadow DOM for normal
+        // element state / attribute invalidations (it's needed for XBL though,
+        // due to the weird way the anon content there works (it doesn't block
+        // combinators).
+        //
+        // However, it's needed as of right now for document state invalidation,
+        // were we rely on iterating every element that ends up in the composed
+        // doc.
+        //
+        // Also, we could avoid having that special-case for document state
+        // invalidations if we invalidate for document state changes per
+        // subtree, though that's kind of annoying because we need to invalidate
+        // the shadow host subtree (to handle :host and ::slotted), and the
+        // actual shadow tree (to handle all other rules in the ShadowRoot).
         if let Some(anon_content) = self.element.xbl_binding_anonymous_content() {
             any_descendant |=
                 self.invalidate_dom_descendants_of(anon_content, invalidations);
         }
 
         if let Some(before) = self.element.before_pseudo_element() {
             any_descendant |=
                 self.invalidate_pseudo_element_or_nac(before, invalidations);
--- a/servo/ports/geckolib/glue.rs
+++ b/servo/ports/geckolib/glue.rs
@@ -4,16 +4,17 @@
 
 use cssparser::{ParseErrorKind, Parser, ParserInput};
 use cssparser::ToCss as ParserToCss;
 use env_logger::LogBuilder;
 use malloc_size_of::MallocSizeOfOps;
 use selectors::{Element, NthIndexCache};
 use selectors::matching::{MatchingContext, MatchingMode, matches_selector};
 use servo_arc::{Arc, ArcBorrow, RawOffsetArc};
+use smallvec::SmallVec;
 use std::cell::RefCell;
 use std::env;
 use std::fmt::Write;
 use std::iter;
 use std::mem;
 use std::ptr;
 use style::applicable_declarations::ApplicableDeclarationBlock;
 use style::context::{CascadeInputs, QuirksMode, SharedStyleContext, StyleContext};
@@ -1769,17 +1770,16 @@ pub unsafe extern "C" fn Servo_SelectorL
 
 #[no_mangle]
 pub unsafe extern "C" fn Servo_SelectorList_QueryAll(
     node: RawGeckoNodeBorrowed,
     selectors: RawServoSelectorListBorrowed,
     content_list: *mut structs::nsSimpleContentList,
     may_use_invalidation: bool,
 ) {
-    use smallvec::SmallVec;
     use std::borrow::Borrow;
     use style::dom_apis::{self, MayUseInvalidation, QueryAll};
 
     let node = GeckoNode(node);
     let selectors = ::selectors::SelectorList::from_ffi(selectors).borrow();
     let mut result = SmallVec::new();
 
     let may_use_invalidation =
@@ -2388,17 +2388,16 @@ pub extern "C" fn Servo_ComputedValues_E
     second: ServoComputedDataBorrowed
 ) -> bool {
     first.custom_properties == second.custom_properties
 }
 
 #[no_mangle]
 pub extern "C" fn Servo_ComputedValues_GetStyleRuleList(values: ServoStyleContextBorrowed,
                                                         rules: RawGeckoServoStyleRuleListBorrowedMut) {
-    use smallvec::SmallVec;
 
     let rule_node = match values.rules {
         Some(ref r) => r,
         None => return,
     };
 
     let mut result = SmallVec::<[_; 10]>::new();
     for node in rule_node.self_and_ancestors() {
@@ -4926,16 +4925,50 @@ pub extern "C" fn Servo_ParseCounterStyl
     let mut parser = Parser::new(&mut input);
     match parser.parse_entirely(counter_style::parse_counter_style_name_definition) {
         Ok(name) => name.0.into_addrefed(),
         Err(_) => ptr::null_mut(),
     }
 }
 
 #[no_mangle]
+pub unsafe extern "C" fn Servo_InvalidateStyleForDocStateChanges(
+    root: RawGeckoElementBorrowed,
+    raw_style_sets: *const nsTArray<RawServoStyleSetBorrowed>,
+    states_changed: u64,
+) {
+    use style::invalidation::element::document_state::DocumentStateInvalidationProcessor;
+    use style::invalidation::element::invalidator::TreeStyleInvalidator;
+
+    let mut borrows = SmallVec::<[_; 20]>::with_capacity((*raw_style_sets).len());
+    for style_set in &**raw_style_sets {
+        borrows.push(PerDocumentStyleData::from_ffi(*style_set).borrow());
+    }
+    let root = GeckoElement(root);
+    let mut processor = DocumentStateInvalidationProcessor::new(
+        borrows.iter().flat_map(|b| b.stylist.iter_origins().map(|(data, _origin)| data)),
+        DocumentState::from_bits_truncate(states_changed),
+        root.as_node().owner_doc().quirks_mode(),
+    );
+
+    let result = TreeStyleInvalidator::new(
+        root,
+        /* stack_limit_checker = */ None,
+        &mut processor,
+    ).invalidate();
+
+    debug_assert!(!result.has_invalidated_siblings(), "How in the world?");
+    if result.has_invalidated_descendants() {
+        bindings::Gecko_NoteDirtySubtreeForInvalidation(root.0);
+    } else if result.has_invalidated_self() {
+        bindings::Gecko_NoteDirtyElement(root.0);
+    }
+}
+
+#[no_mangle]
 pub extern "C" fn Servo_ParseCounterStyleDescriptor(
     descriptor: nsCSSCounterDesc,
     value: *const nsACString,
     raw_extra_data: *mut URLExtraData,
     result: *mut nsCSSValue,
 ) -> bool {
     let value = unsafe { value.as_ref().unwrap().as_str_unchecked() };
     let url_data = unsafe {