style: Add ability to clear and rebuild individual origins. draft
authorCameron McCormack <cam@mcc.id.au>
Sat, 12 Aug 2017 17:15:44 +0800
changeset 645506 cb1ed81739efa1579cd80e82ff0676abe227c7e6
parent 645505 11512f2d40b7e2e7a2ed5496f43f4842ce9cabda
child 645507 93cbf10578e9ac3a436604ae4f7bc82a5b3adc9f
push id73769
push userbmo:cam@mcc.id.au
push dateSun, 13 Aug 2017 04:04:30 +0000
milestone57.0a1
style: Add ability to clear and rebuild individual origins. MozReview-Commit-ID: 2msIfteoA9Q
servo/components/style/gecko/data.rs
servo/components/style/stylist.rs
--- a/servo/components/style/gecko/data.rs
+++ b/servo/components/style/gecko/data.rs
@@ -12,17 +12,17 @@ use gecko_bindings::structs::RawGeckoPre
 use gecko_bindings::structs::nsIDocument;
 use gecko_bindings::sugar::ownership::{HasArcFFI, HasBoxFFI, HasFFI, HasSimpleFFI};
 use invalidation::media_queries::{MediaListKey, ToMediaListKey};
 use media_queries::{Device, MediaList};
 use properties::ComputedValues;
 use servo_arc::Arc;
 use shared_lock::{Locked, StylesheetGuards, SharedRwLockReadGuard};
 use stylesheet_set::StylesheetSet;
