diff --git a/components/layout_thread/lib.rs b/components/layout_thread/lib.rs index 736826ece07..1a6bd945caf 100644 --- a/components/layout_thread/lib.rs +++ b/components/layout_thread/lib.rs @@ -117,7 +117,6 @@ use std::collections::HashMap; use std::mem as std_mem; use std::ops::{Deref, DerefMut}; use std::process; -use std::slice; use std::sync::{Arc, Mutex, MutexGuard}; use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::mpsc::{Receiver, Sender, channel}; @@ -130,13 +129,15 @@ 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::stylesheets::{Origin, Stylesheet, StylesheetInDocument, UserAgentStylesheets}; +use style::stylesheet_set::StylesheetSet; +use style::stylesheets::{Origin, OriginSet, Stylesheet, StylesheetContents, StylesheetInDocument, UserAgentStylesheets}; use style::stylist::Stylist; use style::thread_state; use style::timer::Timer; @@ -146,6 +147,35 @@ 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. @@ -160,6 +190,9 @@ 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, @@ -447,17 +480,6 @@ fn add_font_face_rules(stylesheet: &Stylesheet, } } -#[derive(Clone)] -struct StylesheetIterator<'a>(slice::Iter<'a, ServoArc>); - -impl<'a> Iterator for StylesheetIterator<'a> { - type Item = &'a Stylesheet; - - fn next(&mut self) -> Option { - self.0.next().map(|s| &**s) - } -} - impl LayoutThread { /// Creates a new `LayoutThread` structure. fn new(id: PipelineId, @@ -548,7 +570,8 @@ impl LayoutThread { viewport_size: Size2D::new(Au(0), Au(0)), webrender_api: webrender_api_sender.create_api(), webrender_document, - stylist: stylist, + stylist, + stylesheets: StylesheetSet::new(), rw_data: Arc::new(Mutex::new( LayoutThreadData { constellation_chan: constellation_chan, @@ -687,13 +710,46 @@ impl LayoutThread { } /// Receives and dispatches messages from other threads. - fn handle_request_helper<'a, 'b>(&mut self, - request: Msg, - possibly_locked_rw_data: &mut RwData<'a, 'b>) - -> bool { + fn handle_request_helper<'a, 'b>( + &mut self, + request: Msg, + possibly_locked_rw_data: &mut RwData<'a, 'b>, + ) -> bool { match request { - Msg::AddStylesheet(style_info) => { - self.handle_add_stylesheet(style_info, possibly_locked_rw_data) + Msg::AddStylesheet(stylesheet, before_stylesheet) => { + let guard = stylesheet.shared_lock.read(); + + self.handle_add_stylesheet( + &stylesheet, + &guard, + possibly_locked_rw_data, + ); + + match before_stylesheet { + Some(insertion_point) => { + self.stylesheets.insert_stylesheet_before( + Some(self.stylist.device()), + DocumentStyleSheet(stylesheet.clone()), + DocumentStyleSheet(insertion_point), + &guard, + ) + } + None => { + self.stylesheets.append_stylesheet( + Some(self.stylist.device()), + DocumentStyleSheet(stylesheet.clone()), + &guard, + ) + } + } + } + Msg::RemoveStylesheet(stylesheet) => { + let guard = stylesheet.shared_lock.read(); + self.stylesheets.remove_stylesheet( + Some(self.stylist.device()), + DocumentStyleSheet(stylesheet.clone()), + &guard, + ); } Msg::SetQuirksMode(mode) => self.handle_set_quirks_mode(mode), Msg::GetRPC(response_chan) => { @@ -860,14 +916,16 @@ impl LayoutThread { let _ = self.parallel_traversal.take(); } - fn handle_add_stylesheet<'a, 'b>(&self, - stylesheet: ServoArc, - possibly_locked_rw_data: &mut RwData<'a, 'b>) { + fn handle_add_stylesheet<'a, 'b>( + &self, + stylesheet: &ServoArc, + guard: &SharedRwLockReadGuard, + possibly_locked_rw_data: &mut RwData<'a, 'b>, + ) { // Find all font-face rules and notify the font cache of them. // GWTODO: Need to handle unloading web fonts. let rw_data = possibly_locked_rw_data.lock(); - let guard = stylesheet.shared_lock.read(); if stylesheet.is_effective_for_device(self.stylist.device(), &guard) { add_font_face_rules(&*stylesheet, &guard, @@ -1157,7 +1215,7 @@ impl LayoutThread { self.document_shared_lock = Some(document_shared_lock.clone()); let author_guard = document_shared_lock.read(); let device = Device::new(MediaType::screen(), initial_viewport, device_pixel_ratio); - self.stylist.set_device(device, &author_guard, &data.document_stylesheets); + self.stylist.set_device(device, &author_guard, self.stylesheets.iter()); self.viewport_size = self.stylist.viewport_constraints().map_or(current_screen_size, |constraints| { @@ -1176,7 +1234,7 @@ impl LayoutThread { .send(ConstellationMsg::ViewportConstrained(self.id, constraints.clone())) .unwrap(); } - if data.document_stylesheets.iter().any(|sheet| sheet.dirty_on_viewport_size_change()) { + if self.stylesheets.iter().any(|sheet| sheet.0.dirty_on_viewport_size_change()) { let mut iter = element.as_node().traverse_preorder(); let mut next = iter.next(); @@ -1185,6 +1243,8 @@ impl LayoutThread { let el = node.as_element().unwrap(); if let Some(mut d) = element.mutate_data() { if d.has_styles() { + // FIXME(emilio): This only needs to recascade, + // afaict. d.restyle.hint.insert(RestyleHint::restyle_subtree()); } } @@ -1207,14 +1267,24 @@ impl LayoutThread { author: &author_guard, ua_or_user: &ua_or_user_guard, }; - let mut extra_data = Default::default(); - let needs_dirtying = self.stylist.update( - StylesheetIterator(data.document_stylesheets.iter()), - &guards, - Some(ua_stylesheets), - data.stylesheets_changed, - /* author_styles_disabled = */ false, - &mut extra_data); + + let needs_dirtying = { + let mut extra_data = Default::default(); + let (iter, mut origins_dirty) = self.stylesheets.flush(Some(element)); + if data.stylesheets_changed { + origins_dirty = OriginSet::all(); + } + let origins_rebuilt = self.stylist.rebuild( + iter, + &guards, + Some(ua_stylesheets), + /* author_style_disabled = */ false, + &mut extra_data, + origins_dirty, + ); + !origins_rebuilt.is_empty() + }; + let needs_reflow = viewport_size_changed && !needs_dirtying; if needs_dirtying { if let Some(mut d) = element.mutate_data() { diff --git a/components/script/dom/bindings/trace.rs b/components/script/dom/bindings/trace.rs index 4168fb4effa..df681e3add9 100644 --- a/components/script/dom/bindings/trace.rs +++ b/components/script/dom/bindings/trace.rs @@ -102,7 +102,8 @@ use style::media_queries::MediaList; use style::properties::PropertyDeclarationBlock; use style::selector_parser::{PseudoElement, Snapshot}; use style::shared_lock::{SharedRwLock as StyleSharedRwLock, Locked as StyleLocked}; -use style::stylesheets::{CssRules, FontFaceRule, KeyframesRule, MediaRule}; +use style::stylesheet_set::StylesheetSet; +use style::stylesheets::{CssRules, FontFaceRule, KeyframesRule, MediaRule, Stylesheet}; use style::stylesheets::{NamespaceRule, StyleRule, ImportRule, SupportsRule, ViewportRule}; use style::stylesheets::keyframes_rule::Keyframe; use style::values::specified::Length; @@ -374,6 +375,7 @@ unsafe_no_jsmanaged_fields!(AttrIdentifier); unsafe_no_jsmanaged_fields!(AttrValue); unsafe_no_jsmanaged_fields!(Snapshot); unsafe_no_jsmanaged_fields!(PendingRestyle); +unsafe_no_jsmanaged_fields!(Stylesheet); unsafe_no_jsmanaged_fields!(HttpsState); unsafe_no_jsmanaged_fields!(Request); unsafe_no_jsmanaged_fields!(RequestInit); @@ -641,6 +643,18 @@ unsafe impl JSTraceable for StyleLocked { } } +unsafe impl JSTraceable for StylesheetSet +where + S: JSTraceable + ::style::stylesheets::StylesheetInDocument + PartialEq + 'static, +{ + unsafe fn trace(&self, tracer: *mut JSTracer) { + for s in self.iter() { + s.trace(tracer) + } + } +} + + /// Holds a set of JSTraceables that need to be rooted struct RootedTraceableSet { set: Vec<*const JSTraceable>, diff --git a/components/script/dom/document.rs b/components/script/dom/document.rs index 39f5a8e6d38..3105f0f4341 100644 --- a/components/script/dom/document.rs +++ b/components/script/dom/document.rs @@ -36,6 +36,7 @@ use dom::bindings::xmlname::{namespace_from_domstring, validate_and_extract, xml use dom::bindings::xmlname::XMLName::InvalidXMLName; use dom::closeevent::CloseEvent; use dom::comment::Comment; +use dom::cssstylesheet::CSSStyleSheet; use dom::customelementregistry::CustomElementDefinition; use dom::customevent::CustomEvent; use dom::documentfragment::DocumentFragment; @@ -63,6 +64,7 @@ use dom::htmlheadelement::HTMLHeadElement; use dom::htmlhtmlelement::HTMLHtmlElement; use dom::htmliframeelement::HTMLIFrameElement; use dom::htmlimageelement::HTMLImageElement; +use dom::htmlmetaelement::HTMLMetaElement; use dom::htmlscriptelement::{HTMLScriptElement, ScriptResult}; use dom::htmltitleelement::HTMLTitleElement; use dom::keyboardevent::KeyboardEvent; @@ -135,10 +137,12 @@ use std::time::{Duration, Instant}; use style::attr::AttrValue; use style::context::{QuirksMode, ReflowGoal}; use style::invalidation::element::restyle_hints::{RestyleHint, RESTYLE_SELF, RESTYLE_STYLE_ATTRIBUTE}; +use style::media_queries::{Device, MediaList, MediaType}; use style::selector_parser::{RestyleDamage, Snapshot}; -use style::shared_lock::SharedRwLock as StyleSharedRwLock; +use style::shared_lock::{SharedRwLock as StyleSharedRwLock, SharedRwLockReadGuard}; use style::str::{HTML_SPACE_CHARACTERS, split_html_space_chars, str_join}; -use style::stylesheets::Stylesheet; +use style::stylesheet_set::StylesheetSet; +use style::stylesheets::{Stylesheet, StylesheetContents, OriginSet}; use task_source::TaskSource; use time; use timers::OneshotTimerCallback; @@ -166,14 +170,6 @@ pub enum IsHTMLDocument { NonHTMLDocument, } -#[derive(JSTraceable, HeapSizeOf)] -#[must_root] -pub struct StylesheetInDocument { - pub node: JS, - #[ignore_heap_size_of = "Arc"] - pub stylesheet: Arc, -} - #[derive(Debug, HeapSizeOf)] pub struct PendingRestyle { /// If this element had a state or attribute change since the last restyle, track @@ -197,6 +193,34 @@ impl PendingRestyle { } } +#[derive(Clone, JSTraceable, HeapSizeOf)] +#[must_root] +struct StyleSheetInDocument { + #[ignore_heap_size_of = "Arc"] + sheet: Arc, + owner: JS, +} + +impl PartialEq for StyleSheetInDocument { + fn eq(&self, other: &Self) -> bool { + Arc::ptr_eq(&self.sheet, &other.sheet) + } +} + +impl ::style::stylesheets::StylesheetInDocument for StyleSheetInDocument { + fn contents(&self, guard: &SharedRwLockReadGuard) -> &StylesheetContents { + self.sheet.contents(guard) + } + + fn media<'a>(&'a self, guard: &'a SharedRwLockReadGuard) -> Option<&'a MediaList> { + self.sheet.media(guard) + } + + fn enabled(&self) -> bool { + self.sheet.enabled() + } +} + /// https://dom.spec.whatwg.org/#document #[dom_struct] pub struct Document { @@ -229,9 +253,7 @@ pub struct Document { /// Can be acquired once for accessing many objects. style_shared_lock: StyleSharedRwLock, /// List of stylesheets associated with nodes in this document. |None| if the list needs to be refreshed. - stylesheets: DOMRefCell>>, - /// Whether the list of stylesheets has changed since the last reflow was triggered. - stylesheets_changed_since_reflow: Cell, + stylesheets: DOMRefCell>, stylesheet_list: MutNullableJS, ready_state: Cell, /// Whether the DOMContentLoaded event has already been dispatched. @@ -495,15 +517,12 @@ impl Document { // FIXME: This should check the dirty bit on the document, // not the document element. Needs some layout changes to make // that workable. - self.stylesheets_changed_since_reflow.get() || - match self.GetDocumentElement() { - Some(root) => { - root.upcast::().has_dirty_descendants() || - !self.pending_restyles.borrow().is_empty() || - self.needs_paint() - } - None => false, - } + self.stylesheets.borrow().has_changed() || + self.GetDocumentElement().map_or(false, |root| { + root.upcast::().has_dirty_descendants() || + !self.pending_restyles.borrow().is_empty() || + self.needs_paint() + }) } /// Returns the first `base` element in the DOM that has an `href` attribute. @@ -1501,20 +1520,16 @@ impl Document { } pub fn invalidate_stylesheets(&self) { - self.stylesheets_changed_since_reflow.set(true); - *self.stylesheets.borrow_mut() = None; + self.stylesheets.borrow_mut().force_dirty(OriginSet::all()); + // Mark the document element dirty so a reflow will be performed. + // + // FIXME(emilio): Use the StylesheetSet invalidation stuff. if let Some(element) = self.GetDocumentElement() { element.upcast::().dirty(NodeDamage::NodeStyleDamaged); } } - pub fn get_and_reset_stylesheets_changed_since_reflow(&self) -> bool { - let changed = self.stylesheets_changed_since_reflow.get(); - self.stylesheets_changed_since_reflow.set(false); - changed - } - pub fn trigger_mozbrowser_event(&self, event: MozBrowserEvent) { if PREFS.is_mozbrowser_enabled() { if let Some((parent_pipeline_id, _)) = self.window.parent_info() { @@ -2203,8 +2218,7 @@ impl Document { PER_PROCESS_AUTHOR_SHARED_LOCK.clone() //StyleSharedRwLock::new() }, - stylesheets: DOMRefCell::new(None), - stylesheets_changed_since_reflow: Cell::new(false), + stylesheets: DOMRefCell::new(StylesheetSet::new()), stylesheet_list: MutNullableJS::new(None), ready_state: Cell::new(ready_state), domcontentloaded_dispatched: Cell::new(domcontentloaded_dispatched), @@ -2315,40 +2329,113 @@ impl Document { self.GetDocumentElement().and_then(Root::downcast) } - // Ensure that the stylesheets vector is populated - fn ensure_stylesheets(&self) { - let mut stylesheets = self.stylesheets.borrow_mut(); - if stylesheets.is_none() { - *stylesheets = Some(self.upcast::() - .traverse_preorder() - .filter_map(|node| { - node.get_stylesheet() - .map(|stylesheet| StylesheetInDocument { - node: JS::from_ref(&*node), - stylesheet: stylesheet, - }) - }) - .collect()); - }; - } - /// Return a reference to the per-document shared lock used in stylesheets. pub fn style_shared_lock(&self) -> &StyleSharedRwLock { &self.style_shared_lock } - /// Returns the list of stylesheets associated with nodes in the document. - pub fn stylesheets(&self) -> Vec> { - self.ensure_stylesheets(); - self.stylesheets.borrow().as_ref().unwrap().iter() - .map(|s| s.stylesheet.clone()) - .collect() + /// Flushes the stylesheet list, and returns whether any stylesheet changed. + pub fn flush_stylesheets_for_reflow(&self) -> bool { + // NOTE(emilio): The invalidation machinery is used on the replicated + // list on the layout thread. + let mut stylesheets = self.stylesheets.borrow_mut(); + let have_changed = stylesheets.has_changed(); + stylesheets.flush_without_invalidation(); + have_changed } - pub fn with_style_sheets_in_document(&self, mut f: F) -> T - where F: FnMut(&[StylesheetInDocument]) -> T { - self.ensure_stylesheets(); - f(&self.stylesheets.borrow().as_ref().unwrap()) + /// Returns a `Device` suitable for media query evaluation. + /// + /// FIXME(emilio): This really needs to be somehow more in sync with layout. + /// Feels like a hack. + /// + /// Also, shouldn't return an option, I'm quite sure. + pub fn device(&self) -> Option { + let window_size = match self.window().window_size() { + Some(ws) => ws, + None => return None, + }; + + let viewport_size = window_size.initial_viewport; + let device_pixel_ratio = window_size.device_pixel_ratio; + Some(Device::new(MediaType::screen(), viewport_size, device_pixel_ratio)) + } + + /// Remove a stylesheet owned by `owner` from the list of document sheets. + #[allow(unrooted_must_root)] // Owner needs to be rooted already necessarily. + pub fn remove_stylesheet(&self, owner: &Element, s: &Arc) { + self.window() + .layout_chan() + .send(Msg::RemoveStylesheet(s.clone())) + .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(), + StyleSheetInDocument { + sheet: s.clone(), + owner: JS::from_ref(owner), + }, + &guard, + ); + } + + /// Add a stylesheet owned by `owner` to the list of document sheets, in the + /// correct tree position. + #[allow(unrooted_must_root)] // Owner needs to be rooted already necessarily. + pub fn add_stylesheet(&self, owner: &Element, sheet: Arc) { + // FIXME(emilio): It'd be nice to unify more code between the elements + // that own stylesheets, but StylesheetOwner is more about loading + // them... + debug_assert!(owner.as_stylesheet_owner().is_some() || + owner.is::(), "Wat"); + + let mut stylesheets = self.stylesheets.borrow_mut(); + let insertion_point = stylesheets.iter().find(|sheet_in_doc| { + owner.upcast::().is_before(sheet_in_doc.owner.upcast()) + }).cloned(); + + self.window() + .layout_chan() + .send(Msg::AddStylesheet( + sheet.clone(), + insertion_point.as_ref().map(|s| s.sheet.clone()) + )) + .unwrap(); + + let sheet = StyleSheetInDocument { + sheet, + owner: JS::from_ref(owner), + }; + + 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); + } + None => { + stylesheets.append_stylesheet(device.as_ref(), sheet, &guard); + } + } + } + + /// Returns the number of document stylesheets. + pub fn stylesheet_count(&self) -> usize { + self.stylesheets.borrow().len() + } + + pub fn stylesheet_at(&self, index: usize) -> Option> { + let stylesheets = self.stylesheets.borrow(); + + stylesheets.get(index).and_then(|s| { + s.owner.upcast::().get_cssom_stylesheet() + }) } /// https://html.spec.whatwg.org/multipage/#appropriate-template-contents-owner-document @@ -3684,8 +3771,7 @@ impl DocumentMethods for Document { self.scripts.set(None); self.anchors.set(None); self.applets.set(None); - *self.stylesheets.borrow_mut() = None; - self.stylesheets_changed_since_reflow.set(true); + *self.stylesheets.borrow_mut() = StylesheetSet::new(); self.animation_frame_ident.set(0); self.animation_frame_list.borrow_mut().clear(); self.pending_restyles.borrow_mut().clear(); diff --git a/components/script/dom/htmllinkelement.rs b/components/script/dom/htmllinkelement.rs index e30dbbc783d..d7e5c071f50 100644 --- a/components/script/dom/htmllinkelement.rs +++ b/components/script/dom/htmllinkelement.rs @@ -24,7 +24,6 @@ use dom::virtualmethods::VirtualMethods; use dom_struct::dom_struct; use html5ever::{LocalName, Prefix}; use net_traits::ReferrerPolicy; -use script_layout_interface::message::Msg; use script_traits::{MozBrowserEvent, ScriptMsg}; use servo_arc::Arc; use std::ascii::AsciiExt; @@ -39,8 +38,6 @@ use style::stylesheets::{CssRuleType, Stylesheet}; use style_traits::PARSING_MODE_DEFAULT; use stylesheet_loader::{StylesheetLoader, StylesheetContextSource, StylesheetOwner}; -unsafe_no_jsmanaged_fields!(Stylesheet); - #[derive(JSTraceable, PartialEq, Clone, Copy, HeapSizeOf)] pub struct RequestGenerationId(u32); @@ -98,10 +95,16 @@ impl HTMLLinkElement { self.request_generation_id.get() } + // FIXME(emilio): These methods are duplicated with + // HTMLStyleElement::set_stylesheet. pub fn set_stylesheet(&self, s: Arc) { + let doc = document_from_node(self); + if let Some(ref s) = *self.stylesheet.borrow() { + doc.remove_stylesheet(self.upcast(), s) + } *self.stylesheet.borrow_mut() = Some(s.clone()); - window_from_node(self).layout_chan().send(Msg::AddStylesheet(s)).unwrap(); - document_from_node(self).invalidate_stylesheets(); + self.cssom_stylesheet.set(None); + doc.add_stylesheet(self.upcast(), s); } pub fn get_stylesheet(&self) -> Option> { @@ -239,8 +242,9 @@ impl VirtualMethods for HTMLLinkElement { s.unbind_from_tree(context); } - let document = document_from_node(self); - document.invalidate_stylesheets(); + if let Some(ref s) = *self.stylesheet.borrow() { + document_from_node(self).remove_stylesheet(self.upcast(), s); + } } } diff --git a/components/script/dom/htmlmetaelement.rs b/components/script/dom/htmlmetaelement.rs index 481da1c092f..d2cb332e7e4 100644 --- a/components/script/dom/htmlmetaelement.rs +++ b/components/script/dom/htmlmetaelement.rs @@ -99,10 +99,10 @@ impl HTMLMetaElement { let content = content.value(); if !content.is_empty() { if let Some(translated_rule) = ViewportRule::from_meta(&**content) { - let document = self.upcast::().owner_doc(); + let document = document_from_node(self); let shared_lock = document.style_shared_lock(); let rule = CssRule::Viewport(Arc::new(shared_lock.wrap(translated_rule))); - *self.stylesheet.borrow_mut() = Some(Arc::new(Stylesheet { + let sheet = Arc::new(Stylesheet { contents: StylesheetContents { rules: CssRules::new(vec![rule], shared_lock), origin: Origin::Author, @@ -118,9 +118,9 @@ impl HTMLMetaElement { media: Arc::new(shared_lock.wrap(MediaList::empty())), shared_lock: shared_lock.clone(), disabled: AtomicBool::new(false), - })); - let doc = document_from_node(self); - doc.invalidate_stylesheets(); + }); + *self.stylesheet.borrow_mut() = Some(sheet.clone()); + document.add_stylesheet(self.upcast(), sheet); } } } @@ -199,6 +199,10 @@ impl VirtualMethods for HTMLMetaElement { if context.tree_in_doc { self.process_referrer_attribute(); + + if let Some(ref s) = *self.stylesheet.borrow() { + document_from_node(self).remove_stylesheet(self.upcast(), s); + } } } } diff --git a/components/script/dom/htmlstyleelement.rs b/components/script/dom/htmlstyleelement.rs index 9c6f34ba7bd..222f25a9b1d 100644 --- a/components/script/dom/htmlstyleelement.rs +++ b/components/script/dom/htmlstyleelement.rs @@ -20,7 +20,6 @@ use dom::virtualmethods::VirtualMethods; use dom_struct::dom_struct; use html5ever::{LocalName, Prefix}; use net_traits::ReferrerPolicy; -use script_layout_interface::message::Msg; use servo_arc::Arc; use std::cell::Cell; use style::media_queries::parse_media_query_list; @@ -109,10 +108,18 @@ impl HTMLStyleElement { self.upcast::().fire_event(atom!("load")); } - win.layout_chan().send(Msg::AddStylesheet(sheet.clone())).unwrap(); - *self.stylesheet.borrow_mut() = Some(sheet); + self.set_stylesheet(sheet); + } + + // FIXME(emilio): This is duplicated with HTMLLinkElement::set_stylesheet. + pub fn set_stylesheet(&self, s: Arc) { + let doc = document_from_node(self); + if let Some(ref s) = *self.stylesheet.borrow() { + doc.remove_stylesheet(self.upcast(), s) + } + *self.stylesheet.borrow_mut() = Some(s.clone()); self.cssom_stylesheet.set(None); - doc.invalidate_stylesheets(); + doc.add_stylesheet(self.upcast(), s); } pub fn get_stylesheet(&self) -> Option> { @@ -180,8 +187,12 @@ impl VirtualMethods for HTMLStyleElement { s.unbind_from_tree(context); } - let doc = document_from_node(self); - doc.invalidate_stylesheets(); + if context.tree_in_doc { + if let Some(ref s) = *self.stylesheet.borrow() { + let doc = document_from_node(self); + doc.remove_stylesheet(self.upcast(), s) + } + } } } diff --git a/components/script/dom/mediaquerylist.rs b/components/script/dom/mediaquerylist.rs index 60cb875163e..199241b14dd 100644 --- a/components/script/dom/mediaquerylist.rs +++ b/components/script/dom/mediaquerylist.rs @@ -22,7 +22,7 @@ use dom_struct::dom_struct; use js::jsapi::JSTracer; use std::cell::Cell; use std::rc::Rc; -use style::media_queries::{Device, MediaList, MediaType}; +use style::media_queries::MediaList; use style_traits::ToCss; pub enum MediaQueryListMatchState { @@ -74,14 +74,9 @@ impl MediaQueryList { } pub fn evaluate(&self) -> bool { - if let Some(window_size) = self.document.window().window_size() { - let viewport_size = window_size.initial_viewport; - let device_pixel_ratio = window_size.device_pixel_ratio; - let device = Device::new(MediaType::screen(), viewport_size, device_pixel_ratio); + self.document.device().map_or(false, |device| { self.media_query_list.evaluate(&device, self.document.quirks_mode()) - } else { - false - } + }) } } diff --git a/components/script/dom/stylesheetlist.rs b/components/script/dom/stylesheetlist.rs index 25d95ae3986..faeb04c1dda 100644 --- a/components/script/dom/stylesheetlist.rs +++ b/components/script/dom/stylesheetlist.rs @@ -36,18 +36,14 @@ impl StyleSheetList { impl StyleSheetListMethods for StyleSheetList { // https://drafts.csswg.org/cssom/#dom-stylesheetlist-length fn Length(&self) -> u32 { - self.document.with_style_sheets_in_document(|s| s.len() as u32) + self.document.stylesheet_count() as u32 } // https://drafts.csswg.org/cssom/#dom-stylesheetlist-item fn Item(&self, index: u32) -> Option> { - // XXXManishearth this doesn't handle the origin clean flag - // and is a cors vulnerability - self.document.with_style_sheets_in_document(|sheets| { - sheets.get(index as usize) - .and_then(|sheet| sheet.node.get_cssom_stylesheet()) - .map(Root::upcast) - }) + // XXXManishearth this doesn't handle the origin clean flag and is a + // cors vulnerability + self.document.stylesheet_at(index as usize).map(Root::upcast) } // check-tidy: no specs after this line diff --git a/components/script/dom/window.rs b/components/script/dom/window.rs index 48677a64d38..d45a6b9bc17 100644 --- a/components/script/dom/window.rs +++ b/components/script/dom/window.rs @@ -1240,7 +1240,8 @@ impl Window { } let document = self.Document(); - let stylesheets_changed = document.get_and_reset_stylesheets_changed_since_reflow(); + + let stylesheets_changed = document.flush_stylesheets_for_reflow(); // Send new document and relevant styles to layout. let reflow = ScriptReflow { @@ -1249,11 +1250,10 @@ impl Window { page_clip_rect: self.page_clip_rect.get(), }, document: self.Document().upcast::().to_trusted_node_address(), - document_stylesheets: document.stylesheets(), - stylesheets_changed: stylesheets_changed, - window_size: window_size, + stylesheets_changed, + window_size, + query_type, script_join_chan: join_chan, - query_type: query_type, dom_count: self.Document().dom_count(), }; diff --git a/components/script_layout_interface/message.rs b/components/script_layout_interface/message.rs index e74447ade26..91963bce226 100644 --- a/components/script_layout_interface/message.rs +++ b/components/script_layout_interface/message.rs @@ -27,8 +27,13 @@ use style::stylesheets::Stylesheet; /// Asynchronous messages that script can send to layout. pub enum Msg { - /// Adds the given stylesheet to the document. - AddStylesheet(ServoArc), + /// Adds the given stylesheet to the document. The second stylesheet is the + /// insertion point (if it exists, the sheet needs to be inserted before + /// it). + AddStylesheet(ServoArc, Option>), + + /// Removes a stylesheet from the document. + RemoveStylesheet(ServoArc), /// Change the quirks mode. SetQuirksMode(QuirksMode), @@ -137,8 +142,6 @@ pub struct ScriptReflow { pub reflow_info: Reflow, /// The document node. pub document: TrustedNodeAddress, - /// The document's list of stylesheets. - pub document_stylesheets: Vec>, /// Whether the document's stylesheets have changed since the last script reflow. pub stylesheets_changed: bool, /// The current window size. diff --git a/components/style/invalidation/stylesheets.rs b/components/style/invalidation/stylesheets.rs index 04a13d934ba..71a1b29e5c0 100644 --- a/components/style/invalidation/stylesheets.rs +++ b/components/style/invalidation/stylesheets.rs @@ -11,16 +11,17 @@ use Atom; use dom::{TElement, TNode}; use fnv::FnvHashSet; use invalidation::element::restyle_hints::RestyleHint; +use media_queries::Device; use selector_parser::SelectorImpl; use selectors::attr::CaseSensitivity; use selectors::parser::{Component, Selector}; use shared_lock::SharedRwLockReadGuard; use stylesheets::{CssRule, StylesheetInDocument}; -use stylist::Stylist; /// An invalidation scope represents a kind of subtree that may need to be /// restyled. #[derive(Debug, Hash, Eq, PartialEq)] +#[cfg_attr(feature = "servo", derive(HeapSizeOf))] enum InvalidationScope { /// All the descendants of an element with a given id. ID(Atom), @@ -54,6 +55,7 @@ impl InvalidationScope { /// /// TODO(emilio): We might be able to do the same analysis for removals and /// media query changes too? +#[cfg_attr(feature = "servo", derive(HeapSizeOf))] pub struct StylesheetInvalidationSet { /// The style scopes we know we have to restyle so far. invalid_scopes: FnvHashSet, @@ -82,7 +84,7 @@ impl StylesheetInvalidationSet { /// next time. pub fn collect_invalidations_for( &mut self, - stylist: &Stylist, + device: &Device, stylesheet: &S, guard: &SharedRwLockReadGuard ) @@ -96,12 +98,12 @@ impl StylesheetInvalidationSet { } if !stylesheet.enabled() || - !stylesheet.is_effective_for_device(stylist.device(), guard) { + !stylesheet.is_effective_for_device(device, guard) { debug!(" > Stylesheet was not effective"); return; // Nothing to do here. } - for rule in stylesheet.effective_rules(stylist.device(), guard) { + for rule in stylesheet.effective_rules(device, guard) { self.collect_invalidations_for_rule(rule, guard); if self.fully_invalid { self.invalid_scopes.clear(); @@ -121,6 +123,11 @@ impl StylesheetInvalidationSet { if let Some(e) = document_element { self.process_invalidations(e); } + self.clear(); + } + + /// Clears the invalidation set without processing. + pub fn clear(&mut self) { self.invalid_scopes.clear(); self.fully_invalid = false; } diff --git a/components/style/stylesheet_set.rs b/components/style/stylesheet_set.rs index 76d6b592a86..9d9578d1909 100644 --- a/components/style/stylesheet_set.rs +++ b/components/style/stylesheet_set.rs @@ -6,13 +6,14 @@ use dom::TElement; use invalidation::stylesheets::StylesheetInvalidationSet; +use media_queries::Device; use shared_lock::SharedRwLockReadGuard; use std::slice; use stylesheets::{OriginSet, PerOrigin, StylesheetInDocument}; -use stylist::Stylist; /// Entry for a StylesheetSet. We don't bother creating a constructor, because /// there's no sensible defaults for the member variables. +#[cfg_attr(feature = "servo", derive(HeapSizeOf))] pub struct StylesheetSetEntry where S: StylesheetInDocument + PartialEq + 'static, @@ -38,6 +39,7 @@ where } /// The set of stylesheets effective for a given document. +#[cfg_attr(feature = "servo", derive(HeapSizeOf))] pub struct StylesheetSet where S: StylesheetInDocument + PartialEq + 'static, @@ -69,6 +71,16 @@ where } } + /// Returns the number of stylesheets in the set. + pub fn len(&self) -> usize { + self.entries.len() + } + + /// Returns the number of stylesheets in the set. + pub fn get(&self, index: usize) -> Option<&S> { + self.entries.get(index).map(|s| &s.sheet) + } + /// Returns whether author styles have been disabled for the current /// stylesheet set. pub fn author_style_disabled(&self) -> bool { @@ -81,46 +93,57 @@ where fn collect_invalidations_for( &mut self, - stylist: &Stylist, + device: Option<&Device>, sheet: &S, guard: &SharedRwLockReadGuard, ) { let origin = sheet.contents(guard).origin; let data = self.invalidation_data.borrow_mut_for_origin(&origin); - data.invalidations.collect_invalidations_for(stylist, sheet, guard); + if let Some(device) = device { + data.invalidations.collect_invalidations_for(device, sheet, guard); + } data.dirty = true; } /// 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. pub fn append_stylesheet( &mut self, - stylist: &Stylist, + device: Option<&Device>, sheet: S, guard: &SharedRwLockReadGuard ) { debug!("StylesheetSet::append_stylesheet"); self.remove_stylesheet_if_present(&sheet); - self.collect_invalidations_for(stylist, &sheet, guard); + self.collect_invalidations_for(device, &sheet, guard); self.entries.push(StylesheetSetEntry { sheet }); } /// 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, - stylist: &Stylist, + device: Option<&Device>, sheet: S, guard: &SharedRwLockReadGuard ) { debug!("StylesheetSet::prepend_stylesheet"); self.remove_stylesheet_if_present(&sheet); - self.collect_invalidations_for(stylist, &sheet, guard); + self.collect_invalidations_for(device, &sheet, guard); self.entries.insert(0, StylesheetSetEntry { sheet }); } /// 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, - stylist: &Stylist, + device: Option<&Device>, sheet: S, before_sheet: S, guard: &SharedRwLockReadGuard @@ -130,20 +153,23 @@ where let index = self.entries.iter().position(|entry| { entry.sheet == before_sheet }).expect("`before_sheet` stylesheet not found"); - self.collect_invalidations_for(stylist, &sheet, guard); + self.collect_invalidations_for(device, &sheet, guard); self.entries.insert(index, StylesheetSetEntry { sheet }); } /// 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, - stylist: &Stylist, + device: Option<&Device>, sheet: S, guard: &SharedRwLockReadGuard, ) { debug!("StylesheetSet::remove_stylesheet"); self.remove_stylesheet_if_present(&sheet); - self.collect_invalidations_for(stylist, &sheet, guard); + self.collect_invalidations_for(device, &sheet, guard); } /// Notes that the author style has been disabled for this document. @@ -174,7 +200,6 @@ where E: TElement, { debug!("StylesheetSet::flush"); - debug_assert!(self.has_changed()); let mut origins = OriginSet::empty(); for (data, origin) in self.invalidation_data.iter_mut_origins() { @@ -188,6 +213,26 @@ where (self.iter(), origins) } + /// Flush stylesheets, but without running any of the invalidation passes. + /// + /// FIXME(emilio): This should eventually disappear. Please keep this + /// Servo-only. + #[cfg(feature = "servo")] + pub fn flush_without_invalidation(&mut self) -> (StylesheetIterator, OriginSet) { + debug!("StylesheetSet::flush_without_invalidation"); + + let mut origins = OriginSet::empty(); + for (data, origin) in self.invalidation_data.iter_mut_origins() { + if data.dirty { + data.invalidations.clear(); + data.dirty = false; + origins |= origin; + } + } + + (self.iter(), origins) + } + /// Returns an iterator over the current list of stylesheets. pub fn iter(&self) -> StylesheetIterator { StylesheetIterator(self.entries.iter()) @@ -204,6 +249,7 @@ where } } +#[cfg_attr(feature = "servo", derive(HeapSizeOf))] struct InvalidationData { /// The stylesheet invalidations for this origin that we still haven't /// processed. diff --git a/components/style/stylist.rs b/components/style/stylist.rs index b0332d71097..93d538d4c9c 100644 --- a/components/style/stylist.rs +++ b/components/style/stylist.rs @@ -196,16 +196,19 @@ impl Stylist { author_style_disabled: bool, extra_data: &mut PerOrigin, mut origins_to_rebuild: OriginSet, - ) -> bool + ) -> OriginSet where I: Iterator + Clone, S: StylesheetInDocument + ToMediaListKey + 'static, { - debug_assert!(!origins_to_rebuild.is_empty() || self.is_device_dirty); if self.is_device_dirty { origins_to_rebuild = OriginSet::all(); } + if origins_to_rebuild.is_empty() { + return origins_to_rebuild; + } + self.num_rebuilds += 1; // Update viewport_constraints regardless of which origins' @@ -279,37 +282,7 @@ impl Stylist { } self.is_device_dirty = false; - true - } - - /// clear the stylist and then rebuild it. Chances are, you want to use - /// either clear() or rebuild(), with the latter done lazily, instead. - pub fn update<'a, I, S>( - &mut self, - doc_stylesheets: I, - guards: &StylesheetGuards, - ua_stylesheets: Option<&UserAgentStylesheets>, - stylesheets_changed: bool, - author_style_disabled: bool, - extra_data: &mut PerOrigin - ) -> bool - where - I: Iterator + Clone, - S: StylesheetInDocument + ToMediaListKey + 'static, - { - // We have to do a dirtiness check before clearing, because if - // we're not actually dirty we need to no-op here. - if !(self.is_device_dirty || stylesheets_changed) { - return false; - } - self.rebuild( - doc_stylesheets, - guards, - ua_stylesheets, - author_style_disabled, - extra_data, - OriginSet::all(), - ) + origins_to_rebuild } fn add_stylesheet( @@ -876,13 +849,19 @@ 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(&mut self, - mut device: Device, - guard: &SharedRwLockReadGuard, - stylesheets: &[Arc<::stylesheets::Stylesheet>]) { + pub fn set_device<'a, I, S>( + &mut self, + mut device: Device, + guard: &SharedRwLockReadGuard, + stylesheets: I, + ) + where + I: Iterator + Clone, + S: StylesheetInDocument + ToMediaListKey + 'static, + { let cascaded_rule = ViewportRule { declarations: viewport_rule::Cascade::from_stylesheets( - stylesheets.iter().map(|s| &**s), + stylesheets.clone(), guard, &device ).finish(), @@ -897,7 +876,7 @@ impl Stylist { self.device = device; let features_changed = self.media_features_change_changed_style( - stylesheets.iter().map(|s| &**s), + stylesheets, guard ); self.is_device_dirty |= !features_changed.is_empty(); diff --git a/ports/geckolib/glue.rs b/ports/geckolib/glue.rs index b362f768afb..640fc9d5640 100644 --- a/ports/geckolib/glue.rs +++ b/ports/geckolib/glue.rs @@ -891,7 +891,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(&data.stylist, sheet, &guard); + data.stylesheets.append_stylesheet(Some(data.stylist.device()), sheet, &guard); } #[no_mangle] @@ -940,7 +940,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(&data.stylist, sheet, &guard); + data.stylesheets.prepend_stylesheet(Some(data.stylist.device()), sheet, &guard); } #[no_mangle] @@ -955,7 +955,7 @@ pub extern "C" fn Servo_StyleSet_InsertStyleSheetBefore( let guard = global_style_data.shared_lock.read(); let sheet = unsafe { GeckoStyleSheet::new(sheet) }; data.stylesheets.insert_stylesheet_before( - &data.stylist, + Some(data.stylist.device()), sheet, unsafe { GeckoStyleSheet::new(before_sheet) }, &guard, @@ -972,7 +972,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(&data.stylist, sheet, &guard); + data.stylesheets.remove_stylesheet(Some(data.stylist.device()), sheet, &guard); } #[no_mangle]