Skip to content

Commit

Permalink
Move Stylesheet loading and ownership from the layout task into HTML …
Browse files Browse the repository at this point in the history
…elements

Stylesheets for `HTMLLinkElement`s are now loaded by the resource task, triggered by the element in question. Stylesheets are owned by the elements they're associated with, which can be `HTMLStyleElement`, `HTMLLinkElement`, and `HTMLMetaElement` (for `<meta name="viewport">).

Additionally, the quirks mode stylesheet (just as the user and user agent stylesheets a couple of commits ago), is implemented as a lazy static, loaded once per process and shared between all documents.

This all has various nice consequences:
 - Stylesheet loading becomes a non-blocking operation.
 - Stylesheets are removed when the element they're associated with is removed from the document.
 - It'll be possible to implement the CSSOM APIs that require direct access to the stylesheets (i.e., ~ all of them).
 - Various subtle correctness issues are fixed.

One piece of interesting follow-up work would be to move parsing of external stylesheets to the resource task, too. Right now, it happens in the link element once loading is complete, so blocks the script task. Moving it to the resource task would probably be fairly straight-forward as it doesn't require access to any external state.
  • Loading branch information
tschneidereit committed Nov 7, 2015
1 parent 068e6a8 commit 543703e
Show file tree
Hide file tree
Showing 15 changed files with 387 additions and 281 deletions.
97 changes: 19 additions & 78 deletions components/layout/layout_task.rs
Expand Up @@ -16,8 +16,6 @@ use context::{SharedLayoutContext, heap_size_of_local_context};
use cssparser::ToCss;
use data::LayoutDataWrapper;
use display_list_builder::ToGfxColor;
use encoding::EncodingRef;
use encoding::all::UTF_8;
use euclid::Matrix4;
use euclid::point::Point2D;
use euclid::rect::Rect;
Expand All @@ -42,7 +40,6 @@ use msg::compositor_msg::{Epoch, LayerId, ScrollPolicy};
use msg::constellation_msg::Msg as ConstellationMsg;
use msg::constellation_msg::{ConstellationChan, Failure, PipelineExitType, PipelineId};
use net_traits::image_cache_task::{ImageCacheChan, ImageCacheResult, ImageCacheTask};
use net_traits::{PendingAsyncLoad, load_bytes_iter};
use opaque_node::OpaqueNodeMethods;
use parallel::{self, WorkQueueData};
use profile_traits::mem::{self, Report, ReportKind, ReportsChan};
Expand All @@ -56,7 +53,6 @@ use script::layout_interface::Animation;
use script::layout_interface::{LayoutChan, LayoutRPC, OffsetParentResponse};
use script::layout_interface::{Msg, NewLayoutTaskInfo, Reflow, ReflowGoal, ReflowQueryType};
use script::layout_interface::{ScriptLayoutChan, ScriptReflow, TrustedNodeAddress};
use script_traits::StylesheetLoadResponder;
use script_traits::{ConstellationControlMsg, LayoutControlMsg, OpaqueScriptLayoutChannel};
use selectors::parser::PseudoElement;
use sequential;
Expand All @@ -72,13 +68,12 @@ use std::sync::mpsc::{channel, Sender, Receiver};
use std::sync::{Arc, Mutex, MutexGuard};
use string_cache::Atom;
use style::computed_values::{self, filter, mix_blend_mode};
use style::media_queries::{Device, MediaQueryList, MediaType};
use style::media_queries::{Device, MediaType};
use style::properties::longhands::{display, position};
use style::properties::style_structs;
use style::selector_matching::{Stylist, USER_OR_USER_AGENT_STYLESHEETS};
use style::stylesheets::{CSSRule, CSSRuleIteratorExt, Origin, Stylesheet};
use style::stylesheets::{CSSRuleIteratorExt, Stylesheet};
use style::values::AuExtensionMethods;
use style::viewport::ViewportRule;
use url::Url;
use util::geometry::{MAX_RECT, ZERO_POINT};
use util::ipc::OptionalIpcSender;
Expand Down Expand Up @@ -576,20 +571,10 @@ impl LayoutTask {
LayoutTaskData>>)
-> bool {
match request {
Msg::AddStylesheet(sheet, mq) => {
self.handle_add_stylesheet(sheet, mq, possibly_locked_rw_data)
}
Msg::LoadStylesheet(url, mq, pending, link_element) => {
self.handle_load_stylesheet(url,
mq,
pending,
link_element,
possibly_locked_rw_data)
Msg::AddStylesheet(style_info) => {
self.handle_add_stylesheet(style_info, possibly_locked_rw_data)
}
Msg::SetQuirksMode => self.handle_set_quirks_mode(possibly_locked_rw_data),
Msg::AddMetaViewport(translated_rule) => {
self.handle_add_meta_viewport(translated_rule, possibly_locked_rw_data)
}
Msg::GetRPC(response_chan) => {
response_chan.send(box LayoutRPCImpl(self.rw_data.clone()) as
Box<LayoutRPC + Send>).unwrap();
Expand Down Expand Up @@ -745,75 +730,31 @@ impl LayoutTask {
response_port.recv().unwrap()
}

fn handle_load_stylesheet<'a>(&'a self,
url: Url,
mq: MediaQueryList,
pending: PendingAsyncLoad,
responder: Box<StylesheetLoadResponder + Send>,
possibly_locked_rw_data:
&mut Option<MutexGuard<'a, LayoutTaskData>>) {
// TODO: Get the actual value. http://dev.w3.org/csswg/css-syntax/#environment-encoding
let environment_encoding = UTF_8 as EncodingRef;

// TODO we don't really even need to load this if mq does not match
let (metadata, iter) = load_bytes_iter(pending);
let protocol_encoding_label = metadata.charset.as_ref().map(|s| &**s);
let final_url = metadata.final_url;

let sheet = Stylesheet::from_bytes_iter(iter,
final_url,
protocol_encoding_label,
Some(environment_encoding),
Origin::Author);

//TODO: mark critical subresources as blocking load as well (#5974)
self.script_chan.send(ConstellationControlMsg::StylesheetLoadComplete(self.id,
url,
responder)).unwrap();

self.handle_add_stylesheet(sheet, mq, possibly_locked_rw_data);
}

fn handle_add_stylesheet<'a>(&'a self,
sheet: Stylesheet,
mq: MediaQueryList,
stylesheet: Arc<Stylesheet>,
possibly_locked_rw_data:
&mut Option<MutexGuard<'a, LayoutTaskData>>) {
// Find all font-face rules and notify the font cache of them.
// GWTODO: Need to handle unloading web fonts (when we handle unloading stylesheets!)
// GWTODO: Need to handle unloading web fonts.

let mut rw_data = self.lock_rw_data(possibly_locked_rw_data);
if mq.evaluate(&rw_data.stylist.device) {
add_font_face_rules(&sheet,
let rw_data = self.lock_rw_data(possibly_locked_rw_data);
if stylesheet.is_effective_for_device(&rw_data.stylist.device) {
add_font_face_rules(&*stylesheet,
&rw_data.stylist.device,
&self.font_cache_task,
&self.font_cache_sender,
&rw_data.outstanding_web_fonts);
rw_data.stylist.add_stylesheet(sheet);
}

LayoutTask::return_rw_data(possibly_locked_rw_data, rw_data);
}

fn handle_add_meta_viewport<'a>(&'a self,
translated_rule: ViewportRule,
possibly_locked_rw_data:
&mut Option<MutexGuard<'a, LayoutTaskData>>)
{
let mut rw_data = self.lock_rw_data(possibly_locked_rw_data);
rw_data.stylist.add_stylesheet(Stylesheet {
rules: vec![CSSRule::Viewport(translated_rule)],
origin: Origin::Author
});
LayoutTask::return_rw_data(possibly_locked_rw_data, rw_data);
}

/// Sets quirks mode for the document, causing the quirks mode stylesheet to be loaded.
/// Sets quirks mode for the document, causing the quirks mode stylesheet to be used.
fn handle_set_quirks_mode<'a>(&'a self,
possibly_locked_rw_data:
&mut Option<MutexGuard<'a, LayoutTaskData>>) {
let mut rw_data = self.lock_rw_data(possibly_locked_rw_data);
rw_data.stylist.add_quirks_mode_stylesheet();
rw_data.stylist.set_quirks_mode(true);
LayoutTask::return_rw_data(possibly_locked_rw_data, rw_data);
}

Expand Down Expand Up @@ -1077,7 +1018,7 @@ impl LayoutTask {
flow_ref::deref_mut(layout_root));
let root_size = {
let root_flow = flow::base(&**layout_root);
if rw_data.stylist.get_viewport_constraints().is_some() {
if rw_data.stylist.viewport_constraints().is_some() {
root_flow.position.size.to_physical(root_flow.writing_mode)
} else {
root_flow.overflow.size
Expand Down Expand Up @@ -1153,6 +1094,9 @@ impl LayoutTask {
}

let mut rw_data = self.lock_rw_data(possibly_locked_rw_data);
let stylesheets: Vec<&Stylesheet> = data.document_stylesheets.iter().map(|entry| &**entry)
.collect();
let stylesheets_changed = data.stylesheets_changed;

let initial_viewport = data.window_size.initial_viewport;
let old_viewport_size = rw_data.viewport_size;
Expand All @@ -1162,9 +1106,9 @@ impl LayoutTask {

// Calculate the actual viewport as per DEVICE-ADAPT § 6
let device = Device::new(MediaType::Screen, initial_viewport);
rw_data.stylist.set_device(device);
rw_data.stylist.set_device(device, &stylesheets);

let constraints = rw_data.stylist.get_viewport_constraints();
let constraints = rw_data.stylist.viewport_constraints().clone();
rw_data.viewport_size = match constraints {
Some(ref constraints) => {
debug!("Viewport constraints: {:?}", constraints);
Expand All @@ -1180,9 +1124,6 @@ impl LayoutTask {
let viewport_size_changed = rw_data.viewport_size != old_viewport_size;
if viewport_size_changed {
if let Some(constraints) = constraints {
let device = Device::new(MediaType::Screen, constraints.size);
rw_data.stylist.set_device(device);

// let the constellation know about the viewport constraints
let ConstellationChan(ref constellation_chan) = rw_data.constellation_chan;
constellation_chan.send(ConstellationMsg::ViewportConstrained(
Expand All @@ -1191,7 +1132,7 @@ impl LayoutTask {
}

// If the entire flow tree is invalid, then it will be reflowed anyhow.
let needs_dirtying = rw_data.stylist.update();
let needs_dirtying = rw_data.stylist.update(&stylesheets, stylesheets_changed);
let needs_reflow = viewport_size_changed && !needs_dirtying;
unsafe {
if needs_dirtying {
Expand Down Expand Up @@ -1221,7 +1162,7 @@ impl LayoutTask {
&self.url,
data.reflow_info.goal);

if node.is_dirty() || node.has_dirty_descendants() || rw_data.stylist.is_dirty() {
if node.is_dirty() || node.has_dirty_descendants() {
// Recalculate CSS styles and rebuild flows and fragments.
profile(time::ProfilerCategory::LayoutStyleRecalc,
self.profiler_metadata(),
Expand Down
2 changes: 1 addition & 1 deletion components/script/dom/create.rs
Expand Up @@ -174,7 +174,7 @@ pub fn create_element(name: QualName, prefix: Option<Atom>,
atom!("label") => make!(HTMLLabelElement),
atom!("legend") => make!(HTMLLegendElement),
atom!("li") => make!(HTMLLIElement),
atom!("link") => make!(HTMLLinkElement),
atom!("link") => make!(HTMLLinkElement, creator),
// https://html.spec.whatwg.org/multipage/#other-elements,-attributes-and-apis:listing
atom!("listing") => make!(HTMLPreElement),
atom!("main") => make!(HTMLElement),
Expand Down
58 changes: 58 additions & 0 deletions components/script/dom/document.rs
Expand Up @@ -48,7 +48,10 @@ use dom::htmlheadelement::HTMLHeadElement;
use dom::htmlhtmlelement::HTMLHtmlElement;
use dom::htmliframeelement::{self, HTMLIFrameElement};
use dom::htmlimageelement::HTMLImageElement;
use dom::htmllinkelement::HTMLLinkElement;
use dom::htmlmetaelement::HTMLMetaElement;
use dom::htmlscriptelement::HTMLScriptElement;
use dom::htmlstyleelement::HTMLStyleElement;
use dom::htmltitleelement::HTMLTitleElement;
use dom::keyboardevent::KeyboardEvent;
use dom::location::Location;
Expand Down Expand Up @@ -96,8 +99,10 @@ use std::default::Default;
use std::iter::FromIterator;
use std::ptr;
use std::rc::Rc;
use std::sync::Arc;
use std::sync::mpsc::channel;
use string_cache::{Atom, QualName};
use style::stylesheets::Stylesheet;
use time;
use url::Url;
use util::str::{DOMString, split_html_space_chars, str_join};
Expand Down Expand Up @@ -135,6 +140,10 @@ pub struct Document {
scripts: MutNullableHeap<JS<HTMLCollection>>,
anchors: MutNullableHeap<JS<HTMLCollection>>,
applets: MutNullableHeap<JS<HTMLCollection>>,
/// List of stylesheets associated with nodes in this document. |None| if the list needs to be refreshed.
stylesheets: DOMRefCell<Option<Vec<Arc<Stylesheet>>>>,
/// Whether the list of stylesheets has changed since the last reflow was triggered.
stylesheets_changed_since_reflow: Cell<bool>,
ready_state: Cell<DocumentReadyState>,
/// Whether the DOMContentLoaded event has already been dispatched.
domcontentloaded_dispatched: Cell<bool>,
Expand Down Expand Up @@ -983,6 +992,21 @@ impl Document {
count_cell.set(count_cell.get() - 1);
}

pub fn invalidate_stylesheets(&self) {
self.stylesheets_changed_since_reflow.set(true);
*self.stylesheets.borrow_mut() = None;
// Mark the document element dirty so a reflow will be performed.
self.get_html_element().map(|root| {
root.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 set_pending_parsing_blocking_script(&self, script: Option<&HTMLScriptElement>) {
assert!(self.get_pending_parsing_blocking_script().is_none() || script.is_none());
self.pending_parsing_blocking_script.set(script);
Expand Down Expand Up @@ -1100,6 +1124,13 @@ impl Document {
if parser.is_suspended() {
parser.resume();
}
} else if self.reflow_timeout.get().is_none() {
// If we don't have a parser, and the reflow timer has been reset, explicitly
// trigger a reflow.
if let LoadType::Stylesheet(_) = load {
self.window().reflow(ReflowGoal::ForDisplay, ReflowQueryType::NoQuery,
ReflowReason::StylesheetLoaded);
}
}

let loader = self.loader.borrow();
Expand Down Expand Up @@ -1304,6 +1335,8 @@ impl Document {
scripts: Default::default(),
anchors: Default::default(),
applets: Default::default(),
stylesheets: DOMRefCell::new(None),
stylesheets_changed_since_reflow: Cell::new(false),
ready_state: Cell::new(ready_state),
domcontentloaded_dispatched: Cell::new(domcontentloaded_dispatched),
possibly_focused: Default::default(),
Expand Down Expand Up @@ -1369,6 +1402,31 @@ impl Document {
self.GetDocumentElement().and_then(Root::downcast)
}

/// Returns the list of stylesheets associated with nodes in the document.
pub fn stylesheets(&self) -> Ref<Vec<Arc<Stylesheet>>> {
{
let mut stylesheets = self.stylesheets.borrow_mut();
if stylesheets.is_none() {
let new_stylesheets: Vec<Arc<Stylesheet>> = self.upcast::<Node>()
.traverse_preorder()
.filter_map(|node| {
if let Some(node) = node.downcast::<HTMLStyleElement>() {
node.get_stylesheet()
} else if let Some(node) = node.downcast::<HTMLLinkElement>() {
node.get_stylesheet()
} else if let Some(node) = node.downcast::<HTMLMetaElement>() {
node.get_stylesheet()
} else {
None
}
})
.collect();
*stylesheets = Some(new_stylesheets);
};
}
Ref::map(self.stylesheets.borrow(), |t| t.as_ref().unwrap())
}

/// https://html.spec.whatwg.org/multipage/#appropriate-template-contents-owner-document
pub fn appropriate_template_contents_owner_document(&self) -> Root<Document> {
self.appropriate_template_contents_owner_document.or_init(|| {
Expand Down

0 comments on commit 543703e

Please sign in to comment.