-use stylesheets::{PerOrigin, StylesheetContents, StylesheetInDocument};
+use stylesheets::{Origin, PerOrigin, StylesheetContents, StylesheetInDocument};
 use stylist::{ExtraStyleData, Stylist};
 
 /// Little wrapper to a Gecko style sheet.
 #[derive(PartialEq, Eq, Debug)]
 pub struct GeckoStyleSheet(*const ServoStyleSheet);
 
 impl ToMediaListKey for ::gecko::data::GeckoStyleSheet {
     fn to_media_list_key(&self) -> MediaListKey {
@@ -188,16 +188,21 @@ impl PerDocumentStyleDataImpl {
     }
 
     /// Clear the stylist.  This will be a no-op if the stylist is
     /// already cleared; the stylist handles that.
     pub fn clear_stylist(&mut self) {
         self.stylist.clear();
     }
 
+    /// Clear the stylist's data for the specified origin.
+    pub fn clear_stylist_origin(&mut self, origin: &Origin) {
+        self.stylist.clear_origin(origin);
+    }
+
     /// Returns whether visited links are enabled.
     fn visited_links_enabled(&self) -> bool {
         unsafe { bindings::Gecko_AreVisitedLinksEnabled() }
     }
     /// Returns whether visited styles are enabled.
     pub fn visited_styles_enabled(&self) -> bool {
         self.visited_links_enabled() && !self.is_private_browsing_enabled()
     }
--- a/servo/components/style/stylist.rs
+++ b/servo/components/style/stylist.rs
@@ -199,24 +199,41 @@ impl Stylist {
         self.cascade_data.clear();
         self.precomputed_pseudo_element_decls.clear();
         self.viewport_constraints = None;
 
         // XXX(heycam) Why do this, if we are preserving the Device?
         self.is_device_dirty = true;
     }
 
+    /// Clear the stylist's state for the specified origin.
+    pub fn clear_origin(&mut self, origin: &Origin) {
+        self.cascade_data.borrow_mut_for_origin(origin).clear();
+
+        if *origin == Origin::UserAgent {
+            // We only collect these declarations from UA sheets.
+            self.precomputed_pseudo_element_decls.clear();
+        }
+
+        // The stored `ViewportConstraints` contains data from rules across
+        // all origins.
+        self.viewport_constraints = None;
+
+        // XXX(heycam) Why do this, if we are preserving the Device?
+        self.is_device_dirty = true;
+    }
+
     /// Returns whether any origin's `CascadeData` has been cleared.
     fn any_origin_cleared(&self) -> bool {
         self.cascade_data
             .iter_origins()
             .any(|(d, _)| d.is_cleared)
     }
 
-    /// rebuild the stylist for the given document stylesheets, and optionally
+    /// Rebuild the stylist for the given document stylesheets, and optionally
     /// with a set of user agent stylesheets.
     ///
     /// This method resets all the style data each time the stylesheets change
     /// (which is indicated by the `stylesheets_changed` parameter), or the
     /// device is dirty, which means we need to re-evaluate media queries.
     pub fn rebuild<'a, I, S>(
         &mut self,
         doc_stylesheets: I,
@@ -227,28 +244,47 @@ impl Stylist {
         extra_data: &mut PerOrigin<ExtraStyleData>
     ) -> bool
     where
         I: Iterator<Item = &'a S> + Clone,
         S: StylesheetInDocument + ToMediaListKey + 'static,
     {
         debug_assert!(!self.any_origin_cleared() || self.is_device_dirty);
 
-        for (data, _) in self.cascade_data.iter_mut_origins() {
-            data.is_cleared = false;
+        // Determine the origins that actually need updating.
+        //
+        // XXX(heycam): What is the relationship between `stylesheets_changed`
+        // and the `is_cleared` fields on each origin's `CascadeData`?  Can
+        // we avoid passing in `stylesheets_changed`?
+        let mut to_update: PerOrigin<bool> = Default::default();
+
+        // If we're provided with a list of UA and user style sheets, then
+        // we must update those cascade levels. (Servo does this, but Gecko
+        // just includes the UA and User sheets in `doc_stylesheets`.)
+        if ua_stylesheets.is_some() {
+            to_update.user_agent = true;
+            to_update.user = true;
+        }
+
+        for (data, origin) in self.cascade_data.iter_mut_origins() {
+            if data.is_cleared {
+                data.is_cleared = false;
+                *to_update.borrow_mut_for_origin(&origin) = true;
+            }
         }
 
         if !(self.is_device_dirty || stylesheets_changed) {
             return false;
         }
 
         self.num_rebuilds += 1;
 
+        // Update viewport_constraints regardless of which origins'
+        // `CascadeData` we're updating.
         self.viewport_constraints = None;
-
         if viewport_rule::enabled() {
             // TODO(emilio): This doesn't look so efficient.
             //
             // Presumably when we properly implement this we can at least have a
             // bit on the stylesheet that says whether it contains viewport
             // rules to skip it entirely?
             //
             // Processing it with the rest of rules seems tricky since it
@@ -259,39 +295,58 @@ impl Stylist {
                 declarations: viewport_rule::Cascade::from_stylesheets(
                     doc_stylesheets.clone(), guards.author, &self.device
                 ).finish()
             };
 
             self.viewport_constraints =
                 ViewportConstraints::maybe_new(&self.device,
                                                &cascaded_rule,
-                                               self.quirks_mode)
+                                               self.quirks_mode);
+
+            if let Some(ref constraints) = self.viewport_constraints {
+                self.device.account_for_viewport_rule(constraints);
+            }
         }
 
-        if let Some(ref constraints) = self.viewport_constraints {
-            self.device.account_for_viewport_rule(constraints);
+        // XXX(heycam): We should probably just move the `extra_data` to be
+        // stored on the `Stylist` instead of Gecko's `PerDocumentStyleData`.
+        // That would let us clear it inside `clear()` and `clear_origin()`.
+        for (update, origin) in to_update.iter_origins() {
+            if *update {
+                extra_data.borrow_mut_for_origin(&origin).clear();
+            }
         }
 
-        extra_data.clear();
-
         if let Some(ua_stylesheets) = ua_stylesheets {
             for stylesheet in &ua_stylesheets.user_or_user_agent_stylesheets {
+                debug_assert!(matches!(
+                    stylesheet.contents(guards.ua_or_user).origin,
+                    Origin::UserAgent | Origin::User));
                 self.add_stylesheet(stylesheet, guards.ua_or_user, extra_data);
             }
 
             if self.quirks_mode != QuirksMode::NoQuirks {
+                let stylesheet = &ua_stylesheets.quirks_mode_stylesheet;
+                debug_assert!(matches!(
+                    stylesheet.contents(guards.ua_or_user).origin,
+                    Origin::UserAgent | Origin::User));
                 self.add_stylesheet(&ua_stylesheets.quirks_mode_stylesheet,
                                     guards.ua_or_user, extra_data);
             }
         }
 
-        // Only use author stylesheets if author styles are enabled.
+        // Only add stylesheets for origins we are updating, and only add
+        // Author level sheets if author style is not disabled.
         let sheets_to_add = doc_stylesheets.filter(|s| {
-            !author_style_disabled || s.origin(guards.author) != Origin::Author
+            match s.contents(guards.author).origin {
+                Origin::UserAgent => to_update.user_agent,
+                Origin::Author => to_update.author && !author_style_disabled,
+                Origin::User => to_update.user,
+            }
         });
 
         for stylesheet in sheets_to_add {
             self.add_stylesheet(stylesheet, guards.author, extra_data);
         }
 
         self.is_device_dirty = false;
         true