Move Stylesheet loading and ownership from the layout task into HTML 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.
This commit is contained in:
Till Schneidereit 2015-10-06 15:55:28 +02:00
parent 068e6a8e9f
commit 543703e3d8
15 changed files with 391 additions and 285 deletions

View file

@ -5,55 +5,76 @@
use cssparser::Parser as CssParser;
use document_loader::LoadType;
use dom::attr::{Attr, AttrValue};
use dom::bindings::cell::DOMRefCell;
use dom::bindings::codegen::Bindings::HTMLLinkElementBinding;
use dom::bindings::codegen::Bindings::HTMLLinkElementBinding::HTMLLinkElementMethods;
use dom::bindings::codegen::Bindings::WindowBinding::WindowMethods;
use dom::bindings::global::GlobalRef;
use dom::bindings::inheritance::Castable;
use dom::bindings::js::{JS, MutNullableHeap, Root};
use dom::bindings::js::{RootedReference};
use dom::bindings::refcounted::Trusted;
use dom::document::Document;
use dom::domtokenlist::DOMTokenList;
use dom::element::{AttributeMutation, Element};
use dom::event::{Event, EventBubbles, EventCancelable};
use dom::eventtarget::EventTarget;
use dom::element::{AttributeMutation, Element, ElementCreator};
use dom::htmlelement::HTMLElement;
use dom::node::{Node, window_from_node};
use dom::node::{Node, document_from_node, window_from_node};
use dom::virtualmethods::VirtualMethods;
use encoding::EncodingRef;
use encoding::all::UTF_8;
use ipc_channel::ipc;
use ipc_channel::router::ROUTER;
use layout_interface::{LayoutChan, Msg};
use msg::constellation_msg::ConstellationChan;
use msg::constellation_msg::Msg as ConstellationMsg;
use script_traits::StylesheetLoadResponder;
use net_traits::{AsyncResponseListener, AsyncResponseTarget, Metadata};
use network_listener::{NetworkListener, PreInvoke};
use std::ascii::AsciiExt;
use std::borrow::ToOwned;
use std::cell::Cell;
use std::default::Default;
use std::mem;
use std::sync::{Arc, Mutex};
use string_cache::Atom;
use style::media_queries::parse_media_query_list;
use url::UrlParser;
use style::media_queries::{MediaQueryList, parse_media_query_list};
use style::stylesheets::{Origin, Stylesheet};
use url::{Url, UrlParser};
use util::str::{DOMString, HTML_SPACE_CHARACTERS};
no_jsmanaged_fields!(Stylesheet);
#[dom_struct]
pub struct HTMLLinkElement {
htmlelement: HTMLElement,
rel_list: MutNullableHeap<JS<DOMTokenList>>,
stylesheet: DOMRefCell<Option<Arc<Stylesheet>>>,
/// https://html.spec.whatwg.org/multipage/#a-style-sheet-that-is-blocking-scripts
parser_inserted: Cell<bool>,
}
impl HTMLLinkElement {
fn new_inherited(localName: DOMString, prefix: Option<DOMString>, document: &Document) -> HTMLLinkElement {
fn new_inherited(localName: DOMString, prefix: Option<DOMString>, document: &Document,
creator: ElementCreator) -> HTMLLinkElement {
HTMLLinkElement {
htmlelement: HTMLElement::new_inherited(localName, prefix, document),
rel_list: Default::default(),
parser_inserted: Cell::new(creator == ElementCreator::ParserCreated),
stylesheet: DOMRefCell::new(None),
}
}
#[allow(unrooted_must_root)]
pub fn new(localName: DOMString,
prefix: Option<DOMString>,
document: &Document) -> Root<HTMLLinkElement> {
let element = HTMLLinkElement::new_inherited(localName, prefix, document);
document: &Document,
creator: ElementCreator) -> Root<HTMLLinkElement> {
let element = HTMLLinkElement::new_inherited(localName, prefix, document, creator);
Node::reflect_node(box element, document, HTMLLinkElementBinding::Wrap)
}
pub fn get_stylesheet(&self) -> Option<Arc<Stylesheet>> {
self.stylesheet.borrow().clone()
}
}
fn get_attr(element: &Element, local_name: &Atom) -> Option<String> {
@ -64,7 +85,7 @@ fn get_attr(element: &Element, local_name: &Atom) -> Option<String> {
})
}
fn is_stylesheet(value: &Option<String>) -> bool {
fn string_is_stylesheet(value: &Option<String>) -> bool {
match *value {
Some(ref value) => {
value.split(HTML_SPACE_CHARACTERS)
@ -100,14 +121,14 @@ impl VirtualMethods for HTMLLinkElement {
let rel = get_attr(self.upcast(), &atom!(rel));
match attr.local_name() {
&atom!(href) => {
if is_stylesheet(&rel) {
if string_is_stylesheet(&rel) {
self.handle_stylesheet_url(&attr.value());
} else if is_favicon(&rel) {
self.handle_favicon_url(&attr.value());
}
},
&atom!(media) => {
if is_stylesheet(&rel) {
if string_is_stylesheet(&rel) {
self.handle_stylesheet_url(&attr.value());
}
},
@ -134,7 +155,7 @@ impl VirtualMethods for HTMLLinkElement {
let href = get_attr(element, &atom!("href"));
match (rel, href) {
(ref rel, Some(ref href)) if is_stylesheet(rel) => {
(ref rel, Some(ref href)) if string_is_stylesheet(rel) => {
self.handle_stylesheet_url(href);
}
(ref rel, Some(ref href)) if is_favicon(rel) => {
@ -164,13 +185,35 @@ impl HTMLLinkElement {
let mut css_parser = CssParser::new(&mq_str);
let media = parse_media_query_list(&mut css_parser);
// TODO: #8085 - Don't load external stylesheets if the node's mq doesn't match.
let doc = window.Document();
let link_element = Trusted::new(window.get_cx(), self, window.script_chan().clone());
let load_dispatcher = StylesheetLoadDispatcher::new(link_element);
let script_chan = window.script_chan();
let elem = Trusted::new(window.get_cx(), self, script_chan.clone());
let pending = doc.prepare_async_load(LoadType::Stylesheet(url.clone()));
let LayoutChan(ref layout_chan) = window.layout_chan();
layout_chan.send(Msg::LoadStylesheet(url, media, pending, box load_dispatcher)).unwrap();
let context = Arc::new(Mutex::new(StylesheetContext {
elem: elem,
media: Some(media),
data: vec!(),
metadata: None,
url: url.clone(),
}));
let (action_sender, action_receiver) = ipc::channel().unwrap();
let listener = NetworkListener {
context: context,
script_chan: script_chan,
};
let response_target = AsyncResponseTarget {
sender: action_sender,
};
ROUTER.add_route(action_receiver.to_opaque(), box move |message| {
listener.notify(message.to().unwrap());
});
if self.parser_inserted.get() {
doc.increment_script_blocking_stylesheet_count();
}
doc.load_async(LoadType::Stylesheet(url), response_target);
}
Err(e) => debug!("Parsing url {} failed: {}", href, e)
}
@ -190,6 +233,62 @@ impl HTMLLinkElement {
}
}
/// The context required for asynchronously loading an external stylesheet.
struct StylesheetContext {
/// The element that initiated the request.
elem: Trusted<HTMLLinkElement>,
media: Option<MediaQueryList>,
/// The response body received to date.
data: Vec<u8>,
/// The response metadata received to date.
metadata: Option<Metadata>,
/// The initial URL requested.
url: Url,
}
impl PreInvoke for StylesheetContext {}
impl AsyncResponseListener for StylesheetContext {
fn headers_available(&mut self, metadata: Metadata) {
self.metadata = Some(metadata);
}
fn data_available(&mut self, payload: Vec<u8>) {
let mut payload = payload;
self.data.append(&mut payload);
}
fn response_complete(&mut self, _status: Result<(), String>) {
let data = mem::replace(&mut self.data, vec!());
let metadata = self.metadata.take().unwrap();
// TODO: Get the actual value. http://dev.w3.org/csswg/css-syntax/#environment-encoding
let environment_encoding = UTF_8 as EncodingRef;
let protocol_encoding_label = metadata.charset.as_ref().map(|s| &**s);
let final_url = metadata.final_url;
let mut sheet = Stylesheet::from_bytes(&data, final_url, protocol_encoding_label,
Some(environment_encoding), Origin::Author);
let media = self.media.take().unwrap();
sheet.set_media(Some(media));
let sheet = Arc::new(sheet);
let elem = self.elem.root();
let elem = elem.r();
let document = document_from_node(elem);
let document = document.r();
let win = window_from_node(elem);
let LayoutChan(ref layout_chan) = win.r().layout_chan();
layout_chan.send(Msg::AddStylesheet(sheet.clone())).unwrap();
*elem.stylesheet.borrow_mut() = Some(sheet);
document.invalidate_stylesheets();
if elem.parser_inserted.get() {
document.decrement_script_blocking_stylesheet_count();
}
document.finish_load(LoadType::Stylesheet(self.url.clone()));
}
}
impl HTMLLinkElementMethods for HTMLLinkElement {
// https://html.spec.whatwg.org/multipage/#dom-link-href
make_url_getter!(Href);
@ -244,27 +343,3 @@ impl HTMLLinkElementMethods for HTMLLinkElement {
// https://html.spec.whatwg.org/multipage/#dom-link-target
make_setter!(SetTarget, "target");
}
pub struct StylesheetLoadDispatcher {
elem: Trusted<HTMLLinkElement>,
}
impl StylesheetLoadDispatcher {
pub fn new(elem: Trusted<HTMLLinkElement>) -> StylesheetLoadDispatcher {
StylesheetLoadDispatcher {
elem: elem,
}
}
}
impl StylesheetLoadResponder for StylesheetLoadDispatcher {
fn respond(self: Box<StylesheetLoadDispatcher>) {
let elem = self.elem.root();
let window = window_from_node(elem.r());
let event = Event::new(GlobalRef::Window(window.r()),
DOMString("load".to_owned()),
EventBubbles::DoesNotBubble,
EventCancelable::NotCancelable);
event.fire(elem.upcast::<EventTarget>());
}
}