diff --git a/servo/components/layout_thread/lib.rs b/servo/components/layout_thread/lib.rs index caff54134d74..59796fe3f92d 100644 --- a/servo/components/layout_thread/lib.rs +++ b/servo/components/layout_thread/lib.rs @@ -129,15 +129,13 @@ use style::context::RegisteredSpeculativePainters; use style::dom::{ShowSubtree, ShowSubtreeDataAndPrimaryValues, TElement, TNode}; use style::error_reporting::{NullReporter, RustLogReporter}; use style::invalidation::element::restyle_hints::RestyleHint; -use style::invalidation::media_queries::{MediaListKey, ToMediaListKey}; use style::logical_geometry::LogicalPoint; use style::media_queries::{Device, MediaList, MediaType}; use style::properties::PropertyId; use style::selector_parser::SnapshotMap; use style::servo::restyle_damage::{REFLOW, REFLOW_OUT_OF_FLOW, REPAINT, REPOSITION, STORE_OVERFLOW}; use style::shared_lock::{SharedRwLock, SharedRwLockReadGuard, StylesheetGuards}; -use style::stylesheet_set::StylesheetSet; -use style::stylesheets::{Origin, Stylesheet, StylesheetContents, StylesheetInDocument, UserAgentStylesheets}; +use style::stylesheets::{Origin, OriginSet, Stylesheet, DocumentStyleSheet, StylesheetInDocument, UserAgentStylesheets}; use style::stylist::Stylist; use style::thread_state; use style::timer::Timer; @@ -147,35 +145,6 @@ use style_traits::CSSPixel; use style_traits::DevicePixel; use style_traits::SpeculativePainter; -#[derive(Clone)] -struct DocumentStyleSheet(ServoArc); - -impl PartialEq for DocumentStyleSheet { - fn eq(&self, other: &Self) -> bool { - ServoArc::ptr_eq(&self.0, &other.0) - } -} - -impl ToMediaListKey for DocumentStyleSheet { - fn to_media_list_key(&self) -> MediaListKey { - self.0.to_media_list_key() - } -} - -impl StylesheetInDocument for DocumentStyleSheet { - fn contents(&self, guard: &SharedRwLockReadGuard) -> &StylesheetContents { - self.0.contents(guard) - } - - fn media<'a>(&'a self, guard: &'a SharedRwLockReadGuard) -> Option<&'a MediaList> { - self.0.media(guard) - } - - fn enabled(&self) -> bool { - self.0.enabled() - } -} - /// Information needed by the layout thread. pub struct LayoutThread { /// The ID of the pipeline that we belong to. @@ -190,9 +159,6 @@ pub struct LayoutThread { /// Performs CSS selector matching and style resolution. stylist: Stylist, - /// The list of stylesheets synchronized with the document. - stylesheets: StylesheetSet, - /// Is the current reflow of an iframe, as opposed to a root window? is_iframe: bool, @@ -549,7 +515,6 @@ impl LayoutThread { webrender_api: webrender_api_sender.create_api(), webrender_document, stylist: Stylist::new(device, QuirksMode::NoQuirks), - stylesheets: StylesheetSet::new(), rw_data: Arc::new(Mutex::new( LayoutThreadData { constellation_chan: constellation_chan, @@ -704,16 +669,14 @@ impl LayoutThread { match before_stylesheet { Some(insertion_point) => { - self.stylesheets.insert_stylesheet_before( - Some(self.stylist.device()), + self.stylist.insert_stylesheet_before( DocumentStyleSheet(stylesheet.clone()), DocumentStyleSheet(insertion_point), &guard, ) } None => { - self.stylesheets.append_stylesheet( - Some(self.stylist.device()), + self.stylist.append_stylesheet( DocumentStyleSheet(stylesheet.clone()), &guard, ) @@ -722,8 +685,7 @@ impl LayoutThread { } Msg::RemoveStylesheet(stylesheet) => { let guard = stylesheet.shared_lock.read(); - self.stylesheets.remove_stylesheet( - Some(self.stylist.device()), + self.stylist.remove_stylesheet( DocumentStyleSheet(stylesheet.clone()), &guard, ); @@ -1188,8 +1150,9 @@ impl LayoutThread { let author_guard = document_shared_lock.read(); let device = Device::new(MediaType::screen(), initial_viewport, device_pixel_ratio); let sheet_origins_affected_by_device_change = - self.stylist.set_device(device, &author_guard, self.stylesheets.iter()); + self.stylist.set_device(device, &author_guard); + self.stylist.force_stylesheet_origins_dirty(sheet_origins_affected_by_device_change); self.viewport_size = self.stylist.viewport_constraints().map_or(current_screen_size, |constraints| { debug!("Viewport constraints: {:?}", constraints); @@ -1207,7 +1170,7 @@ impl LayoutThread { .send(ConstellationMsg::ViewportConstrained(self.id, constraints.clone())) .unwrap(); } - if self.stylesheets.iter().any(|sheet| sheet.0.dirty_on_viewport_size_change()) { + if self.stylist.iter_stylesheets().any(|sheet| sheet.0.dirty_on_viewport_size_change()) { let mut iter = element.as_node().traverse_preorder(); let mut next = iter.next(); @@ -1241,65 +1204,41 @@ impl LayoutThread { ua_or_user: &ua_or_user_guard, }; - let needs_dirtying = { - debug!("Flushing stylist"); - - let mut origins_dirty = sheet_origins_affected_by_device_change; - - debug!("Device changes: {:?}", origins_dirty); - + { if self.first_reflow.get() { debug!("First reflow, rebuilding user and UA rules"); - - origins_dirty |= Origin::User; - origins_dirty |= Origin::UserAgent; + let mut ua_and_user = OriginSet::empty(); + ua_and_user |= Origin::User; + ua_and_user |= Origin::UserAgent; + self.stylist.force_stylesheet_origins_dirty(ua_and_user); for stylesheet in &ua_stylesheets.user_or_user_agent_stylesheets { self.handle_add_stylesheet(stylesheet, &ua_or_user_guard); } } - let (iter, invalidation_origins_dirty) = self.stylesheets.flush(Some(element)); - debug!("invalidation: {:?}", invalidation_origins_dirty); - - origins_dirty |= invalidation_origins_dirty; - if data.stylesheets_changed { debug!("Doc sheets changed, flushing author sheets too"); - origins_dirty |= Origin::Author; + self.stylist.force_stylesheet_origins_dirty(Origin::Author.into()); } - if !origins_dirty.is_empty() { - let mut extra_data = Default::default(); - self.stylist.rebuild( - iter, - &guards, - Some(ua_stylesheets), - /* author_style_disabled = */ false, - &mut extra_data, - origins_dirty, - ); - } - - !origins_dirty.is_empty() - }; - - let needs_reflow = viewport_size_changed && !needs_dirtying; - if needs_dirtying { - if let Some(mut d) = element.mutate_data() { - if d.has_styles() { - d.restyle.hint.insert(RestyleHint::restyle_subtree()); - } - } + let mut extra_data = Default::default(); + self.stylist.flush( + &guards, + Some(ua_stylesheets), + &mut extra_data, + Some(element), + ); } - if needs_reflow { + + if viewport_size_changed { if let Some(mut flow) = self.try_get_layout_root(element.as_node()) { LayoutThread::reflow_all_nodes(FlowRef::deref_mut(&mut flow)); } } let restyles = document.drain_pending_restyles(); - debug!("Draining restyles: {} (needs dirtying? {:?})", - restyles.len(), needs_dirtying); + debug!("Draining restyles: {}", restyles.len()); + let mut map = SnapshotMap::new(); let elements_with_snapshot: Vec<_> = restyles diff --git a/servo/components/script/dom/document.rs b/servo/components/script/dom/document.rs index 3105f0f43414..4629ff8814f5 100644 --- a/servo/components/script/dom/document.rs +++ b/servo/components/script/dom/document.rs @@ -2338,6 +2338,11 @@ impl Document { pub fn flush_stylesheets_for_reflow(&self) -> bool { // NOTE(emilio): The invalidation machinery is used on the replicated // list on the layout thread. + // + // FIXME(emilio): This really should differentiate between CSSOM changes + // and normal stylesheets additions / removals, because in the last case + // the layout thread already has that information and we could avoid + // dirtying the whole thing. let mut stylesheets = self.stylesheets.borrow_mut(); let have_changed = stylesheets.has_changed(); stylesheets.flush_without_invalidation(); @@ -2370,11 +2375,10 @@ impl Document { .unwrap(); let guard = s.shared_lock.read(); - let device = self.device(); // FIXME(emilio): Would be nice to remove the clone, etc. self.stylesheets.borrow_mut().remove_stylesheet( - device.as_ref(), + None, StyleSheetInDocument { sheet: s.clone(), owner: JS::from_ref(owner), @@ -2414,13 +2418,12 @@ impl Document { let lock = self.style_shared_lock(); let guard = lock.read(); - let device = self.device(); match insertion_point { Some(ip) => { - stylesheets.insert_stylesheet_before(device.as_ref(), sheet, ip, &guard); + stylesheets.insert_stylesheet_before(None, sheet, ip, &guard); } None => { - stylesheets.append_stylesheet(device.as_ref(), sheet, &guard); + stylesheets.append_stylesheet(None, sheet, &guard); } } } diff --git a/servo/components/style/gecko/data.rs b/servo/components/style/gecko/data.rs index 7d99e4af89ff..39cfddd37381 100644 --- a/servo/components/style/gecko/data.rs +++ b/servo/components/style/gecko/data.rs @@ -16,7 +16,6 @@ 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 stylist::{ExtraStyleData, Stylist}; @@ -114,9 +113,6 @@ pub struct PerDocumentStyleDataImpl { /// Rule processor. pub stylist: Stylist, - /// List of stylesheets, mirrored from Gecko. - pub stylesheets: StylesheetSet, - /// List of effective @font-face and @counter-style rules. pub extra_style_data: PerOrigin, } @@ -135,7 +131,6 @@ impl PerDocumentStyleData { PerDocumentStyleData(AtomicRefCell::new(PerDocumentStyleDataImpl { stylist: Stylist::new(device, quirks_mode.into()), - stylesheets: StylesheetSet::new(), extra_style_data: Default::default(), })) } @@ -153,26 +148,20 @@ impl PerDocumentStyleData { impl PerDocumentStyleDataImpl { /// Recreate the style data if the stylesheets have changed. - pub fn flush_stylesheets(&mut self, - guard: &SharedRwLockReadGuard, - document_element: Option) - where E: TElement, + pub fn flush_stylesheets( + &mut self, + guard: &SharedRwLockReadGuard, + document_element: Option, + ) + where + E: TElement, { - if !self.stylesheets.has_changed() { - return; - } - - let author_style_disabled = self.stylesheets.author_style_disabled(); - - let (iter, dirty_origins) = self.stylesheets.flush(document_element); - self.stylist.rebuild( - iter, + self.stylist.flush( &StylesheetGuards::same(guard), /* ua_sheets = */ None, - author_style_disabled, &mut self.extra_style_data, - dirty_origins, - ); + document_element, + ) } /// Returns whether private browsing is enabled. diff --git a/servo/components/style/stylesheet_set.rs b/servo/components/style/stylesheet_set.rs index 9d9578d1909d..91e87cff2398 100644 --- a/servo/components/style/stylesheet_set.rs +++ b/servo/components/style/stylesheet_set.rs @@ -107,8 +107,7 @@ where /// Appends a new stylesheet to the current set. /// - /// FIXME(emilio): `device` shouldn't be optional, but a bunch of work needs - /// to happen to make the invalidations work properly in servo. + /// No device implies not computing invalidations. pub fn append_stylesheet( &mut self, device: Option<&Device>, @@ -122,9 +121,6 @@ where } /// Prepend a new stylesheet to the current set. - /// - /// FIXME(emilio): `device` shouldn't be optional, but a bunch of work needs - /// to happen to make the invalidations work properly in servo. pub fn prepend_stylesheet( &mut self, device: Option<&Device>, @@ -138,15 +134,12 @@ where } /// Insert a given stylesheet before another stylesheet in the document. - /// - /// FIXME(emilio): `device` shouldn't be optional, but a bunch of work needs - /// to happen to make the invalidations work properly in servo. pub fn insert_stylesheet_before( &mut self, device: Option<&Device>, sheet: S, before_sheet: S, - guard: &SharedRwLockReadGuard + guard: &SharedRwLockReadGuard, ) { debug!("StylesheetSet::insert_stylesheet_before"); self.remove_stylesheet_if_present(&sheet); @@ -158,9 +151,6 @@ where } /// Remove a given stylesheet from the set. - /// - /// FIXME(emilio): `device` shouldn't be optional, but a bunch of work needs - /// to happen to make the invalidations work properly in servo. pub fn remove_stylesheet( &mut self, device: Option<&Device>, diff --git a/servo/components/style/stylesheets/mod.rs b/servo/components/style/stylesheets/mod.rs index e418878363db..1ec7cbbfb061 100644 --- a/servo/components/style/stylesheets/mod.rs +++ b/servo/components/style/stylesheets/mod.rs @@ -44,12 +44,13 @@ pub use self::memory::{MallocSizeOf, MallocSizeOfFn, MallocSizeOfWithGuard}; #[cfg(feature = "gecko")] pub use self::memory::{MallocSizeOfWithRepeats, SizeOfState}; pub use self::namespace_rule::NamespaceRule; -pub use self::origin::{Origin, OriginSet, PerOrigin}; +pub use self::origin::{Origin, OriginSet, PerOrigin, PerOriginIter}; pub use self::page_rule::PageRule; pub use self::rule_parser::{State, TopLevelRuleParser}; pub use self::rule_list::{CssRules, CssRulesHelpers}; pub use self::rules_iterator::{AllRules, EffectiveRules, NestedRuleIterationCondition, RulesIterator}; -pub use self::stylesheet::{Namespaces, Stylesheet, StylesheetContents, StylesheetInDocument, UserAgentStylesheets}; +pub use self::stylesheet::{Namespaces, Stylesheet, DocumentStyleSheet}; +pub use self::stylesheet::{StylesheetContents, StylesheetInDocument, UserAgentStylesheets}; pub use self::style_rule::StyleRule; pub use self::supports_rule::SupportsRule; pub use self::viewport_rule::ViewportRule; diff --git a/servo/components/style/stylesheets/stylesheet.rs b/servo/components/style/stylesheets/stylesheet.rs index 15d6d5ceec82..9dc8446f6b64 100644 --- a/servo/components/style/stylesheets/stylesheet.rs +++ b/servo/components/style/stylesheets/stylesheet.rs @@ -7,6 +7,7 @@ use context::QuirksMode; use cssparser::{Parser, RuleListParser, ParserInput}; use error_reporting::{ParseErrorReporter, ContextualParseError}; use fnv::FnvHashMap; +use invalidation::media_queries::{MediaListKey, ToMediaListKey}; use media_queries::{MediaList, Device}; use parking_lot::RwLock; use parser::ParserContext; @@ -275,6 +276,41 @@ impl StylesheetInDocument for Stylesheet { } } +/// A simple wrapper over an `Arc`, with pointer comparison, and +/// suitable for its use in a `StylesheetSet`. +#[derive(Clone)] +#[cfg_attr(feature = "servo", derive(HeapSizeOf))] +pub struct DocumentStyleSheet( + #[cfg_attr(feature = "servo", ignore_heap_size_of = "Arc")] + pub Arc +); + +impl PartialEq for DocumentStyleSheet { + fn eq(&self, other: &Self) -> bool { + Arc::ptr_eq(&self.0, &other.0) + } +} + +impl ToMediaListKey for DocumentStyleSheet { + fn to_media_list_key(&self) -> MediaListKey { + self.0.to_media_list_key() + } +} + +impl StylesheetInDocument for DocumentStyleSheet { + fn contents(&self, guard: &SharedRwLockReadGuard) -> &StylesheetContents { + self.0.contents(guard) + } + + fn media<'a>(&'a self, guard: &'a SharedRwLockReadGuard) -> Option<&'a MediaList> { + self.0.media(guard) + } + + fn enabled(&self) -> bool { + self.0.enabled() + } +} + impl Stylesheet { /// Updates an empty stylesheet from a given string of text. pub fn update_from_str(existing: &Stylesheet, diff --git a/servo/components/style/stylist.rs b/servo/components/style/stylist.rs index e12dfe31a839..dc6924c6c5b9 100644 --- a/servo/components/style/stylist.rs +++ b/servo/components/style/stylist.rs @@ -36,11 +36,13 @@ use servo_arc::{Arc, ArcBorrow}; use shared_lock::{Locked, SharedRwLockReadGuard, StylesheetGuards}; use smallvec::VecLike; use std::fmt::Debug; +use std::ops; use style_traits::viewport::ViewportConstraints; +use stylesheet_set::{StylesheetSet, StylesheetIterator}; #[cfg(feature = "gecko")] use stylesheets::{CounterStyleRule, FontFaceRule}; use stylesheets::{CssRule, StyleRule}; -use stylesheets::{StylesheetInDocument, Origin, OriginSet, PerOrigin}; +use stylesheets::{StylesheetInDocument, Origin, OriginSet, PerOrigin, PerOriginIter}; use stylesheets::UserAgentStylesheets; use stylesheets::keyframes_rule::KeyframesAnimation; use stylesheets::viewport_rule::{self, MaybeNew, ViewportRule}; @@ -48,140 +50,43 @@ use thread_state; pub use ::fnv::FnvHashMap; -/// This structure holds all the selectors and device characteristics -/// for a given document. The selectors are converted into `Rule`s -/// (defined in rust-selectors), and sorted into `SelectorMap`s keyed -/// off stylesheet origin and pseudo-element (see `CascadeData`). -/// -/// This structure is effectively created once per pipeline, in the -/// LayoutThread corresponding to that pipeline. +/// The type of the stylesheets that the stylist contains. +#[cfg(feature = "servo")] +pub type StylistSheet = ::stylesheets::DocumentStyleSheet; + +/// The type of the stylesheets that the stylist contains. +#[cfg(feature = "gecko")] +pub type StylistSheet = ::gecko::data::GeckoStyleSheet; + +/// All the computed information for a stylesheet. +#[derive(Default)] #[cfg_attr(feature = "servo", derive(HeapSizeOf))] -pub struct Stylist { - /// Device that the stylist is currently evaluating against. - /// - /// This field deserves a bigger comment due to the different use that Gecko - /// and Servo give to it (that we should eventually unify). - /// - /// With Gecko, the device is never changed. Gecko manually tracks whether - /// the device data should be reconstructed, and "resets" the state of the - /// device. - /// - /// On Servo, on the other hand, the device is a really cheap representation - /// that is recreated each time some constraint changes and calling - /// `set_device`. - device: Device, - - /// Viewport constraints based on the current device. - viewport_constraints: Option, - - /// If true, the quirks-mode stylesheet is applied. - #[cfg_attr(feature = "servo", ignore_heap_size_of = "defined in selectors")] - quirks_mode: QuirksMode, - - /// Selector maps for all of the style sheets in the stylist, after - /// evalutaing media rules against the current device, split out per - /// cascade level. - cascade_data: PerOrigin, - - /// The rule tree, that stores the results of selector matching. - rule_tree: RuleTree, +struct DocumentCascadeData { + /// Common data for all the origins. + per_origin: PerOrigin, /// Applicable declarations for a given non-eagerly cascaded pseudo-element. + /// /// These are eagerly computed once, and then used to resolve the new /// computed values on the fly on layout. /// + /// These are only filled from UA stylesheets. + /// /// FIXME(emilio): Use the rule tree! precomputed_pseudo_element_decls: PerPseudoElementMap>, - - /// The total number of times the stylist has been rebuilt. - num_rebuilds: usize, } -/// What cascade levels to include when styling elements. -#[derive(Copy, Clone, PartialEq)] -pub enum RuleInclusion { - /// Include rules for style sheets at all cascade levels. This is the - /// normal rule inclusion mode. - All, - /// Only include rules from UA and user level sheets. Used to implement - /// `getDefaultComputedStyle`. - DefaultOnly, -} - -#[cfg(feature = "gecko")] -impl From for RuleInclusion { - fn from(value: StyleRuleInclusion) -> Self { - match value { - StyleRuleInclusion::All => RuleInclusion::All, - StyleRuleInclusion::DefaultOnly => RuleInclusion::DefaultOnly, - } - } -} - -impl Stylist { - /// Construct a new `Stylist`, using given `Device` and `QuirksMode`. - /// If more members are added here, think about whether they should - /// be reset in clear(). - #[inline] - pub fn new(device: Device, quirks_mode: QuirksMode) -> Self { - Stylist { - viewport_constraints: None, - device: device, - quirks_mode: quirks_mode, - - cascade_data: Default::default(), - precomputed_pseudo_element_decls: PerPseudoElementMap::default(), - rule_tree: RuleTree::new(), - num_rebuilds: 0, - } - - // FIXME: Add iso-8859-9.css when the document’s encoding is ISO-8859-8. +impl DocumentCascadeData { + fn iter_origins(&self) -> PerOriginIter { + self.per_origin.iter_origins() } - /// Returns the number of selectors. - pub fn num_selectors(&self) -> usize { - self.cascade_data.iter_origins().map(|(d, _)| d.num_selectors).sum() - } - - /// Returns the number of declarations. - pub fn num_declarations(&self) -> usize { - self.cascade_data.iter_origins().map(|(d, _)| d.num_declarations).sum() - } - - /// Returns the number of times the stylist has been rebuilt. - pub fn num_rebuilds(&self) -> usize { - self.num_rebuilds - } - - /// Returns the number of revalidation_selectors. - pub fn num_revalidation_selectors(&self) -> usize { - self.cascade_data.iter_origins() - .map(|(d, _)| d.selectors_for_cache_revalidation.len()).sum() - } - - /// Returns the number of entries in invalidation maps. - pub fn num_invalidations(&self) -> usize { - self.cascade_data.iter_origins() - .map(|(d, _)| d.invalidation_map.len()).sum() - } - - /// Invokes `f` with the `InvalidationMap` for each origin. - /// - /// NOTE(heycam) This might be better as an `iter_invalidation_maps`, once - /// we have `impl trait` and can return that easily without bothering to - /// create a whole new iterator type. - pub fn each_invalidation_map(&self, mut f: F) - where F: FnMut(&InvalidationMap) - { - for (data, _) in self.cascade_data.iter_origins() { - f(&data.invalidation_map) - } - } - - /// Rebuild the stylist for the given document stylesheets, and optionally - /// with a set of user agent stylesheets. - pub fn rebuild<'a, I, S>( + /// Rebuild the cascade data for the given document stylesheets, and + /// optionally with a set of user agent stylesheets. + fn rebuild<'a, I, S>( &mut self, + device: &Device, + quirks_mode: QuirksMode, doc_stylesheets: I, guards: &StylesheetGuards, ua_stylesheets: Option<&UserAgentStylesheets>, @@ -195,41 +100,9 @@ impl Stylist { { debug_assert!(!origins_to_rebuild.is_empty()); - 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 - // overrides the viewport size which may change the evaluation of - // media queries (or may not? how are viewport units in media - // queries defined?) - let cascaded_rule = ViewportRule { - 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); - - if let Some(ref constraints) = self.viewport_constraints { - self.device.account_for_viewport_rule(constraints); - } - } - for origin in origins_to_rebuild.iter() { extra_data.borrow_mut_for_origin(&origin).clear(); - self.cascade_data.borrow_mut_for_origin(&origin).clear(); + self.per_origin.borrow_mut_for_origin(&origin).clear(); } if origins_to_rebuild.contains(Origin::UserAgent.into()) { @@ -248,14 +121,16 @@ impl Stylist { if origins_to_rebuild.contains(sheet_origin.into()) { self.add_stylesheet( + device, + quirks_mode, stylesheet, guards.ua_or_user, - extra_data + extra_data, ); } } - if self.quirks_mode != QuirksMode::NoQuirks { + if quirks_mode != QuirksMode::NoQuirks { let stylesheet = &ua_stylesheets.quirks_mode_stylesheet; let sheet_origin = stylesheet.contents(guards.ua_or_user).origin; @@ -267,6 +142,8 @@ impl Stylist { if origins_to_rebuild.contains(sheet_origin.into()) { self.add_stylesheet( + device, + quirks_mode, &ua_stylesheets.quirks_mode_stylesheet, guards.ua_or_user, extra_data @@ -285,12 +162,20 @@ impl Stylist { }); for stylesheet in sheets_to_add { - self.add_stylesheet(stylesheet, guards.author, extra_data); + self.add_stylesheet( + device, + quirks_mode, + stylesheet, + guards.author, + extra_data + ); } } fn add_stylesheet( &mut self, + device: &Device, + quirks_mode: QuirksMode, stylesheet: &S, guard: &SharedRwLockReadGuard, _extra_data: &mut PerOrigin @@ -299,19 +184,19 @@ impl Stylist { S: StylesheetInDocument + ToMediaListKey + 'static, { if !stylesheet.enabled() || - !stylesheet.is_effective_for_device(&self.device, guard) { + !stylesheet.is_effective_for_device(device, guard) { return; } let origin = stylesheet.origin(guard); let origin_cascade_data = - self.cascade_data.borrow_mut_for_origin(&origin); + self.per_origin.borrow_mut_for_origin(&origin); origin_cascade_data .effective_media_query_results .saw_effective(stylesheet); - for rule in stylesheet.effective_rules(&self.device, guard) { + for rule in stylesheet.effective_rules(device, guard) { match *rule { CssRule::Style(ref locked) => { let style_rule = locked.read_with(&guard); @@ -352,7 +237,7 @@ impl Stylist { }; let hashes = - AncestorHashes::new(&selector, self.quirks_mode); + AncestorHashes::new(&selector, quirks_mode); let rule = Rule::new( selector.clone(), @@ -361,11 +246,11 @@ impl Stylist { origin_cascade_data.rules_source_order ); - map.insert(rule, self.quirks_mode); + map.insert(rule, quirks_mode); origin_cascade_data .invalidation_map - .note_selector(selector, self.quirks_mode); + .note_selector(selector, quirks_mode); let mut visitor = StylistSelectorVisitor { needs_revalidation: false, passed_rightmost_selector: false, @@ -380,7 +265,8 @@ impl Stylist { if visitor.needs_revalidation { origin_cascade_data.selectors_for_cache_revalidation.insert( RevalidationSelectorAndHashes::new(selector.clone(), hashes), - self.quirks_mode); + quirks_mode + ); } } origin_cascade_data.rules_source_order += 1; @@ -433,12 +319,290 @@ impl Stylist { } } } +} + +/// A wrapper over a StylesheetSet that can be `Sync`, since it's only used and +/// exposed via mutable methods in the `Stylist`. +#[cfg_attr(feature = "servo", derive(HeapSizeOf))] +struct StylistStylesheetSet(StylesheetSet); +// Read above to see why this is fine. +unsafe impl Sync for StylistStylesheetSet {} + +impl StylistStylesheetSet { + fn new() -> Self { + StylistStylesheetSet(StylesheetSet::new()) + } +} + +impl ops::Deref for StylistStylesheetSet { + type Target = StylesheetSet; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl ops::DerefMut for StylistStylesheetSet { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +/// This structure holds all the selectors and device characteristics +/// for a given document. The selectors are converted into `Rule`s +/// and sorted into `SelectorMap`s keyed off stylesheet origin and +/// pseudo-element (see `CascadeData`). +/// +/// This structure is effectively created once per pipeline, in the +/// LayoutThread corresponding to that pipeline. +#[cfg_attr(feature = "servo", derive(HeapSizeOf))] +pub struct Stylist { + /// Device that the stylist is currently evaluating against. + /// + /// This field deserves a bigger comment due to the different use that Gecko + /// and Servo give to it (that we should eventually unify). + /// + /// With Gecko, the device is never changed. Gecko manually tracks whether + /// the device data should be reconstructed, and "resets" the state of the + /// device. + /// + /// On Servo, on the other hand, the device is a really cheap representation + /// that is recreated each time some constraint changes and calling + /// `set_device`. + device: Device, + + /// Viewport constraints based on the current device. + viewport_constraints: Option, + + /// The list of stylesheets. + stylesheets: StylistStylesheetSet, + + /// If true, the quirks-mode stylesheet is applied. + #[cfg_attr(feature = "servo", ignore_heap_size_of = "defined in selectors")] + quirks_mode: QuirksMode, + + /// Selector maps for all of the style sheets in the stylist, after + /// evalutaing media rules against the current device, split out per + /// cascade level. + cascade_data: DocumentCascadeData, + + /// The rule tree, that stores the results of selector matching. + rule_tree: RuleTree, + + /// The total number of times the stylist has been rebuilt. + num_rebuilds: usize, +} + +/// What cascade levels to include when styling elements. +#[derive(Copy, Clone, PartialEq)] +pub enum RuleInclusion { + /// Include rules for style sheets at all cascade levels. This is the + /// normal rule inclusion mode. + All, + /// Only include rules from UA and user level sheets. Used to implement + /// `getDefaultComputedStyle`. + DefaultOnly, +} + +#[cfg(feature = "gecko")] +impl From for RuleInclusion { + fn from(value: StyleRuleInclusion) -> Self { + match value { + StyleRuleInclusion::All => RuleInclusion::All, + StyleRuleInclusion::DefaultOnly => RuleInclusion::DefaultOnly, + } + } +} + +impl Stylist { + /// Construct a new `Stylist`, using given `Device` and `QuirksMode`. + /// If more members are added here, think about whether they should + /// be reset in clear(). + #[inline] + pub fn new(device: Device, quirks_mode: QuirksMode) -> Self { + Self { + viewport_constraints: None, + device, + quirks_mode, + stylesheets: StylistStylesheetSet::new(), + cascade_data: Default::default(), + rule_tree: RuleTree::new(), + num_rebuilds: 0, + } + } + + /// Returns the number of selectors. + pub fn num_selectors(&self) -> usize { + self.cascade_data.iter_origins().map(|(d, _)| d.num_selectors).sum() + } + + /// Returns the number of declarations. + pub fn num_declarations(&self) -> usize { + self.cascade_data.iter_origins().map(|(d, _)| d.num_declarations).sum() + } + + /// Returns the number of times the stylist has been rebuilt. + pub fn num_rebuilds(&self) -> usize { + self.num_rebuilds + } + + /// Returns the number of revalidation_selectors. + pub fn num_revalidation_selectors(&self) -> usize { + self.cascade_data.iter_origins() + .map(|(d, _)| d.selectors_for_cache_revalidation.len()).sum() + } + + /// Returns the number of entries in invalidation maps. + pub fn num_invalidations(&self) -> usize { + self.cascade_data.iter_origins() + .map(|(d, _)| d.invalidation_map.len()).sum() + } + + /// Invokes `f` with the `InvalidationMap` for each origin. + /// + /// NOTE(heycam) This might be better as an `iter_invalidation_maps`, once + /// we have `impl trait` and can return that easily without bothering to + /// create a whole new iterator type. + pub fn each_invalidation_map(&self, mut f: F) + where F: FnMut(&InvalidationMap) + { + for (data, _) in self.cascade_data.iter_origins() { + f(&data.invalidation_map) + } + } + + /// Flush the list of stylesheets if they changed, ensuring the stylist is + /// up-to-date. + /// + /// FIXME(emilio): Move the `ua_sheets` to the Stylist too? + pub fn flush( + &mut self, + guards: &StylesheetGuards, + ua_sheets: Option<&UserAgentStylesheets>, + extra_data: &mut PerOrigin, + document_element: Option, + ) + where + E: TElement, + { + if !self.stylesheets.has_changed() { + return; + } + + let author_style_disabled = self.stylesheets.author_style_disabled(); + let (doc_stylesheets, origins_to_rebuild) = self.stylesheets.flush(document_element); + + if origins_to_rebuild.is_empty() { + return; + } + + 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 + // overrides the viewport size which may change the evaluation of + // media queries (or may not? how are viewport units in media + // queries defined?) + let cascaded_rule = ViewportRule { + 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); + + if let Some(ref constraints) = self.viewport_constraints { + self.device.account_for_viewport_rule(constraints); + } + } + + self.cascade_data.rebuild( + &self.device, + self.quirks_mode, + doc_stylesheets, + guards, + ua_sheets, + author_style_disabled, + extra_data, + origins_to_rebuild, + ); + } + + /// Insert a given stylesheet before another stylesheet in the document. + pub fn insert_stylesheet_before( + &mut self, + sheet: StylistSheet, + before_sheet: StylistSheet, + guard: &SharedRwLockReadGuard, + ) { + self.stylesheets.insert_stylesheet_before( + Some(&self.device), + sheet, + before_sheet, + guard, + ) + } + + /// Marks a given stylesheet origin as dirty, due to, for example, changes + /// in the declarations that affect a given rule. + /// + /// FIXME(emilio): Eventually it'd be nice for this to become more + /// fine-grained. + pub fn force_stylesheet_origins_dirty(&mut self, origins: OriginSet) { + self.stylesheets.force_dirty(origins) + } + + /// Iterate over the given set of stylesheets. + /// + /// This is very intentionally exposed only on `&mut self`, since we don't + /// want to give access to the stylesheet list from worker threads. + pub fn iter_stylesheets(&mut self) -> StylesheetIterator { + self.stylesheets.iter() + } + + /// Sets whether author style is enabled or not. + pub fn set_author_style_disabled(&mut self, disabled: bool) { + self.stylesheets.set_author_style_disabled(disabled); + } + + /// Returns whether we've recorded any stylesheet change so far. + pub fn stylesheets_have_changed(&self) -> bool { + self.stylesheets.has_changed() + } + + /// Appends a new stylesheet to the current set. + pub fn append_stylesheet(&mut self, sheet: StylistSheet, guard: &SharedRwLockReadGuard) { + self.stylesheets.append_stylesheet(Some(&self.device), sheet, guard) + } + + /// Appends a new stylesheet to the current set. + pub fn prepend_stylesheet(&mut self, sheet: StylistSheet, guard: &SharedRwLockReadGuard) { + self.stylesheets.prepend_stylesheet(Some(&self.device), sheet, guard) + } + + /// Remove a given stylesheet to the current set. + pub fn remove_stylesheet(&mut self, sheet: StylistSheet, guard: &SharedRwLockReadGuard) { + self.stylesheets.remove_stylesheet(Some(&self.device), sheet, guard) + } /// Returns whether the given attribute might appear in an attribute /// selector of some rule in the stylist. - pub fn might_have_attribute_dependency(&self, - local_name: &LocalName) - -> bool { + pub fn might_have_attribute_dependency( + &self, + local_name: &LocalName, + ) -> bool { if *local_name == local_name!("style") { self.cascade_data .iter_origins() @@ -483,15 +647,16 @@ impl Stylist { -> Arc { debug_assert!(pseudo.is_precomputed()); - let rule_node = match self.precomputed_pseudo_element_decls.get(pseudo) { - Some(declarations) => { - self.rule_tree.insert_ordered_rules_with_important( - declarations.into_iter().map(|a| (a.source.clone(), a.level())), - guards - ) - } - None => self.rule_tree.root().clone(), - }; + let rule_node = + match self.cascade_data.precomputed_pseudo_element_decls.get(pseudo) { + Some(declarations) => { + self.rule_tree.insert_ordered_rules_with_important( + declarations.into_iter().map(|a| (a.source.clone(), a.level())), + guards + ) + } + None => self.rule_tree.root().clone(), + }; // NOTE(emilio): We skip calculating the proper layout parent style // here. @@ -523,11 +688,12 @@ impl Stylist { /// Returns the style for an anonymous box of the given type. #[cfg(feature = "servo")] - pub fn style_for_anonymous(&self, - guards: &StylesheetGuards, - pseudo: &PseudoElement, - parent_style: &ComputedValues) - -> Arc { + pub fn style_for_anonymous( + &self, + guards: &StylesheetGuards, + pseudo: &PseudoElement, + parent_style: &ComputedValues + ) -> Arc { use font_metrics::ServoMetricsProvider; // For most (but not all) pseudo-elements, we inherit all values from the parent. @@ -554,8 +720,13 @@ impl Stylist { if inherit_all { cascade_flags.insert(INHERIT_ALL); } - self.precomputed_values_for_pseudo(guards, &pseudo, Some(parent_style), cascade_flags, - &ServoMetricsProvider) + self.precomputed_values_for_pseudo( + guards, + &pseudo, + Some(parent_style), + cascade_flags, + &ServoMetricsProvider + ) } /// Computes a pseudo-element style lazily during layout. @@ -849,22 +1020,21 @@ impl Stylist { /// FIXME(emilio): The semantics of the device for Servo and Gecko are /// different enough we may want to unify them. #[cfg(feature = "servo")] - pub fn set_device<'a, I, S>( + pub fn set_device( &mut self, mut device: Device, guard: &SharedRwLockReadGuard, - stylesheets: I, - ) -> OriginSet - where - I: Iterator + Clone, - S: StylesheetInDocument + ToMediaListKey + 'static, - { - let cascaded_rule = ViewportRule { - declarations: viewport_rule::Cascade::from_stylesheets( - stylesheets.clone(), - guard, - &device - ).finish(), + ) -> OriginSet { + let cascaded_rule = { + let stylesheets = self.stylesheets.iter(); + + ViewportRule { + declarations: viewport_rule::Cascade::from_stylesheets( + stylesheets.clone(), + guard, + &device + ).finish(), + } }; self.viewport_constraints = @@ -875,28 +1045,21 @@ impl Stylist { } self.device = device; - self.media_features_change_changed_style( - stylesheets, - guard, - ) + self.media_features_change_changed_style(guard) } /// Returns whether, given a media feature change, any previously-applicable /// style has become non-applicable, or vice-versa for each origin. - pub fn media_features_change_changed_style<'a, I, S>( + pub fn media_features_change_changed_style( &self, - stylesheets: I, guard: &SharedRwLockReadGuard, - ) -> OriginSet - where - I: Iterator, - S: StylesheetInDocument + ToMediaListKey + 'static, - { + ) -> OriginSet { use invalidation::media_queries::PotentiallyEffectiveMediaRules; debug!("Stylist::media_features_change_changed_style"); let mut origins = OriginSet::empty(); + let stylesheets = self.stylesheets.iter(); 'stylesheets_loop: for stylesheet in stylesheets { let effective_now = @@ -909,7 +1072,7 @@ impl Stylist { } let origin_cascade_data = - self.cascade_data.borrow_for_origin(&origin); + self.cascade_data.per_origin.borrow_for_origin(&origin); let effective_then = origin_cascade_data @@ -1034,7 +1197,7 @@ impl Stylist { // nsXBLPrototypeResources::LoadResources() loads Chrome XBL style // sheets under eAuthorSheetFeatures level. - if let Some(map) = self.cascade_data.author.borrow_for_pseudo(pseudo_element) { + if let Some(map) = self.cascade_data.per_origin.author.borrow_for_pseudo(pseudo_element) { map.get_all_matching_rules(element, &rule_hash_target, applicable_declarations, @@ -1081,7 +1244,7 @@ impl Stylist { let only_default_rules = rule_inclusion == RuleInclusion::DefaultOnly; // Step 1: Normal user-agent rules. - if let Some(map) = self.cascade_data.user_agent.borrow_for_pseudo(pseudo_element) { + if let Some(map) = self.cascade_data.per_origin.user_agent.borrow_for_pseudo(pseudo_element) { map.get_all_matching_rules(element, &rule_hash_target, applicable_declarations, @@ -1117,7 +1280,7 @@ impl Stylist { // Which may be more what you would probably expect. if rule_hash_target.matches_user_and_author_rules() { // Step 3a: User normal rules. - if let Some(map) = self.cascade_data.user.borrow_for_pseudo(pseudo_element) { + if let Some(map) = self.cascade_data.per_origin.user.borrow_for_pseudo(pseudo_element) { map.get_all_matching_rules(element, &rule_hash_target, applicable_declarations, @@ -1140,7 +1303,7 @@ impl Stylist { // See nsStyleSet::FileRules(). if !cut_off_inheritance { // Step 3c: Author normal rules. - if let Some(map) = self.cascade_data.author.borrow_for_pseudo(pseudo_element) { + if let Some(map) = self.cascade_data.per_origin.author.borrow_for_pseudo(pseudo_element) { map.get_all_matching_rules(element, &rule_hash_target, applicable_declarations, diff --git a/servo/ports/geckolib/glue.rs b/servo/ports/geckolib/glue.rs index c2763e85bbaf..077e54d740cc 100644 --- a/servo/ports/geckolib/glue.rs +++ b/servo/ports/geckolib/glue.rs @@ -215,7 +215,7 @@ fn traverse_subtree(element: GeckoElement, } let per_doc_data = PerDocumentStyleData::from_ffi(raw_data).borrow(); - debug_assert!(!per_doc_data.stylesheets.has_changed()); + debug_assert!(!per_doc_data.stylist.stylesheets_have_changed()); let global_style_data = &*GLOBAL_STYLE_DATA; let guard = global_style_data.shared_lock.read(); @@ -902,7 +902,7 @@ pub extern "C" fn Servo_StyleSet_AppendStyleSheet( let data = &mut *data; let guard = global_style_data.shared_lock.read(); let sheet = unsafe { GeckoStyleSheet::new(sheet) }; - data.stylesheets.append_stylesheet(Some(data.stylist.device()), sheet, &guard); + data.stylist.append_stylesheet(sheet, &guard); } #[no_mangle] @@ -930,10 +930,7 @@ pub extern "C" fn Servo_StyleSet_MediumFeaturesChanged( } data.stylist.device_mut().reset_computed_values(); let origins_in_which_rules_changed = - data.stylist.media_features_change_changed_style( - data.stylesheets.iter(), - &guard, - ); + data.stylist.media_features_change_changed_style(&guard); // We'd like to return `OriginFlags` here, but bindgen bitfield enums don't // work as return values with the Linux 32-bit ABI at the moment because @@ -951,7 +948,7 @@ pub extern "C" fn Servo_StyleSet_PrependStyleSheet( let data = &mut *data; let guard = global_style_data.shared_lock.read(); let sheet = unsafe { GeckoStyleSheet::new(sheet) }; - data.stylesheets.prepend_stylesheet(Some(data.stylist.device()), sheet, &guard); + data.stylist.prepend_stylesheet(sheet, &guard); } #[no_mangle] @@ -965,8 +962,7 @@ pub extern "C" fn Servo_StyleSet_InsertStyleSheetBefore( let data = &mut *data; let guard = global_style_data.shared_lock.read(); let sheet = unsafe { GeckoStyleSheet::new(sheet) }; - data.stylesheets.insert_stylesheet_before( - Some(data.stylist.device()), + data.stylist.insert_stylesheet_before( sheet, unsafe { GeckoStyleSheet::new(before_sheet) }, &guard, @@ -983,7 +979,7 @@ pub extern "C" fn Servo_StyleSet_RemoveStyleSheet( let data = &mut *data; let guard = global_style_data.shared_lock.read(); let sheet = unsafe { GeckoStyleSheet::new(sheet) }; - data.stylesheets.remove_stylesheet(Some(data.stylist.device()), sheet, &guard); + data.stylist.remove_stylesheet(sheet, &guard); } #[no_mangle] @@ -1005,8 +1001,8 @@ pub extern "C" fn Servo_StyleSet_NoteStyleSheetsChanged( changed_origins: OriginFlags, ) { let mut data = PerDocumentStyleData::from_ffi(raw_data).borrow_mut(); - data.stylesheets.force_dirty(OriginSet::from(changed_origins)); - data.stylesheets.set_author_style_disabled(author_style_disabled); + data.stylist.force_stylesheet_origins_dirty(OriginSet::from(changed_origins)); + data.stylist.set_author_style_disabled(author_style_disabled); } #[no_mangle]