diff options
-rw-r--r-- | components/script/dom/htmllinkelement.rs | 162 | ||||
-rw-r--r-- | components/script/dom/htmlstyleelement.rs | 11 | ||||
-rw-r--r-- | components/script/lib.rs | 1 | ||||
-rw-r--r-- | components/script/stylesheet_loader.rs | 254 |
4 files changed, 274 insertions, 154 deletions
diff --git a/components/script/dom/htmllinkelement.rs b/components/script/dom/htmllinkelement.rs index 54483e481db..1af4efd3e65 100644 --- a/components/script/dom/htmllinkelement.rs +++ b/components/script/dom/htmllinkelement.rs @@ -3,52 +3,34 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ use cssparser::Parser as CssParser; -use document_loader::LoadType; use dom::attr::Attr; use dom::bindings::cell::DOMRefCell; -use dom::bindings::codegen::Bindings::DOMTokenListBinding::DOMTokenListMethods; use dom::bindings::codegen::Bindings::HTMLLinkElementBinding; use dom::bindings::codegen::Bindings::HTMLLinkElementBinding::HTMLLinkElementMethods; use dom::bindings::inheritance::Castable; use dom::bindings::js::{MutNullableJS, Root, RootedReference}; -use dom::bindings::refcounted::Trusted; -use dom::bindings::reflector::DomObject; use dom::bindings::str::DOMString; use dom::cssstylesheet::CSSStyleSheet; use dom::document::Document; use dom::domtokenlist::DOMTokenList; use dom::element::{AttributeMutation, Element, ElementCreator}; -use dom::eventtarget::EventTarget; use dom::globalscope::GlobalScope; use dom::htmlelement::HTMLElement; use dom::node::{Node, document_from_node, window_from_node}; use dom::stylesheet::StyleSheet as DOMStyleSheet; use dom::virtualmethods::VirtualMethods; -use encoding::EncodingRef; -use encoding::all::UTF_8; use html5ever_atoms::LocalName; -use hyper::header::ContentType; -use hyper::mime::{Mime, TopLevel, SubLevel}; -use hyper_serde::Serde; -use ipc_channel::ipc; -use ipc_channel::router::ROUTER; -use net_traits::{FetchResponseListener, FetchMetadata, Metadata, NetworkError, ReferrerPolicy}; -use net_traits::request::{CredentialsMode, Destination, RequestInit, Type as RequestType}; -use network_listener::{NetworkListener, PreInvoke}; -use script_layout_interface::message::Msg; use script_traits::{MozBrowserEvent, ScriptMsg as ConstellationMsg}; -use servo_url::ServoUrl; 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 std::sync::Arc; use style::attr::AttrValue; -use style::media_queries::{MediaList, parse_media_query_list}; -use style::parser::ParserContextExtraData; +use style::media_queries::parse_media_query_list; use style::str::HTML_SPACE_CHARACTERS; -use style::stylesheets::{Stylesheet, Origin}; +use style::stylesheets::Stylesheet; +use stylesheet_loader::{StylesheetLoader, StylesheetContextSource}; unsafe_no_jsmanaged_fields!(Stylesheet); @@ -288,50 +270,13 @@ 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 elem = Trusted::new(self); - - let context = Arc::new(Mutex::new(StylesheetContext { - elem: elem, + // TODO: #8085 - Don't load external stylesheets if the node's mq + // doesn't match. + let loader = StylesheetLoader::for_element(self.upcast()); + loader.load(StylesheetContextSource::LinkElement { + url: url, media: Some(media), - data: vec!(), - metadata: None, - url: url.clone(), - })); - - let (action_sender, action_receiver) = ipc::channel().unwrap(); - let listener = NetworkListener { - context: context, - task_source: document.window().networking_task_source(), - wrapper: Some(document.window().get_runnable_wrapper()) - }; - ROUTER.add_route(action_receiver.to_opaque(), box move |message| { - listener.notify_fetch(message.to().unwrap()); }); - - if self.parser_inserted.get() { - document.increment_script_blocking_stylesheet_count(); - } - - let referrer_policy = match self.RelList().Contains("noreferrer".into()) { - true => Some(ReferrerPolicy::NoReferrer), - false => document.get_referrer_policy(), - }; - - let request = RequestInit { - url: url.clone(), - type_: RequestType::Style, - destination: Destination::Style, - credentials_mode: CredentialsMode::Include, - use_url_credentials: true, - origin: document.url(), - pipeline_id: Some(self.global().pipeline_id()), - referrer_url: Some(document.url()), - referrer_policy: referrer_policy, - .. RequestInit::default() - }; - - document.fetch_async(LoadType::Stylesheet(url), request, action_sender); } fn handle_favicon_url(&self, rel: &str, href: &str, sizes: &Option<String>) { @@ -352,95 +297,6 @@ impl HTMLLinkElement { } } -/// The context required for asynchronously loading an external stylesheet. -struct StylesheetContext { - /// The element that initiated the request. - elem: Trusted<HTMLLinkElement>, - media: Option<MediaList>, - /// The response body received to date. - data: Vec<u8>, - /// The response metadata received to date. - metadata: Option<Metadata>, - /// The initial URL requested. - url: ServoUrl, -} - -impl PreInvoke for StylesheetContext {} - -impl FetchResponseListener for StylesheetContext { - fn process_request_body(&mut self) {} - - fn process_request_eof(&mut self) {} - - fn process_response(&mut self, - metadata: Result<FetchMetadata, NetworkError>) { - self.metadata = metadata.ok().map(|m| { - match m { - FetchMetadata::Unfiltered(m) => m, - FetchMetadata::Filtered { unsafe_, .. } => unsafe_ - } - }); - if let Some(ref meta) = self.metadata { - if let Some(Serde(ContentType(Mime(TopLevel::Text, SubLevel::Css, _)))) = meta.content_type { - } else { - self.elem.root().upcast::<EventTarget>().fire_event(atom!("error")); - } - } - } - - fn process_response_chunk(&mut self, mut payload: Vec<u8>) { - self.data.append(&mut payload); - } - - fn process_response_eof(&mut self, status: Result<(), NetworkError>) { - let elem = self.elem.root(); - let document = document_from_node(&*elem); - let mut successful = false; - - if status.is_ok() { - let metadata = match self.metadata.take() { - Some(meta) => meta, - None => return, - }; - let is_css = metadata.content_type.map_or(false, |Serde(ContentType(Mime(top, sub, _)))| - top == TopLevel::Text && sub == SubLevel::Css); - - let data = if is_css { mem::replace(&mut self.data, vec!()) } else { vec!() }; - - // 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 win = window_from_node(&*elem); - - let sheet = Arc::new(Stylesheet::from_bytes( - &data, final_url, protocol_encoding_label, Some(environment_encoding), - Origin::Author, self.media.take().unwrap(), win.css_error_reporter(), - ParserContextExtraData::default())); - - let win = window_from_node(&*elem); - win.layout_chan().send(Msg::AddStylesheet(sheet.clone())).unwrap(); - - *elem.stylesheet.borrow_mut() = Some(sheet); - document.invalidate_stylesheets(); - - // FIXME: Revisit once consensus is reached at: https://github.com/whatwg/html/issues/1142 - successful = metadata.status.map_or(false, |(code, _)| code == 200); - } - - if elem.parser_inserted.get() { - document.decrement_script_blocking_stylesheet_count(); - } - - document.finish_load(LoadType::Stylesheet(self.url.clone())); - - let event = if successful { atom!("load") } else { atom!("error") }; - - elem.upcast::<EventTarget>().fire_event(event); - } -} - impl HTMLLinkElementMethods for HTMLLinkElement { // https://html.spec.whatwg.org/multipage/#dom-link-href make_url_getter!(Href, "href"); diff --git a/components/script/dom/htmlstyleelement.rs b/components/script/dom/htmlstyleelement.rs index 5dbbd338199..4cf0575ed3d 100644 --- a/components/script/dom/htmlstyleelement.rs +++ b/components/script/dom/htmlstyleelement.rs @@ -102,10 +102,19 @@ impl HTMLStyleElement { let data = node.GetTextContent().expect("Element.textContent must be a string"); let mq = parse_media_query_list(&mut CssParser::new(&mq_str)); - let sheet = Stylesheet::from_str(&data, url, Origin::Author, mq, win.css_error_reporter(), + let loader = StylesheetLoader::for_element(self.upcast()); + let sheet = Stylesheet::from_str(&data, url, Origin::Author, mq, + Some(&loader), + win.css_error_reporter(), ParserContextExtraData::default()); + let sheet = Arc::new(sheet); + // No subresource loads were triggered, just fire the load event now. + if self.pending_loads.get() == 0 { + self.upcast::<EventTarget>().fire_event(atom!("load")); + } + win.layout_chan().send(Msg::AddStylesheet(sheet.clone())).unwrap(); *self.stylesheet.borrow_mut() = Some(sheet); let doc = document_from_node(self); diff --git a/components/script/lib.rs b/components/script/lib.rs index 1a751635ea5..508a2f13e9c 100644 --- a/components/script/lib.rs +++ b/components/script/lib.rs @@ -116,6 +116,7 @@ pub mod script_runtime; pub mod script_thread; mod serviceworker_manager; mod serviceworkerjob; +mod stylesheet_loader; mod task_source; pub mod textinput; mod timers; diff --git a/components/script/stylesheet_loader.rs b/components/script/stylesheet_loader.rs new file mode 100644 index 00000000000..38232e6c4bd --- /dev/null +++ b/components/script/stylesheet_loader.rs @@ -0,0 +1,254 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use document_loader::LoadType; +use dom::bindings::codegen::Bindings::DOMTokenListBinding::DOMTokenListBinding::DOMTokenListMethods; +use dom::bindings::codegen::Bindings::HTMLLinkElementBinding::HTMLLinkElementBinding::HTMLLinkElementMethods; +use dom::bindings::inheritance::Castable; +use dom::bindings::refcounted::Trusted; +use dom::bindings::reflector::DomObject; +use dom::eventtarget::EventTarget; +use dom::htmlelement::HTMLElement; +use dom::htmllinkelement::HTMLLinkElement; +use dom::htmlstyleelement::HTMLStyleElement; +use dom::node::{document_from_node, window_from_node}; +use encoding::EncodingRef; +use encoding::all::UTF_8; +use hyper::header::ContentType; +use hyper::mime::{Mime, TopLevel, SubLevel}; +use hyper_serde::Serde; +use ipc_channel::ipc; +use ipc_channel::router::ROUTER; +use net_traits::{FetchResponseListener, FetchMetadata, Metadata, NetworkError, ReferrerPolicy}; +use net_traits::request::{CredentialsMode, Destination, RequestInit, Type as RequestType}; +use network_listener::{NetworkListener, PreInvoke}; +use parking_lot::RwLock; +use script_layout_interface::message::Msg; +use servo_url::ServoUrl; +use std::mem; +use std::sync::{Arc, Mutex}; +use style::media_queries::MediaList; +use style::parser::ParserContextExtraData; +use style::stylesheets::{ImportRule, Stylesheet, Origin}; +use style::stylesheets::StylesheetLoader as StyleStylesheetLoader; + +pub enum StylesheetContextSource { + // NB: `media` is just an option so we avoid cloning it. + LinkElement { media: Option<MediaList>, url: ServoUrl }, + Import(Arc<RwLock<ImportRule>>), +} + +impl StylesheetContextSource { + fn url(&self) -> ServoUrl { + match *self { + StylesheetContextSource::LinkElement { ref url, .. } => url.clone(), + StylesheetContextSource::Import(ref import) => { + let import = import.read(); + // Look at the parser in style::stylesheets, where we don't + // trigger a load if the url is invalid. + import.url.url() + .expect("Invalid urls shouldn't enter the loader") + .clone() + } + } + } +} + +/// The context required for asynchronously loading an external stylesheet. +pub struct StylesheetContext { + /// The element that initiated the request. + elem: Trusted<HTMLElement>, + source: StylesheetContextSource, + metadata: Option<Metadata>, + /// The response body received to date. + data: Vec<u8>, +} + +impl PreInvoke for StylesheetContext {} + +impl FetchResponseListener for StylesheetContext { + fn process_request_body(&mut self) {} + + fn process_request_eof(&mut self) {} + + fn process_response(&mut self, + metadata: Result<FetchMetadata, NetworkError>) { + self.metadata = metadata.ok().map(|m| { + match m { + FetchMetadata::Unfiltered(m) => m, + FetchMetadata::Filtered { unsafe_, .. } => unsafe_ + } + }); + } + + fn process_response_chunk(&mut self, mut payload: Vec<u8>) { + self.data.append(&mut payload); + } + + fn process_response_eof(&mut self, status: Result<(), NetworkError>) { + let elem = self.elem.root(); + let document = document_from_node(&*elem); + let mut successful = false; + + if status.is_ok() { + let metadata = match self.metadata.take() { + Some(meta) => meta, + None => return, + }; + let is_css = metadata.content_type.map_or(false, |Serde(ContentType(Mime(top, sub, _)))| + top == TopLevel::Text && sub == SubLevel::Css); + + let data = if is_css { mem::replace(&mut self.data, vec![]) } else { vec![] }; + + // 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 win = window_from_node(&*elem); + + let loader = StylesheetLoader::for_element(&elem); + match self.source { + StylesheetContextSource::LinkElement { ref mut media, .. } => { + let sheet = + Arc::new(Stylesheet::from_bytes(&data, final_url, + protocol_encoding_label, + Some(environment_encoding), + Origin::Author, + media.take().unwrap(), + Some(&loader), + win.css_error_reporter(), + ParserContextExtraData::default())); + elem.downcast::<HTMLLinkElement>() + .unwrap() + .set_stylesheet(sheet.clone()); + + let win = window_from_node(&*elem); + win.layout_chan().send(Msg::AddStylesheet(sheet)).unwrap(); + } + StylesheetContextSource::Import(ref import) => { + let import = import.read(); + Stylesheet::update_from_bytes(&import.stylesheet, + &data, + protocol_encoding_label, + Some(environment_encoding), + Some(&loader), + win.css_error_reporter(), + ParserContextExtraData::default()); + } + } + + document.invalidate_stylesheets(); + + // FIXME: Revisit once consensus is reached at: + // https://github.com/whatwg/html/issues/1142 + successful = metadata.status.map_or(false, |(code, _)| code == 200); + } + + if let Some(ref link) = elem.downcast::<HTMLLinkElement>() { + if link.parser_inserted() { + document.decrement_script_blocking_stylesheet_count(); + } + } else if let Some(ref style) = elem.downcast::<HTMLStyleElement>() { + if style.parser_inserted() { + document.decrement_script_blocking_stylesheet_count(); + } + } else { + unreachable!( + "Stylesheet loads can only be triggered by <link> or <style> elements!"); + } + + let url = self.source.url(); + document.finish_load(LoadType::Stylesheet(url)); + + if let Some(ref link) = elem.downcast::<HTMLLinkElement>() { + if let Some(any_failed) = link.load_finished(successful) { + let event = if any_failed { atom!("error") } else { atom!("load") }; + link.upcast::<EventTarget>().fire_event(event); + } + } else if let Some(ref style) = elem.downcast::<HTMLStyleElement>() { + if let Some(any_failed) = style.load_finished(successful) { + let event = if any_failed { atom!("error") } else { atom!("load") }; + style.upcast::<EventTarget>().fire_event(event); + } + } else { + unreachable!( + "Stylesheet loads can only be triggered by <link> or <style> elements!"); + } + } +} + +pub struct StylesheetLoader<'a> { + elem: &'a HTMLElement, +} + +impl<'a> StylesheetLoader<'a> { + pub fn for_element(element: &'a HTMLElement) -> Self { + StylesheetLoader { + elem: element, + } + } +} + +impl<'a> StylesheetLoader<'a> { + pub fn load(&self, source: StylesheetContextSource) { + let url = source.url(); + let context = Arc::new(Mutex::new(StylesheetContext { + elem: Trusted::new(&*self.elem), + source: source, + metadata: None, + data: vec![], + })); + + let document = document_from_node(self.elem); + + let (action_sender, action_receiver) = ipc::channel().unwrap(); + let listener = NetworkListener { + context: context, + task_source: document.window().networking_task_source(), + wrapper: Some(document.window().get_runnable_wrapper()) + }; + ROUTER.add_route(action_receiver.to_opaque(), box move |message| { + listener.notify_fetch(message.to().unwrap()); + }); + + + let mut referrer_policy = document.get_referrer_policy(); + if let Some(ref link) = self.elem.downcast::<HTMLLinkElement>() { + link.increment_pending_loads_count(); + if link.parser_inserted() { + document.increment_script_blocking_stylesheet_count(); + } + if link.RelList().Contains("noreferrer".into()) { + referrer_policy = Some(ReferrerPolicy::NoReferrer); + } + } else if let Some(ref style) = self.elem.downcast::<HTMLStyleElement>() { + style.increment_pending_loads_count(); + if style.parser_inserted() { + document.increment_script_blocking_stylesheet_count(); + } + } + + let request = RequestInit { + url: url.clone(), + type_: RequestType::Style, + destination: Destination::Style, + credentials_mode: CredentialsMode::Include, + use_url_credentials: true, + origin: document.url(), + pipeline_id: Some(self.elem.global().pipeline_id()), + referrer_url: Some(document.url()), + referrer_policy: referrer_policy, + .. RequestInit::default() + }; + + document.fetch_async(LoadType::Stylesheet(url), request, action_sender); + } +} + +impl<'a> StyleStylesheetLoader for StylesheetLoader<'a> { + fn request_stylesheet(&self, import: &Arc<RwLock<ImportRule>>) { + self.load(StylesheetContextSource::Import(import.clone())) + } +} |