style: Replicate the list of stylesheets on the layout thread.

This is a patch that unifies a bit how Gecko and Stylo stylesheets work, in
order to be able to eventually move the stylesheets into the stylist, and be
able to incrementally update the invalidation map.
This commit is contained in:
Emilio Cobos Álvarez 2017-08-16 15:46:17 +02:00
parent b8159e659e
commit d1725b1f19
No known key found for this signature in database
GPG key ID: 056B727BB9C1027C
14 changed files with 413 additions and 198 deletions

View file

@ -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<MediaList> {
}
}
unsafe impl<S> JSTraceable for StylesheetSet<S>
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>,

View file

@ -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<Node>,
#[ignore_heap_size_of = "Arc"]
pub stylesheet: Arc<Stylesheet>,
}
#[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<Stylesheet>,
owner: JS<Element>,
}
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<Option<Vec<StylesheetInDocument>>>,
/// Whether the list of stylesheets has changed since the last reflow was triggered.
stylesheets_changed_since_reflow: Cell<bool>,
stylesheets: DOMRefCell<StylesheetSet<StyleSheetInDocument>>,
stylesheet_list: MutNullableJS<StyleSheetList>,
ready_state: Cell<DocumentReadyState>,
/// 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::<Node>().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::<Node>().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::<Node>().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::<Node>()
.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<Arc<Stylesheet>> {
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<F, T>(&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<Device> {
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<Stylesheet>) {
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<Stylesheet>) {
// 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::<HTMLMetaElement>(), "Wat");
let mut stylesheets = self.stylesheets.borrow_mut();
let insertion_point = stylesheets.iter().find(|sheet_in_doc| {
owner.upcast::<Node>().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<Root<CSSStyleSheet>> {
let stylesheets = self.stylesheets.borrow();
stylesheets.get(index).and_then(|s| {
s.owner.upcast::<Node>().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();

View file

@ -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<Stylesheet>) {
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<Arc<Stylesheet>> {
@ -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);
}
}
}

View file

@ -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::<Node>().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);
}
}
}
}

View file

@ -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::<EventTarget>().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<Stylesheet>) {
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<Arc<Stylesheet>> {
@ -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)
}
}
}
}

View file

@ -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
}
})
}
}

View file

@ -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<Root<StyleSheet>> {
// 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

View file

@ -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::<Node>().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(),
};