diff --git a/components/layout_thread/lib.rs b/components/layout_thread/lib.rs index 20ef9ff6cb4..342f8195cb6 100644 --- a/components/layout_thread/lib.rs +++ b/components/layout_thread/lib.rs @@ -1542,6 +1542,7 @@ fn get_ua_stylesheets() -> Result { None, Origin::UserAgent, Default::default(), + None, Box::new(StdoutErrorReporter), ParserContextExtraData::default())) } @@ -1555,7 +1556,8 @@ fn get_ua_stylesheets() -> Result { for &(ref contents, ref url) in &opts::get().user_stylesheets { user_or_user_agent_stylesheets.push(Stylesheet::from_bytes( &contents, url.clone(), None, None, Origin::User, Default::default(), - Box::new(StdoutErrorReporter), ParserContextExtraData::default())); + None, Box::new(StdoutErrorReporter), + ParserContextExtraData::default())); } let quirks_mode_stylesheet = try!(parse_ua_stylesheet("quirks-mode.css")); diff --git a/components/script/dom/bindings/trace.rs b/components/script/dom/bindings/trace.rs index ad8dc6b935e..3365c106bd9 100644 --- a/components/script/dom/bindings/trace.rs +++ b/components/script/dom/bindings/trace.rs @@ -98,7 +98,7 @@ use style::keyframes::Keyframe; use style::media_queries::MediaList; use style::properties::PropertyDeclarationBlock; use style::selector_parser::{PseudoElement, Snapshot}; -use style::stylesheets::{CssRules, KeyframesRule, MediaRule, NamespaceRule, StyleRule}; +use style::stylesheets::{CssRules, KeyframesRule, MediaRule, NamespaceRule, StyleRule, ImportRule}; use style::values::specified::Length; use style::viewport::ViewportRule; use time::Duration; @@ -525,6 +525,12 @@ unsafe impl JSTraceable for RwLock { } } +unsafe impl JSTraceable for RwLock { + unsafe fn trace(&self, _trc: *mut JSTracer) { + // Do nothing. + } +} + unsafe impl JSTraceable for RwLock { unsafe fn trace(&self, _trc: *mut JSTracer) { // Do nothing. diff --git a/components/script/dom/create.rs b/components/script/dom/create.rs index cb591e74c55..7c9a9eeea9c 100644 --- a/components/script/dom/create.rs +++ b/components/script/dom/create.rs @@ -244,7 +244,7 @@ fn create_html_element(name: QualName, local_name!("span") => make!(HTMLSpanElement), local_name!("strike") => make!(HTMLElement), local_name!("strong") => make!(HTMLElement), - local_name!("style") => make!(HTMLStyleElement), + local_name!("style") => make!(HTMLStyleElement, creator), local_name!("sub") => make!(HTMLElement), local_name!("summary") => make!(HTMLElement), local_name!("sup") => make!(HTMLElement), diff --git a/components/script/dom/cssimportrule.rs b/components/script/dom/cssimportrule.rs new file mode 100644 index 00000000000..582a6f597f9 --- /dev/null +++ b/components/script/dom/cssimportrule.rs @@ -0,0 +1,53 @@ +/* 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 dom::bindings::codegen::Bindings::CSSImportRuleBinding; +use dom::bindings::js::Root; +use dom::bindings::reflector::reflect_dom_object; +use dom::bindings::str::DOMString; +use dom::cssrule::{CSSRule, SpecificCSSRule}; +use dom::cssstylesheet::CSSStyleSheet; +use dom::window::Window; +use parking_lot::RwLock; +use std::sync::Arc; +use style::stylesheets::ImportRule; +use style_traits::ToCss; + +#[dom_struct] +pub struct CSSImportRule { + cssrule: CSSRule, + #[ignore_heap_size_of = "Arc"] + import_rule: Arc>, +} + +impl CSSImportRule { + fn new_inherited(parent_stylesheet: &CSSStyleSheet, + import_rule: Arc>) + -> Self { + CSSImportRule { + cssrule: CSSRule::new_inherited(parent_stylesheet), + import_rule: import_rule, + } + } + + #[allow(unrooted_must_root)] + pub fn new(window: &Window, + parent_stylesheet: &CSSStyleSheet, + import_rule: Arc>) -> Root { + reflect_dom_object(box Self::new_inherited(parent_stylesheet, import_rule), + window, + CSSImportRuleBinding::Wrap) + } +} + +impl SpecificCSSRule for CSSImportRule { + fn ty(&self) -> u16 { + use dom::bindings::codegen::Bindings::CSSRuleBinding::CSSRuleConstants; + CSSRuleConstants::IMPORT_RULE + } + + fn get_css(&self) -> DOMString { + self.import_rule.read().to_css_string().into() + } +} diff --git a/components/script/dom/cssrule.rs b/components/script/dom/cssrule.rs index 603354c8621..27b439f9dc4 100644 --- a/components/script/dom/cssrule.rs +++ b/components/script/dom/cssrule.rs @@ -9,6 +9,7 @@ use dom::bindings::js::{JS, Root}; use dom::bindings::reflector::{Reflector, reflect_dom_object}; use dom::bindings::str::DOMString; use dom::cssfontfacerule::CSSFontFaceRule; +use dom::cssimportrule::CSSImportRule; use dom::csskeyframerule::CSSKeyframeRule; use dom::csskeyframesrule::CSSKeyframesRule; use dom::cssmediarule::CSSMediaRule; @@ -64,6 +65,8 @@ impl CSSRule { rule as &SpecificCSSRule } else if let Some(rule) = self.downcast::() { rule as &SpecificCSSRule + } else if let Some(rule) = self.downcast::() { + rule as &SpecificCSSRule } else { unreachable!() } @@ -75,6 +78,7 @@ impl CSSRule { rule: StyleCssRule) -> Root { // be sure to update the match in as_specific when this is updated match rule { + StyleCssRule::Import(s) => Root::upcast(CSSImportRule::new(window, parent_stylesheet, s)), StyleCssRule::Style(s) => Root::upcast(CSSStyleRule::new(window, parent_stylesheet, s)), StyleCssRule::FontFace(s) => Root::upcast(CSSFontFaceRule::new(window, parent_stylesheet, s)), StyleCssRule::Keyframes(s) => Root::upcast(CSSKeyframesRule::new(window, parent_stylesheet, s)), diff --git a/components/script/dom/element.rs b/components/script/dom/element.rs index 66021ce6104..21dfde99f22 100644 --- a/components/script/dom/element.rs +++ b/components/script/dom/element.rs @@ -51,9 +51,11 @@ use dom::htmlimageelement::{HTMLImageElement, LayoutHTMLImageElementHelpers}; use dom::htmlinputelement::{HTMLInputElement, LayoutHTMLInputElementHelpers}; use dom::htmllabelelement::HTMLLabelElement; use dom::htmllegendelement::HTMLLegendElement; +use dom::htmllinkelement::HTMLLinkElement; use dom::htmlobjectelement::HTMLObjectElement; use dom::htmloptgroupelement::HTMLOptGroupElement; use dom::htmlselectelement::HTMLSelectElement; +use dom::htmlstyleelement::HTMLStyleElement; use dom::htmltablecellelement::{HTMLTableCellElement, HTMLTableCellElementLayoutHelpers}; use dom::htmltableelement::{HTMLTableElement, HTMLTableElementLayoutHelpers}; use dom::htmltablerowelement::{HTMLTableRowElement, HTMLTableRowElementLayoutHelpers}; @@ -109,6 +111,7 @@ use style::sink::Push; use style::stylist::ApplicableDeclarationBlock; use style::values::CSSFloat; use style::values::specified::{self, CSSColor, CSSRGBA, LengthOrPercentage}; +use stylesheet_loader::StylesheetOwner; // TODO: Update focus state when the top-level browsing context gains or loses system focus, // and when the element enters or leaves a browsing context container. @@ -2407,6 +2410,18 @@ impl Element { }) } + pub fn as_stylesheet_owner(&self) -> Option<&StylesheetOwner> { + if let Some(s) = self.downcast::() { + return Some(s as &StylesheetOwner) + } + + if let Some(l) = self.downcast::() { + return Some(l as &StylesheetOwner) + } + + None + } + // https://html.spec.whatwg.org/multipage/#category-submit pub fn as_maybe_validatable(&self) -> Option<&Validatable> { let element = match self.upcast::().type_id() { diff --git a/components/script/dom/htmllinkelement.rs b/components/script/dom/htmllinkelement.rs index 145b53b3ec7..cc66a822d92 100644 --- a/components/script/dom/htmllinkelement.rs +++ b/components/script/dom/htmllinkelement.rs @@ -3,52 +3,36 @@ * 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::DOMTokenListBinding::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 net_traits::ReferrerPolicy; 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, StylesheetOwner}; unsafe_no_jsmanaged_fields!(Stylesheet); @@ -62,6 +46,11 @@ pub struct HTMLLinkElement { /// https://html.spec.whatwg.org/multipage/#a-style-sheet-that-is-blocking-scripts parser_inserted: Cell, + /// The number of loads that this link element has triggered (could be more + /// than one because of imports) and have not yet finished. + pending_loads: Cell, + /// Whether any of the loads have failed. + any_failed_load: Cell, } impl HTMLLinkElement { @@ -73,6 +62,8 @@ impl HTMLLinkElement { parser_inserted: Cell::new(creator == ElementCreator::ParserCreated), stylesheet: DOMRefCell::new(None), cssom_stylesheet: MutNullableJS::new(None), + pending_loads: Cell::new(0), + any_failed_load: Cell::new(false), } } @@ -86,6 +77,16 @@ impl HTMLLinkElement { HTMLLinkElementBinding::Wrap) } + pub fn parser_inserted(&self) -> bool { + self.parser_inserted.get() + } + + pub fn set_stylesheet(&self, s: Arc) { + assert!(self.stylesheet.borrow().is_none()); + *self.stylesheet.borrow_mut() = Some(s); + } + + pub fn get_stylesheet(&self) -> Option> { self.stylesheet.borrow().clone() } @@ -231,8 +232,11 @@ impl HTMLLinkElement { // Step 2. let url = match document.base_url().join(href) { - Err(e) => return debug!("Parsing url {} failed: {}", href, e), Ok(url) => url, + Err(e) => { + debug!("Parsing url {} failed: {}", href, e); + return; + } }; let element = self.upcast::(); @@ -246,50 +250,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) { @@ -310,92 +277,37 @@ impl HTMLLinkElement { } } -/// The context required for asynchronously loading an external stylesheet. -struct StylesheetContext { - /// The element that initiated the request. - elem: Trusted, - media: Option, - /// The response body received to date. - data: Vec, - /// The response metadata received to date. - metadata: Option, - /// 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) { - 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::().fire_event(atom!("error")); - } - } +impl StylesheetOwner for HTMLLinkElement { + fn increment_pending_loads_count(&self) { + self.pending_loads.set(self.pending_loads.get() + 1) } - fn process_response_chunk(&mut self, mut payload: Vec) { - self.data.append(&mut payload); + fn load_finished(&self, succeeded: bool) -> Option { + assert!(self.pending_loads.get() > 0, "What finished?"); + if !succeeded { + self.any_failed_load.set(true); + } + + self.pending_loads.set(self.pending_loads.get() - 1); + if self.pending_loads.get() != 0 { + return None; + } + + let any_failed = self.any_failed_load.get(); + self.any_failed_load.set(false); + Some(any_failed) } - fn process_response_eof(&mut self, status: Result<(), NetworkError>) { - let elem = self.elem.root(); - let document = document_from_node(&*elem); - let mut successful = false; + fn parser_inserted(&self) -> bool { + self.parser_inserted.get() + } - 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); + fn referrer_policy(&self) -> Option { + if self.RelList().Contains("noreferrer".into()) { + return Some(ReferrerPolicy::NoReferrer) } - 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::().fire_event(event); + None } } diff --git a/components/script/dom/htmlstyleelement.rs b/components/script/dom/htmlstyleelement.rs index 8576a282052..2507f7bee35 100644 --- a/components/script/dom/htmlstyleelement.rs +++ b/components/script/dom/htmlstyleelement.rs @@ -12,17 +12,21 @@ use dom::bindings::js::{MutNullableJS, Root}; use dom::bindings::str::DOMString; use dom::cssstylesheet::CSSStyleSheet; use dom::document::Document; -use dom::element::Element; +use dom::element::{Element, ElementCreator}; +use dom::eventtarget::EventTarget; use dom::htmlelement::HTMLElement; use dom::node::{ChildrenMutation, Node, document_from_node, window_from_node}; use dom::stylesheet::StyleSheet as DOMStyleSheet; use dom::virtualmethods::VirtualMethods; use html5ever_atoms::LocalName; +use net_traits::ReferrerPolicy; use script_layout_interface::message::Msg; +use std::cell::Cell; use std::sync::Arc; use style::media_queries::parse_media_query_list; use style::parser::ParserContextExtraData; use style::stylesheets::{Stylesheet, Origin}; +use stylesheet_loader::{StylesheetLoader, StylesheetOwner}; #[dom_struct] pub struct HTMLStyleElement { @@ -30,24 +34,33 @@ pub struct HTMLStyleElement { #[ignore_heap_size_of = "Arc"] stylesheet: DOMRefCell>>, cssom_stylesheet: MutNullableJS, + /// https://html.spec.whatwg.org/multipage/#a-style-sheet-that-is-blocking-scripts + parser_inserted: Cell, + pending_loads: Cell, + any_failed_load: Cell, } impl HTMLStyleElement { fn new_inherited(local_name: LocalName, prefix: Option, - document: &Document) -> HTMLStyleElement { + document: &Document, + creator: ElementCreator) -> HTMLStyleElement { HTMLStyleElement { htmlelement: HTMLElement::new_inherited(local_name, prefix, document), stylesheet: DOMRefCell::new(None), cssom_stylesheet: MutNullableJS::new(None), + parser_inserted: Cell::new(creator == ElementCreator::ParserCreated), + pending_loads: Cell::new(0), + any_failed_load: Cell::new(false), } } #[allow(unrooted_must_root)] pub fn new(local_name: LocalName, prefix: Option, - document: &Document) -> Root { - Node::reflect_node(box HTMLStyleElement::new_inherited(local_name, prefix, document), + document: &Document, + creator: ElementCreator) -> Root { + Node::reflect_node(box HTMLStyleElement::new_inherited(local_name, prefix, document, creator), document, HTMLStyleElementBinding::Wrap) } @@ -68,10 +81,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::().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); @@ -121,6 +143,37 @@ impl VirtualMethods for HTMLStyleElement { } } +impl StylesheetOwner for HTMLStyleElement { + fn increment_pending_loads_count(&self) { + self.pending_loads.set(self.pending_loads.get() + 1) + } + + fn load_finished(&self, succeeded: bool) -> Option { + assert!(self.pending_loads.get() > 0, "What finished?"); + if !succeeded { + self.any_failed_load.set(true); + } + + self.pending_loads.set(self.pending_loads.get() - 1); + if self.pending_loads.get() != 0 { + return None; + } + + let any_failed = self.any_failed_load.get(); + self.any_failed_load.set(false); + Some(any_failed) + } + + fn parser_inserted(&self) -> bool { + self.parser_inserted.get() + } + + fn referrer_policy(&self) -> Option { + None + } +} + + impl HTMLStyleElementMethods for HTMLStyleElement { // https://drafts.csswg.org/cssom/#dom-linkstyle-sheet fn GetSheet(&self) -> Option> { diff --git a/components/script/dom/mod.rs b/components/script/dom/mod.rs index 03847518408..b432b5a12e5 100644 --- a/components/script/dom/mod.rs +++ b/components/script/dom/mod.rs @@ -244,6 +244,7 @@ pub mod crypto; pub mod css; pub mod cssfontfacerule; pub mod cssgroupingrule; +pub mod cssimportrule; pub mod csskeyframerule; pub mod csskeyframesrule; pub mod cssmediarule; diff --git a/components/script/dom/webidls/CSSImportRule.webidl b/components/script/dom/webidls/CSSImportRule.webidl new file mode 100644 index 00000000000..b8131a7bb87 --- /dev/null +++ b/components/script/dom/webidls/CSSImportRule.webidl @@ -0,0 +1,11 @@ +/* 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/. */ + +// https://drafts.csswg.org/cssom/#cssimportrule +[Exposed=Window] +interface CSSImportRule : CSSRule { + // readonly attribute DOMString href; + // [SameObject, PutForwards=mediaText] readonly attribute MediaList media; + // [SameObject] readonly attribute CSSStyleSheet styleSheet; +}; 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..427593c8500 --- /dev/null +++ b/components/script/stylesheet_loader.rs @@ -0,0 +1,245 @@ +/* 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::inheritance::Castable; +use dom::bindings::refcounted::Trusted; +use dom::bindings::reflector::DomObject; +use dom::element::Element; +use dom::eventtarget::EventTarget; +use dom::htmlelement::HTMLElement; +use dom::htmllinkelement::HTMLLinkElement; +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 trait StylesheetOwner { + /// Returns whether this element was inserted by the parser (i.e., it should + /// trigger a document-load-blocking load). + fn parser_inserted(&self) -> bool; + + /// Which referrer policy should loads triggered by this owner follow, or + /// `None` for the default. + fn referrer_policy(&self) -> Option; + + /// Notes that a new load is pending to finish. + fn increment_pending_loads_count(&self); + + /// Returns None if there are still pending loads, or whether any load has + /// failed since the loads started. + fn load_finished(&self, successful: bool) -> Option; +} + +pub enum StylesheetContextSource { + // NB: `media` is just an option so we avoid cloning it. + LinkElement { media: Option, url: ServoUrl }, + Import(Arc>), +} + +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, + source: StylesheetContextSource, + metadata: Option, + /// The response body received to date. + data: Vec, +} + +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) { + 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) { + 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::() + .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); + } + + let owner = elem.upcast::().as_stylesheet_owner() + .expect("Stylesheet not loaded by + + + diff --git a/tests/wpt/mozilla/tests/mozilla/interfaces.html b/tests/wpt/mozilla/tests/mozilla/interfaces.html index a658a8eb346..85e6335aafb 100644 --- a/tests/wpt/mozilla/tests/mozilla/interfaces.html +++ b/tests/wpt/mozilla/tests/mozilla/interfaces.html @@ -22,6 +22,7 @@ test_interfaces([ "CSS", "CSSFontFaceRule", "CSSGroupingRule", + "CSSImportRule", "CSSKeyframeRule", "CSSKeyframesRule", "CSSMediaRule",