diff --git a/servo/components/layout/layout_task.rs b/servo/components/layout/layout_task.rs index c001fc31ee92..6347509ad907 100644 --- a/servo/components/layout/layout_task.rs +++ b/servo/components/layout/layout_task.rs @@ -16,8 +16,6 @@ use context::{SharedLayoutContext, StylistWrapper, 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; @@ -42,7 +40,6 @@ use msg::compositor_msg::{Epoch, LayerId, ScrollPolicy}; use msg::constellation_msg::Msg as ConstellationMsg; use msg::constellation_msg::{ConstellationChan, Failure, 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}; @@ -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; @@ -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; -use style::stylesheets::{CSSRule, CSSRuleIteratorExt, Origin, Stylesheet}; +use style::selector_matching::{Stylist, USER_OR_USER_AGENT_STYLESHEETS}; +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; @@ -381,8 +376,8 @@ impl LayoutTask { let stylist = box Stylist::new(device); let outstanding_web_fonts_counter = Arc::new(AtomicUsize::new(0)); - for user_or_user_agent_stylesheet in stylist.stylesheets() { - add_font_face_rules(user_or_user_agent_stylesheet, + for stylesheet in &*USER_OR_USER_AGENT_STYLESHEETS { + add_font_face_rules(stylesheet, &stylist.device, &font_cache_task, &font_cache_sender, @@ -577,20 +572,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).unwrap(); @@ -744,75 +729,31 @@ impl LayoutTask { response_port.recv().unwrap() } - fn handle_load_stylesheet<'a>(&'a self, - url: Url, - mq: MediaQueryList, - pending: PendingAsyncLoad, - responder: Box, - possibly_locked_rw_data: - &mut Option>) { - // 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, possibly_locked_rw_data: &mut Option>) { // 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>) - { - 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>) { 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); } @@ -1076,7 +1017,7 @@ impl LayoutTask { flow_ref::deref_mut(layout_root)); let root_size = { let root_flow = flow::base(&**layout_root); - if rw_data.stylist.constrain_viewport().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 @@ -1152,6 +1093,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; @@ -1160,9 +1104,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.constrain_viewport(); + let constraints = rw_data.stylist.viewport_constraints().clone(); rw_data.viewport_size = match constraints { Some(ref constraints) => { debug!("Viewport constraints: {:?}", constraints); @@ -1178,9 +1122,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( @@ -1189,7 +1130,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 { @@ -1218,7 +1159,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(), diff --git a/servo/components/script/dom/create.rs b/servo/components/script/dom/create.rs index 4e2b07399b12..2f1d35d3b596 100644 --- a/servo/components/script/dom/create.rs +++ b/servo/components/script/dom/create.rs @@ -174,7 +174,7 @@ pub fn create_element(name: QualName, prefix: Option, 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), diff --git a/servo/components/script/dom/document.rs b/servo/components/script/dom/document.rs index a7267ca00865..e47b7f361862 100644 --- a/servo/components/script/dom/document.rs +++ b/servo/components/script/dom/document.rs @@ -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; @@ -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}; @@ -135,6 +140,10 @@ pub struct Document { scripts: MutNullableHeap>, anchors: MutNullableHeap>, applets: MutNullableHeap>, + /// List of stylesheets associated with nodes in this document. |None| if the list needs to be refreshed. + stylesheets: DOMRefCell>>>, + /// Whether the list of stylesheets has changed since the last reflow was triggered. + stylesheets_changed_since_reflow: Cell, ready_state: Cell, /// Whether the DOMContentLoaded event has already been dispatched. domcontentloaded_dispatched: Cell, @@ -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::().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); @@ -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(); @@ -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(), @@ -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>> { + { + let mut stylesheets = self.stylesheets.borrow_mut(); + if stylesheets.is_none() { + let new_stylesheets: Vec> = self.upcast::() + .traverse_preorder() + .filter_map(|node| { + if let Some(node) = node.downcast::() { + node.get_stylesheet() + } else if let Some(node) = node.downcast::() { + node.get_stylesheet() + } else if let Some(node) = node.downcast::() { + 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 { self.appropriate_template_contents_owner_document.or_init(|| { diff --git a/servo/components/script/dom/htmllinkelement.rs b/servo/components/script/dom/htmllinkelement.rs index 6fd36f60074d..2860c98dd5c5 100644 --- a/servo/components/script/dom/htmllinkelement.rs +++ b/servo/components/script/dom/htmllinkelement.rs @@ -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>, + stylesheet: DOMRefCell>>, + + /// https://html.spec.whatwg.org/multipage/#a-style-sheet-that-is-blocking-scripts + parser_inserted: Cell, } impl HTMLLinkElement { - fn new_inherited(localName: DOMString, prefix: Option, document: &Document) -> HTMLLinkElement { + fn new_inherited(localName: DOMString, prefix: Option, 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, - document: &Document) -> Root { - let element = HTMLLinkElement::new_inherited(localName, prefix, document); + document: &Document, + creator: ElementCreator) -> Root { + let element = HTMLLinkElement::new_inherited(localName, prefix, document, creator); Node::reflect_node(box element, document, HTMLLinkElementBinding::Wrap) } + + pub fn get_stylesheet(&self) -> Option> { + self.stylesheet.borrow().clone() + } } fn get_attr(element: &Element, local_name: &Atom) -> Option { @@ -64,7 +85,7 @@ fn get_attr(element: &Element, local_name: &Atom) -> Option { }) } -fn is_stylesheet(value: &Option) -> bool { +fn string_is_stylesheet(value: &Option) -> 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, + media: Option, + /// The response body received to date. + data: Vec, + /// The response metadata received to date. + metadata: Option, + /// 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) { + 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, -} - -impl StylesheetLoadDispatcher { - pub fn new(elem: Trusted) -> StylesheetLoadDispatcher { - StylesheetLoadDispatcher { - elem: elem, - } - } -} - -impl StylesheetLoadResponder for StylesheetLoadDispatcher { - fn respond(self: Box) { - 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::()); - } -} diff --git a/servo/components/script/dom/htmlmetaelement.rs b/servo/components/script/dom/htmlmetaelement.rs index e615a0efbf73..dd0245104cb3 100644 --- a/servo/components/script/dom/htmlmetaelement.rs +++ b/servo/components/script/dom/htmlmetaelement.rs @@ -2,6 +2,7 @@ * 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::cell::DOMRefCell; use dom::bindings::codegen::Bindings::HTMLMetaElementBinding; use dom::bindings::codegen::Bindings::HTMLMetaElementBinding::HTMLMetaElementMethods; use dom::bindings::inheritance::Castable; @@ -9,16 +10,18 @@ use dom::bindings::js::{Root, RootedReference}; use dom::document::Document; use dom::element::Element; use dom::htmlelement::HTMLElement; -use dom::node::{Node, window_from_node}; +use dom::node::{Node, document_from_node}; use dom::virtualmethods::VirtualMethods; -use layout_interface::{LayoutChan, Msg}; use std::ascii::AsciiExt; +use std::sync::Arc; +use style::stylesheets::{CSSRule, Origin, Stylesheet}; use style::viewport::ViewportRule; use util::str::{DOMString, HTML_SPACE_CHARACTERS}; #[dom_struct] pub struct HTMLMetaElement { htmlelement: HTMLElement, + stylesheet: DOMRefCell>>, } impl HTMLMetaElement { @@ -26,7 +29,8 @@ impl HTMLMetaElement { prefix: Option, document: &Document) -> HTMLMetaElement { HTMLMetaElement { - htmlelement: HTMLElement::new_inherited(localName, prefix, document) + htmlelement: HTMLElement::new_inherited(localName, prefix, document), + stylesheet: DOMRefCell::new(None), } } @@ -38,6 +42,10 @@ impl HTMLMetaElement { Node::reflect_node(box element, document, HTMLMetaElementBinding::Wrap) } + pub fn get_stylesheet(&self) -> Option> { + self.stylesheet.borrow().clone() + } + fn process_attributes(&self) { let element = self.upcast::(); if let Some(name) = element.get_attribute(&ns!(""), &atom!("name")).r() { @@ -45,22 +53,25 @@ impl HTMLMetaElement { let name = name.trim_matches(HTML_SPACE_CHARACTERS); match name { - "viewport" => self.translate_viewport(), + "viewport" => self.apply_viewport(), _ => {} } } } - fn translate_viewport(&self) { + fn apply_viewport(&self) { let element = self.upcast::(); if let Some(content) = element.get_attribute(&ns!(""), &atom!("content")).r() { let content = content.value(); if !content.is_empty() { if let Some(translated_rule) = ViewportRule::from_meta(&**content) { - let win = window_from_node(self); - let LayoutChan(ref layout_chan) = win.layout_chan(); - - layout_chan.send(Msg::AddMetaViewport(translated_rule)).unwrap(); + *self.stylesheet.borrow_mut() = Some(Arc::new(Stylesheet { + rules: vec![CSSRule::Viewport(translated_rule)], + origin: Origin::Author, + media: None, + })); + let doc = document_from_node(self); + doc.invalidate_stylesheets(); } } } diff --git a/servo/components/script/dom/htmlstyleelement.rs b/servo/components/script/dom/htmlstyleelement.rs index d1ac6dcc37b7..e225ec339819 100644 --- a/servo/components/script/dom/htmlstyleelement.rs +++ b/servo/components/script/dom/htmlstyleelement.rs @@ -3,6 +3,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ use cssparser::Parser as CssParser; +use dom::bindings::cell::DOMRefCell; use dom::bindings::codegen::Bindings::HTMLStyleElementBinding; use dom::bindings::codegen::Bindings::NodeBinding::NodeMethods; use dom::bindings::inheritance::Castable; @@ -10,9 +11,10 @@ use dom::bindings::js::Root; use dom::document::Document; use dom::element::Element; use dom::htmlelement::HTMLElement; -use dom::node::{ChildrenMutation, Node, window_from_node}; +use dom::node::{ChildrenMutation, Node, document_from_node, window_from_node}; use dom::virtualmethods::VirtualMethods; use layout_interface::{LayoutChan, Msg}; +use std::sync::Arc; use style::media_queries::parse_media_query_list; use style::stylesheets::{Origin, Stylesheet}; use util::str::DOMString; @@ -20,6 +22,7 @@ use util::str::DOMString; #[dom_struct] pub struct HTMLStyleElement { htmlelement: HTMLElement, + stylesheet: DOMRefCell>>, } impl HTMLStyleElement { @@ -27,7 +30,8 @@ impl HTMLStyleElement { prefix: Option, document: &Document) -> HTMLStyleElement { HTMLStyleElement { - htmlelement: HTMLElement::new_inherited(localName, prefix, document) + htmlelement: HTMLElement::new_inherited(localName, prefix, document), + stylesheet: DOMRefCell::new(None), } } @@ -52,13 +56,23 @@ impl HTMLStyleElement { Some(a) => String::from(&**a.value()), None => String::new(), }; - let mut css_parser = CssParser::new(&mq_str); - let media = parse_media_query_list(&mut css_parser); let data = node.GetTextContent().expect("Element.textContent must be a string"); - let sheet = Stylesheet::from_str(&data, url, Origin::Author); + let mut sheet = Stylesheet::from_str(&data, url, Origin::Author); + let mut css_parser = CssParser::new(&mq_str); + let media = parse_media_query_list(&mut css_parser); + sheet.set_media(Some(media)); + let sheet = Arc::new(sheet); + let LayoutChan(ref layout_chan) = win.layout_chan(); - layout_chan.send(Msg::AddStylesheet(sheet, media)).unwrap(); + layout_chan.send(Msg::AddStylesheet(sheet.clone())).unwrap(); + *self.stylesheet.borrow_mut() = Some(sheet); + let doc = document_from_node(self); + doc.r().invalidate_stylesheets(); + } + + pub fn get_stylesheet(&self) -> Option> { + self.stylesheet.borrow().clone() } } diff --git a/servo/components/script/dom/window.rs b/servo/components/script/dom/window.rs index e296cea77d5d..e50674c222cc 100644 --- a/servo/components/script/dom/window.rs +++ b/servo/components/script/dom/window.rs @@ -99,6 +99,7 @@ pub enum ReflowReason { WindowResize, DOMContentLoaded, DocumentLoaded, + StylesheetLoaded, ImageLoaded, RequestAnimationFrame, WebFontLoaded, @@ -915,6 +916,9 @@ impl Window { debug_reflow_events(&goal, &query_type, &reason); } + let document = self.Document(); + let stylesheets_changed = document.get_and_reset_stylesheets_changed_since_reflow(); + // Send new document and relevant styles to layout. let reflow = ScriptReflow { reflow_info: Reflow { @@ -922,6 +926,8 @@ impl Window { page_clip_rect: self.page_clip_rect.get(), }, document: self.Document().upcast::().to_trusted_node_address(), + document_stylesheets: document.stylesheets().clone(), + stylesheets_changed: stylesheets_changed, window_size: window_size, script_join_chan: join_chan, query_type: query_type, @@ -1325,6 +1331,7 @@ fn debug_reflow_events(goal: &ReflowGoal, query_type: &ReflowQueryType, reason: ReflowReason::WindowResize => "\tWindowResize", ReflowReason::DOMContentLoaded => "\tDOMContentLoaded", ReflowReason::DocumentLoaded => "\tDocumentLoaded", + ReflowReason::StylesheetLoaded => "\tStylesheetLoaded", ReflowReason::ImageLoaded => "\tImageLoaded", ReflowReason::RequestAnimationFrame => "\tRequestAnimationFrame", ReflowReason::WebFontLoaded => "\tWebFontLoaded", diff --git a/servo/components/script/layout_interface.rs b/servo/components/script/layout_interface.rs index 7a735d227376..d18098bf6c1c 100644 --- a/servo/components/script/layout_interface.rs +++ b/servo/components/script/layout_interface.rs @@ -16,32 +16,24 @@ use msg::compositor_msg::Epoch; use msg::compositor_msg::LayerId; use msg::constellation_msg::{ConstellationChan, Failure, PipelineId}; use msg::constellation_msg::{WindowSizeData}; -use net_traits::PendingAsyncLoad; use net_traits::image_cache_task::ImageCacheTask; use profile_traits::mem::ReportsChan; use script_traits::{ConstellationControlMsg, LayoutControlMsg}; -use script_traits::{OpaqueScriptLayoutChannel, StylesheetLoadResponder, UntrustedNodeAddress}; +use script_traits::{OpaqueScriptLayoutChannel, UntrustedNodeAddress}; use selectors::parser::PseudoElement; use std::any::Any; +use std::sync::Arc; use std::sync::mpsc::{Receiver, Sender, channel}; use string_cache::Atom; use style::animation::PropertyAnimation; -use style::media_queries::MediaQueryList; use style::stylesheets::Stylesheet; -use style::viewport::ViewportRule; use url::Url; pub use dom::node::TrustedNodeAddress; /// Asynchronous messages that script can send to layout. pub enum Msg { /// Adds the given stylesheet to the document. - AddStylesheet(Stylesheet, MediaQueryList), - - /// Adds the given stylesheet to the document. - LoadStylesheet(Url, MediaQueryList, PendingAsyncLoad, Box), - - /// Adds a @viewport rule (translated from a element) to the document. - AddMetaViewport(ViewportRule), + AddStylesheet(Arc), /// Puts a document into quirks mode, causing the quirks mode stylesheet to be loaded. SetQuirksMode, @@ -175,6 +167,10 @@ pub struct ScriptReflow { pub reflow_info: Reflow, /// The document node. pub document: TrustedNodeAddress, + /// The document's list of stylesheets. + pub document_stylesheets: Vec>, + /// Whether the document's stylesheets have changed since the last script reflow. + pub stylesheets_changed: bool, /// The current window size. pub window_size: WindowSizeData, /// The channel that we send a notification to. diff --git a/servo/components/script/script_task.rs b/servo/components/script/script_task.rs index fcf29605b800..f99196374572 100644 --- a/servo/components/script/script_task.rs +++ b/servo/components/script/script_task.rs @@ -20,7 +20,7 @@ use devtools; use devtools_traits::ScriptToDevtoolsControlMsg; use devtools_traits::{DevtoolScriptControlMsg, DevtoolsPageInfo}; -use document_loader::{DocumentLoader, LoadType}; +use document_loader::DocumentLoader; use dom::bindings::cell::DOMRefCell; use dom::bindings::codegen::Bindings::DocumentBinding::{DocumentMethods, DocumentReadyState}; use dom::bindings::conversions::{FromJSValConvertible, StringificationBehavior}; @@ -1001,10 +1001,6 @@ impl ScriptTask { self.handle_tick_all_animations(pipeline_id), ConstellationControlMsg::WebFontLoaded(pipeline_id) => self.handle_web_font_loaded(pipeline_id), - ConstellationControlMsg::StylesheetLoadComplete(id, url, responder) => { - responder.respond(); - self.handle_resource_loaded(id, LoadType::Stylesheet(url)); - } ConstellationControlMsg::GetCurrentState(sender, pipeline_id) => { let state = self.handle_get_current_state(pipeline_id); sender.send(state).unwrap(); @@ -1153,13 +1149,6 @@ impl ScriptTask { panic!("Page rect message sent to nonexistent pipeline"); } - /// Handle a request to load a page in a new child frame of an existing page. - fn handle_resource_loaded(&self, pipeline: PipelineId, load: LoadType) { - let page = get_page(&self.root_page(), pipeline); - let doc = page.document(); - doc.finish_load(load); - } - /// Get the current state of a given pipeline. fn handle_get_current_state(&self, pipeline_id: PipelineId) -> ScriptState { // Check if the main page load is still pending diff --git a/servo/components/script_traits/lib.rs b/servo/components/script_traits/lib.rs index ba65e0e2c7ad..7da6b05cf77e 100644 --- a/servo/components/script_traits/lib.rs +++ b/servo/components/script_traits/lib.rs @@ -41,7 +41,6 @@ use net_traits::storage_task::StorageTask; use profile_traits::mem; use std::any::Any; use std::sync::mpsc::{Receiver, Sender}; -use url::Url; use util::mem::HeapSizeOf; /// The address of a node. Layout sends these back. They must be validated via @@ -90,13 +89,6 @@ pub struct NewLayoutInfo { pub layout_shutdown_chan: Sender<()>, } -/// `StylesheetLoadResponder` is used to notify a responder that a style sheet -/// has loaded. -pub trait StylesheetLoadResponder { - /// Respond to a loaded style sheet. - fn respond(self: Box); -} - /// Used to determine if a script has any pending asynchronous activity. #[derive(Copy, Clone, Debug, PartialEq)] pub enum ScriptState { @@ -141,8 +133,6 @@ pub enum ConstellationControlMsg { /// Notifies the script task that a new Web font has been loaded, and thus the page should be /// reflowed. WebFontLoaded(PipelineId), - /// Notifies script that a stylesheet has finished loading. - StylesheetLoadComplete(PipelineId, Url, Box), /// Get the current state of the script task for a given pipeline. GetCurrentState(Sender, PipelineId), } diff --git a/servo/components/style/font_face.rs b/servo/components/style/font_face.rs index eea501b9cf20..ff4f2993d81c 100644 --- a/servo/components/style/font_face.rs +++ b/servo/components/style/font_face.rs @@ -9,20 +9,21 @@ use properties::longhands::font_family::parse_one_family; use std::ascii::AsciiExt; use string_cache::Atom; use url::{Url, UrlParser}; +use util::mem::HeapSizeOf; -#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] +#[derive(Clone, Debug, HeapSizeOf, PartialEq, Eq, Deserialize, Serialize)] pub enum Source { Url(UrlSource), Local(Atom), } -#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] +#[derive(Clone, Debug, HeapSizeOf, PartialEq, Eq, Deserialize, Serialize)] pub struct UrlSource { pub url: Url, pub format_hints: Vec, } -#[derive(Debug, PartialEq, Eq)] +#[derive(Debug, HeapSizeOf, PartialEq, Eq)] pub struct FontFaceRule { pub family: Atom, pub sources: Vec, diff --git a/servo/components/style/media_queries.rs b/servo/components/style/media_queries.rs index 76516747bd17..066fc76528d2 100644 --- a/servo/components/style/media_queries.rs +++ b/servo/components/style/media_queries.rs @@ -8,15 +8,16 @@ use euclid::size::{Size2D, TypedSize2D}; use properties::longhands; use std::ascii::AsciiExt; use util::geometry::ViewportPx; +use util::mem::HeapSizeOf; use values::specified; -#[derive(Debug, PartialEq)] +#[derive(Debug, HeapSizeOf, PartialEq)] pub struct MediaQueryList { pub media_queries: Vec } -#[derive(PartialEq, Eq, Copy, Clone, Debug)] +#[derive(PartialEq, Eq, Copy, Clone, Debug, HeapSizeOf)] pub enum Range { Min(T), Max(T), @@ -59,20 +60,20 @@ impl Range { } /// http://dev.w3.org/csswg/mediaqueries-3/#media1 -#[derive(PartialEq, Copy, Clone, Debug)] +#[derive(PartialEq, Copy, Clone, Debug, HeapSizeOf)] pub enum Expression { /// http://dev.w3.org/csswg/mediaqueries-3/#width Width(Range), } /// http://dev.w3.org/csswg/mediaqueries-3/#media0 -#[derive(PartialEq, Eq, Copy, Clone, Debug)] +#[derive(PartialEq, Eq, Copy, Clone, Debug, HeapSizeOf)] pub enum Qualifier { Only, Not, } -#[derive(Debug, PartialEq)] +#[derive(Debug, HeapSizeOf, PartialEq)] pub struct MediaQuery { pub qualifier: Option, pub media_type: MediaQueryType, @@ -91,20 +92,20 @@ impl MediaQuery { } /// http://dev.w3.org/csswg/mediaqueries-3/#media0 -#[derive(PartialEq, Eq, Copy, Clone, Debug)] +#[derive(PartialEq, Eq, Copy, Clone, Debug, HeapSizeOf)] pub enum MediaQueryType { All, // Always true MediaType(MediaType), } -#[derive(PartialEq, Eq, Copy, Clone, Debug)] +#[derive(PartialEq, Eq, Copy, Clone, Debug, HeapSizeOf)] pub enum MediaType { Screen, Print, Unknown, } -#[derive(Debug)] +#[derive(Debug, HeapSizeOf)] pub struct Device { pub media_type: MediaType, pub viewport_size: TypedSize2D, diff --git a/servo/components/style/selector_matching.rs b/servo/components/style/selector_matching.rs index 8cc56e693ebc..85dadac97d14 100644 --- a/servo/components/style/selector_matching.rs +++ b/servo/components/style/selector_matching.rs @@ -3,7 +3,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ use legacy::PresentationalHintSynthesis; -use media_queries::Device; +use media_queries::{Device, MediaType}; use node::TElementAttributes; use properties::{PropertyDeclaration, PropertyDeclarationBlock}; use restyle_hints::{RestyleHint, StateDependencySet}; @@ -26,16 +26,67 @@ use viewport::{MaybeNew, ViewportRuleCascade}; pub type DeclarationBlock = GenericDeclarationBlock>; -pub struct Stylist { - // List of stylesheets (including all media rules) - stylesheets: Vec, +lazy_static! { + pub static ref USER_OR_USER_AGENT_STYLESHEETS: Vec = { + let mut stylesheets = vec!(); + // FIXME: presentational-hints.css should be at author origin with zero specificity. + // (Does it make a difference?) + for &filename in &["user-agent.css", "servo.css", "presentational-hints.css"] { + match read_resource_file(&[filename]) { + Ok(res) => { + let ua_stylesheet = Stylesheet::from_bytes( + &res, + Url::parse(&format!("chrome:///{:?}", filename)).unwrap(), + None, + None, + Origin::UserAgent); + stylesheets.push(ua_stylesheet); + } + Err(..) => { + error!("Failed to load UA stylesheet {}!", filename); + process::exit(1); + } + } + } + for &(ref contents, ref url) in &opts::get().user_stylesheets { + stylesheets.push(Stylesheet::from_bytes( + &contents, url.clone(), None, None, Origin::User)); + } + stylesheets + }; +} +lazy_static! { + pub static ref QUIRKS_MODE_STYLESHEET: Stylesheet = { + match read_resource_file(&["quirks-mode.css"]) { + Ok(res) => { + Stylesheet::from_bytes( + &res, + Url::parse("chrome:///quirks-mode.css").unwrap(), + None, + None, + Origin::UserAgent) + }, + Err(..) => { + error!("Stylist failed to load 'quirks-mode.css'!"); + process::exit(1); + } + } + }; +} + +pub struct Stylist { // Device that the stylist is currently evaluating against. pub device: Device, - // If true, a stylesheet has been added or the device has - // changed, and the stylist needs to be updated. - is_dirty: bool, + // Viewport constraints based on the current device. + viewport_constraints: Option, + + // If true, the quirks-mode stylesheet is applied. + quirks_mode: bool, + + // If true, the device has changed, and the stylist needs to be updated. + is_device_dirty: bool, // The current selector maps, after evaluating media // rules against the current device. @@ -51,10 +102,11 @@ pub struct Stylist { impl Stylist { #[inline] pub fn new(device: Device) -> Stylist { - let mut stylist = Stylist { - stylesheets: vec!(), + let stylist = Stylist { + viewport_constraints: None, device: device, - is_dirty: true, + is_device_dirty: true, + quirks_mode: false, element_map: PerPseudoElementSelectorMap::new(), before_map: PerPseudoElementSelectorMap::new(), @@ -63,113 +115,93 @@ impl Stylist { state_deps: StateDependencySet::new(), }; // FIXME: Add iso-8859-9.css when the document’s encoding is ISO-8859-8. - // FIXME: presentational-hints.css should be at author origin with zero specificity. - // (Does it make a difference?) - for &filename in &["user-agent.css", "servo.css", "presentational-hints.css"] { - match read_resource_file(&[filename]) { - Ok(res) => { - let ua_stylesheet = Stylesheet::from_bytes( - &res, - Url::parse(&format!("chrome:///{:?}", filename)).unwrap(), - None, - None, - Origin::UserAgent); - stylist.add_stylesheet(ua_stylesheet); - } - Err(..) => { - error!("Stylist::new() failed at loading {}!", filename); - process::exit(1); - } - } - } - for &(ref contents, ref url) in &opts::get().user_stylesheets { - stylist.add_stylesheet(Stylesheet::from_bytes( - &contents, url.clone(), None, None, Origin::User)); - } stylist } - #[inline] - pub fn stylesheets(&self) -> &[Stylesheet] { - &self.stylesheets - } + pub fn update(&mut self, doc_stylesheets: &[&Stylesheet], + stylesheets_changed: bool) -> bool { + if !(self.is_device_dirty || stylesheets_changed) { + return false; + } + self.element_map = PerPseudoElementSelectorMap::new(); + self.before_map = PerPseudoElementSelectorMap::new(); + self.after_map = PerPseudoElementSelectorMap::new(); + self.rules_source_order = 0; + self.state_deps.clear(); - pub fn constrain_viewport(&self) -> Option { - let cascaded_rule = self.stylesheets.iter() - .flat_map(|s| s.effective_rules(&self.device).viewport()) - .cascade(); - - ViewportConstraints::maybe_new(self.device.viewport_size, &cascaded_rule) - } - - pub fn update(&mut self) -> bool { - if self.is_dirty { - self.element_map = PerPseudoElementSelectorMap::new(); - self.before_map = PerPseudoElementSelectorMap::new(); - self.after_map = PerPseudoElementSelectorMap::new(); - self.rules_source_order = 0; - self.state_deps.clear(); - - for stylesheet in &self.stylesheets { - let (mut element_map, mut before_map, mut after_map) = match stylesheet.origin { - Origin::UserAgent => ( - &mut self.element_map.user_agent, - &mut self.before_map.user_agent, - &mut self.after_map.user_agent, - ), - Origin::Author => ( - &mut self.element_map.author, - &mut self.before_map.author, - &mut self.after_map.author, - ), - Origin::User => ( - &mut self.element_map.user, - &mut self.before_map.user, - &mut self.after_map.user, - ), - }; - let mut rules_source_order = self.rules_source_order; - - // Take apart the StyleRule into individual Rules and insert - // them into the SelectorMap of that priority. - macro_rules! append( - ($style_rule: ident, $priority: ident) => { - if $style_rule.declarations.$priority.len() > 0 { - for selector in &$style_rule.selectors { - let map = match selector.pseudo_element { - None => &mut element_map, - Some(PseudoElement::Before) => &mut before_map, - Some(PseudoElement::After) => &mut after_map, - }; - map.$priority.insert(Rule { - selector: selector.compound_selectors.clone(), - declarations: DeclarationBlock { - specificity: selector.specificity, - declarations: $style_rule.declarations.$priority.clone(), - source_order: rules_source_order, - }, - }); - } - } - }; - ); - - for style_rule in stylesheet.effective_rules(&self.device).style() { - append!(style_rule, normal); - append!(style_rule, important); - rules_source_order += 1; - for selector in &style_rule.selectors { - self.state_deps.note_selector(selector.compound_selectors.clone()); - } - } - self.rules_source_order = rules_source_order; - } - - self.is_dirty = false; - return true; + for ref stylesheet in USER_OR_USER_AGENT_STYLESHEETS.iter() { + self.add_stylesheet(&stylesheet); } - false + if self.quirks_mode { + self.add_stylesheet(&QUIRKS_MODE_STYLESHEET); + } + + for ref stylesheet in doc_stylesheets.iter() { + self.add_stylesheet(stylesheet); + } + + self.is_device_dirty = false; + true + } + + fn add_stylesheet(&mut self, stylesheet: &Stylesheet) { + let device = &self.device; + if !stylesheet.is_effective_for_device(device) { + return; + } + let (mut element_map, mut before_map, mut after_map) = match stylesheet.origin { + Origin::UserAgent => ( + &mut self.element_map.user_agent, + &mut self.before_map.user_agent, + &mut self.after_map.user_agent, + ), + Origin::Author => ( + &mut self.element_map.author, + &mut self.before_map.author, + &mut self.after_map.author, + ), + Origin::User => ( + &mut self.element_map.user, + &mut self.before_map.user, + &mut self.after_map.user, + ), + }; + let mut rules_source_order = self.rules_source_order; + + // Take apart the StyleRule into individual Rules and insert + // them into the SelectorMap of that priority. + macro_rules! append( + ($style_rule: ident, $priority: ident) => { + if $style_rule.declarations.$priority.len() > 0 { + for selector in &$style_rule.selectors { + let map = match selector.pseudo_element { + None => &mut element_map, + Some(PseudoElement::Before) => &mut before_map, + Some(PseudoElement::After) => &mut after_map, + }; + map.$priority.insert(Rule { + selector: selector.compound_selectors.clone(), + declarations: DeclarationBlock { + specificity: selector.specificity, + declarations: $style_rule.declarations.$priority.clone(), + source_order: rules_source_order, + }, + }); + } + } + }; + ); + + for style_rule in stylesheet.effective_rules(&self.device).style() { + append!(style_rule, normal); + append!(style_rule, important); + rules_source_order += 1; + for selector in &style_rule.selectors { + self.state_deps.note_selector(selector.compound_selectors.clone()); + } + } + self.rules_source_order = rules_source_order; } pub fn restyle_hint_for_state_change(&self, element: &E, @@ -180,35 +212,29 @@ impl Stylist { self.state_deps.compute_hint(element, current_state, old_state) } - pub fn set_device(&mut self, device: Device) { - let is_dirty = self.is_dirty || self.stylesheets.iter() + pub fn set_device(&mut self, mut device: Device, stylesheets: &[&Stylesheet]) { + let cascaded_rule = stylesheets.iter() + .flat_map(|s| s.effective_rules(&self.device).viewport()) + .cascade(); + + self.viewport_constraints = ViewportConstraints::maybe_new(self.device.viewport_size, &cascaded_rule); + if let Some(ref constraints) = self.viewport_constraints { + device = Device::new(MediaType::Screen, constraints.size); + } + let is_device_dirty = self.is_device_dirty || stylesheets.iter() .flat_map(|stylesheet| stylesheet.rules().media()) .any(|media_rule| media_rule.evaluate(&self.device) != media_rule.evaluate(&device)); self.device = device; - self.is_dirty |= is_dirty; + self.is_device_dirty |= is_device_dirty; } - pub fn add_quirks_mode_stylesheet(&mut self) { - match read_resource_file(&["quirks-mode.css"]) { - Ok(res) => { - self.add_stylesheet(Stylesheet::from_bytes( - &res, - Url::parse("chrome:///quirks-mode.css").unwrap(), - None, - None, - Origin::UserAgent)); - } - Err(..) => { - error!("Stylist::add_quirks_mode_stylesheet() failed at loading 'quirks-mode.css'!"); - process::exit(1); - } - } + pub fn viewport_constraints(&self) -> &Option { + &self.viewport_constraints } - pub fn add_stylesheet(&mut self, stylesheet: Stylesheet) { - self.stylesheets.push(stylesheet); - self.is_dirty = true; + pub fn set_quirks_mode(&mut self, enabled: bool) { + self.quirks_mode = enabled; } /// Returns the applicable CSS declarations for the given element. This corresponds to @@ -227,7 +253,7 @@ impl Stylist { -> bool where E: Element + TElementAttributes, V: VecLike { - assert!(!self.is_dirty); + assert!(!self.is_device_dirty); assert!(style_attribute.is_none() || pseudo_element.is_none(), "Style attributes do not apply to pseudo-elements"); @@ -294,8 +320,8 @@ impl Stylist { shareable } - pub fn is_dirty(&self) -> bool { - self.is_dirty + pub fn is_device_dirty(&self) -> bool { + self.is_device_dirty } } diff --git a/servo/components/style/stylesheets.rs b/servo/components/style/stylesheets.rs index c9d5354238a7..00e9def4900d 100644 --- a/servo/components/style/stylesheets.rs +++ b/servo/components/style/stylesheets.rs @@ -17,12 +17,13 @@ use std::iter::Iterator; use std::slice; use string_cache::{Atom, Namespace}; use url::Url; +use util::mem::HeapSizeOf; use viewport::ViewportRule; /// Each style rule has an origin, which determines where it enters the cascade. /// /// http://dev.w3.org/csswg/css-cascade/#cascading-origins -#[derive(Clone, PartialEq, Eq, Copy, Debug)] +#[derive(Clone, PartialEq, Eq, Copy, Debug, HeapSizeOf)] pub enum Origin { /// http://dev.w3.org/csswg/css-cascade/#cascade-origin-ua UserAgent, @@ -35,16 +36,18 @@ pub enum Origin { } -#[derive(Debug, PartialEq)] +#[derive(Debug, HeapSizeOf, PartialEq)] pub struct Stylesheet { /// List of rules in the order they were found (important for /// cascading order) pub rules: Vec, + /// List of media associated with the Stylesheet, if any. + pub media: Option, pub origin: Origin, } -#[derive(Debug, PartialEq)] +#[derive(Debug, HeapSizeOf, PartialEq)] pub enum CSSRule { Charset(String), Namespace(Option, Namespace), @@ -54,7 +57,7 @@ pub enum CSSRule { Viewport(ViewportRule), } -#[derive(Debug, PartialEq)] +#[derive(Debug, HeapSizeOf, PartialEq)] pub struct MediaRule { pub media_queries: MediaQueryList, pub rules: Vec, @@ -67,7 +70,7 @@ impl MediaRule { } } -#[derive(Debug, PartialEq)] +#[derive(Debug, HeapSizeOf, PartialEq)] pub struct StyleRule { pub selectors: Vec, pub declarations: PropertyDeclarationBlock, @@ -131,6 +134,23 @@ impl Stylesheet { Stylesheet { origin: origin, rules: rules, + media: None, + } + } + + /// Set the MediaQueryList associated with the style-sheet. + pub fn set_media(&mut self, media: Option) { + self.media = media; + } + + /// Returns whether the style-sheet applies for the current device depending + /// on the associated MediaQueryList. + /// + /// Always true if no associated MediaQueryList exists. + pub fn is_effective_for_device(&self, device: &Device) -> bool { + match self.media { + Some(ref media) => media.evaluate(device), + None => true } } diff --git a/servo/components/style/viewport.rs b/servo/components/style/viewport.rs index 226edda1fd95..698015686a17 100644 --- a/servo/components/style/viewport.rs +++ b/servo/components/style/viewport.rs @@ -18,10 +18,11 @@ use std::str::Chars; use style_traits::viewport::{Orientation, UserZoom, ViewportConstraints, Zoom}; use stylesheets::Origin; use util::geometry::ViewportPx; +use util::mem::HeapSizeOf; use values::computed::{Context, ToComputedValue}; use values::specified::{Length, LengthOrPercentageOrAuto, ViewportPercentageLength}; -#[derive(Copy, Clone, Debug, PartialEq)] +#[derive(Copy, Clone, Debug, HeapSizeOf, PartialEq)] pub enum ViewportDescriptor { MinWidth(ViewportLength), MaxWidth(ViewportLength), @@ -133,7 +134,7 @@ struct ViewportRuleParser<'a, 'b: 'a> { context: &'a ParserContext<'b> } -#[derive(Copy, Clone, Debug, PartialEq)] +#[derive(Copy, Clone, Debug, HeapSizeOf, PartialEq)] pub struct ViewportDescriptorDeclaration { pub origin: Origin, pub descriptor: ViewportDescriptor, @@ -228,7 +229,7 @@ impl<'a, 'b> DeclarationParser for ViewportRuleParser<'a, 'b> { } } -#[derive(Debug, PartialEq)] +#[derive(Debug, HeapSizeOf, PartialEq)] pub struct ViewportRule { pub declarations: Vec } diff --git a/servo/components/style_traits/viewport.rs b/servo/components/style_traits/viewport.rs index e08fc3909c61..ac7c1a16be6b 100644 --- a/servo/components/style_traits/viewport.rs +++ b/servo/components/style_traits/viewport.rs @@ -20,7 +20,7 @@ define_css_keyword_enum!(Orientation: "landscape" => Landscape); -#[derive(Debug, PartialEq, Deserialize, Serialize)] +#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] pub struct ViewportConstraints { pub size: TypedSize2D, @@ -54,7 +54,7 @@ impl ToCss for ViewportConstraints { /// Zoom is a number | percentage | auto /// See http://dev.w3.org/csswg/css-device-adapt/#descdef-viewport-zoom -#[derive(Copy, Clone, Debug, PartialEq)] +#[derive(Copy, Clone, Debug, HeapSizeOf, PartialEq)] pub enum Zoom { Number(f32), Percentage(f32), diff --git a/servo/tests/unit/style/stylesheets.rs b/servo/tests/unit/style/stylesheets.rs index 6eb9cde4402d..d6a9c6d30ce8 100644 --- a/servo/tests/unit/style/stylesheets.rs +++ b/servo/tests/unit/style/stylesheets.rs @@ -25,6 +25,7 @@ fn test_parse_stylesheet() { let stylesheet = Stylesheet::from_str(css, url, Origin::UserAgent); assert_eq!(stylesheet, Stylesheet { origin: Origin::UserAgent, + media: None, rules: vec![ CSSRule::Namespace(None, ns!(HTML)), CSSRule::Style(StyleRule {