diff options
author | Glenn Watson <gw@intuitionlibrary.com> | 2014-11-04 13:25:07 -0800 |
---|---|---|
committer | Glenn Watson <gw@intuitionlibrary.com> | 2014-11-04 13:25:21 -0800 |
commit | 11cf538ff46914f4db50aad908e5246335af9ba1 (patch) | |
tree | 8139fd410880fc3b8350f332b612aa91151eb36b | |
parent | e483a189a3c24d0fe475cf2a8dedb11821f7ee21 (diff) | |
download | servo-11cf538ff46914f4db50aad908e5246335af9ba1.tar.gz servo-11cf538ff46914f4db50aad908e5246335af9ba1.zip |
Make media queries work with resize and page zoom.
-rw-r--r-- | components/layout/layout_task.rs | 38 | ||||
-rw-r--r-- | components/script/dom/htmlstyleelement.rs | 4 | ||||
-rw-r--r-- | components/style/media_queries.rs | 6 | ||||
-rw-r--r-- | components/style/selector_matching.rs | 156 | ||||
-rw-r--r-- | components/style/stylesheets.rs | 18 |
5 files changed, 135 insertions, 87 deletions
diff --git a/components/layout/layout_task.rs b/components/layout/layout_task.rs index 9edc1e3f1a5..1ed51197db2 100644 --- a/components/layout/layout_task.rs +++ b/components/layout/layout_task.rs @@ -25,6 +25,7 @@ use encoding::all::UTF_8; use geom::point::Point2D; use geom::rect::Rect; use geom::size::Size2D; +use geom::scale_factor::ScaleFactor; use gfx::display_list::{ContentStackingLevel, DisplayItem, DisplayList}; use gfx::display_list::{OpaqueNode}; use gfx::render_task::{RenderInitMsg, RenderChan, RenderLayer}; @@ -93,10 +94,6 @@ pub struct LayoutTaskData { /// This can be used to easily check for invalid stale data. pub generation: uint, - /// True if a style sheet was added since the last reflow. Currently, this causes all nodes to - /// be dirtied at the next reflow. - pub stylesheet_dirty: bool, - /// A queued response for the union of the content boxes of a node. pub content_box_response: Rect<Au>, @@ -147,10 +144,6 @@ pub struct LayoutTask { /// /// All the other elements of this struct are read-only. pub rw_data: Arc<Mutex<LayoutTaskData>>, - - /// The media queries device state. - /// TODO: Handle updating this when window size changes etc. - pub device: Device, } struct LayoutImageResponder { @@ -259,7 +252,7 @@ impl LayoutTask { let local_image_cache = Arc::new(Mutex::new(LocalImageCache::new(image_cache_task.clone()))); let screen_size = Size2D(Au(0), Au(0)); - let device = Device::new(Screen, opts::get().initial_window_size.as_f32()); + let device = Device::new(Screen, opts::get().initial_window_size.as_f32() * ScaleFactor(1.0)); let parallel_traversal = if opts::get().layout_threads != 1 { Some(WorkQueue::new("LayoutWorker", task_state::Layout, opts::get().layout_threads, ptr::null())) @@ -280,17 +273,15 @@ impl LayoutTask { image_cache_task: image_cache_task.clone(), font_cache_task: font_cache_task, first_reflow: Cell::new(true), - device: device, rw_data: Arc::new(Mutex::new( LayoutTaskData { local_image_cache: local_image_cache, screen_size: screen_size, display_list: None, - stylist: box Stylist::new(&device), + stylist: box Stylist::new(device), parallel_traversal: parallel_traversal, dirty: Rect::zero(), generation: 0, - stylesheet_dirty: false, content_box_response: Rect::zero(), content_boxes_response: Vec::new(), })), @@ -491,7 +482,8 @@ impl LayoutTask { let sheet = Stylesheet::from_bytes_iter(iter, final_url, protocol_encoding_label, - Some(environment_encoding)); + Some(environment_encoding), + AuthorOrigin); self.handle_add_stylesheet(sheet, possibly_locked_rw_data); } @@ -501,12 +493,11 @@ impl LayoutTask { &mut Option<MutexGuard<'a, LayoutTaskData>>) { // Find all font-face rules and notify the font cache of them. // GWTODO: Need to handle unloading web fonts (when we handle unloading stylesheets!) - iter_font_face_rules(&sheet, &self.device, |family, src| { + let mut rw_data = self.lock_rw_data(possibly_locked_rw_data); + iter_font_face_rules(&sheet, &rw_data.stylist.device, |family, src| { self.font_cache_task.add_web_font(family.to_string(), (*src).clone()); }); - let mut rw_data = self.lock_rw_data(possibly_locked_rw_data); - rw_data.stylist.add_stylesheet(sheet, AuthorOrigin, &self.device); - rw_data.stylesheet_dirty = true; + rw_data.stylist.add_stylesheet(sheet); LayoutTask::return_rw_data(possibly_locked_rw_data, rw_data); } @@ -757,12 +748,17 @@ impl LayoutTask { &data.url); // Handle conditions where the entire flow tree is invalid. - let needs_dirtying = rw_data.stylesheet_dirty; + let screen_size_changed = current_screen_size != old_screen_size; - let mut needs_reflow = current_screen_size != old_screen_size; + if screen_size_changed { + let device = Device::new(Screen, data.window_size.initial_viewport); + rw_data.stylist.set_device(device); + } + + let needs_dirtying = rw_data.stylist.update(); // If the entire flow tree is invalid, then it will be reflowed anyhow. - needs_reflow &= !needs_dirtying; + let needs_reflow = screen_size_changed && !needs_dirtying; unsafe { if needs_dirtying { @@ -775,8 +771,6 @@ impl LayoutTask { |mut flow| LayoutTask::reflow_all_nodes(flow.deref_mut())); } - rw_data.stylesheet_dirty = false; - let mut layout_root = profile(time::LayoutStyleRecalcCategory, Some((&data.url, data.iframe, diff --git a/components/script/dom/htmlstyleelement.rs b/components/script/dom/htmlstyleelement.rs index f4d6a315638..a989699e7e4 100644 --- a/components/script/dom/htmlstyleelement.rs +++ b/components/script/dom/htmlstyleelement.rs @@ -15,7 +15,7 @@ use dom::node::{Node, NodeHelpers, ElementNodeTypeId, window_from_node}; use dom::virtualmethods::VirtualMethods; use layout_interface::{AddStylesheetMsg, LayoutChan}; use servo_util::str::DOMString; -use style::Stylesheet; +use style::{AuthorOrigin, Stylesheet}; #[dom_struct] pub struct HTMLStyleElement { @@ -55,7 +55,7 @@ impl<'a> StyleElementHelpers for JSRef<'a, HTMLStyleElement> { let url = win.page().get_url(); let data = node.GetTextContent().expect("Element.textContent must be a string"); - let sheet = Stylesheet::from_str(data.as_slice(), url); + let sheet = Stylesheet::from_str(data.as_slice(), url, AuthorOrigin); let LayoutChan(ref layout_chan) = win.page().layout_chan; layout_chan.send(AddStylesheetMsg(sheet)); } diff --git a/components/style/media_queries.rs b/components/style/media_queries.rs index d5e9dad9f12..afa34e1c559 100644 --- a/components/style/media_queries.rs +++ b/components/style/media_queries.rs @@ -13,7 +13,7 @@ use namespaces::NamespaceMap; use parsing_utils::{BufferedIter, ParserIter}; use properties::common_types::*; use properties::longhands; -use servo_util::geometry::ScreenPx; +use servo_util::geometry::ViewportPx; use url::Url; pub struct MediaRule { @@ -83,11 +83,11 @@ pub enum MediaType { pub struct Device { pub media_type: MediaType, - pub viewport_size: TypedSize2D<ScreenPx, f32>, + pub viewport_size: TypedSize2D<ViewportPx, f32>, } impl Device { - pub fn new(media_type: MediaType, viewport_size: TypedSize2D<ScreenPx, f32>) -> Device { + pub fn new(media_type: MediaType, viewport_size: TypedSize2D<ViewportPx, f32>) -> Device { Device { media_type: media_type, viewport_size: viewport_size, diff --git a/components/style/selector_matching.rs b/components/style/selector_matching.rs index 2a6d65f1be7..74d724fe5a5 100644 --- a/components/style/selector_matching.rs +++ b/components/style/selector_matching.rs @@ -24,7 +24,7 @@ use node::{TElement, TElementAttributes, TNode}; use properties::{PropertyDeclaration, PropertyDeclarationBlock, SpecifiedValue, WidthDeclaration}; use properties::{specified}; use selectors::*; -use stylesheets::{Stylesheet, iter_stylesheet_style_rules}; +use stylesheets::{Stylesheet, iter_stylesheet_media_rules, iter_stylesheet_style_rules}; pub enum StylesheetOrigin { UserAgentOrigin, @@ -264,6 +264,18 @@ impl SelectorMap { pub static RECOMMENDED_SELECTOR_BLOOM_FILTER_SIZE: uint = 4096; pub struct Stylist { + // List of stylesheets (including all media rules) + stylesheets: Vec<Stylesheet>, + + // 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, + + // The current selector maps, after evaluating media + // rules against the current device. element_map: PerPseudoElementSelectorMap, before_map: PerPseudoElementSelectorMap, after_map: PerPseudoElementSelectorMap, @@ -272,8 +284,12 @@ pub struct Stylist { impl Stylist { #[inline] - pub fn new(device: &Device) -> Stylist { + pub fn new(device: Device) -> Stylist { let mut stylist = Stylist { + stylesheets: vec!(), + device: device, + is_dirty: true, + element_map: PerPseudoElementSelectorMap::new(), before_map: PerPseudoElementSelectorMap::new(), after_map: PerPseudoElementSelectorMap::new(), @@ -288,63 +304,96 @@ impl Stylist { read_resource_file([filename]).unwrap().as_slice(), Url::parse(format!("chrome:///{}", filename).as_slice()).unwrap(), None, - None); - stylist.add_stylesheet(ua_stylesheet, UserAgentOrigin, device); + None, + UserAgentOrigin); + stylist.add_stylesheet(ua_stylesheet); } stylist } - pub fn add_stylesheet(&mut self, stylesheet: Stylesheet, origin: StylesheetOrigin, - device: &Device) { - let (mut element_map, mut before_map, mut after_map) = match origin { - UserAgentOrigin => ( - &mut self.element_map.user_agent, - &mut self.before_map.user_agent, - &mut self.after_map.user_agent, - ), - AuthorOrigin => ( - &mut self.element_map.author, - &mut self.before_map.author, - &mut self.after_map.author, - ), - UserOrigin => ( - &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.iter() { - let map = match selector.pseudo_element { - None => &mut element_map, - Some(Before) => &mut before_map, - Some(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, - }, - }); - } - } - }; - ); + 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; + + for stylesheet in self.stylesheets.iter() { + let (mut element_map, mut before_map, mut after_map) = match stylesheet.origin { + UserAgentOrigin => ( + &mut self.element_map.user_agent, + &mut self.before_map.user_agent, + &mut self.after_map.user_agent, + ), + AuthorOrigin => ( + &mut self.element_map.author, + &mut self.before_map.author, + &mut self.after_map.author, + ), + UserOrigin => ( + &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.iter() { + let map = match selector.pseudo_element { + None => &mut element_map, + Some(Before) => &mut before_map, + Some(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, + }, + }); + } + } + }; + ); + + iter_stylesheet_style_rules(stylesheet, &self.device, |style_rule| { + append!(style_rule, normal); + append!(style_rule, important); + rules_source_order += 1; + }); + self.rules_source_order = rules_source_order; + } + + self.is_dirty = false; + return true; + } - iter_stylesheet_style_rules(&stylesheet, device, |style_rule| { - append!(style_rule, normal); - append!(style_rule, important); - rules_source_order += 1; + false + } + + pub fn set_device(&mut self, device: Device) { + let is_dirty = self.stylesheets.iter().any(|stylesheet| { + let mut stylesheet_dirty = false; + iter_stylesheet_media_rules(stylesheet, |rule| { + stylesheet_dirty |= rule.media_queries.evaluate(&self.device) != + rule.media_queries.evaluate(&device); + }); + stylesheet_dirty }); - self.rules_source_order = rules_source_order; + + self.device = device; + self.is_dirty |= is_dirty; + } + + pub fn add_stylesheet(&mut self, stylesheet: Stylesheet) { + self.stylesheets.push(stylesheet); + self.is_dirty = true; } /// Returns the applicable CSS declarations for the given element. This corresponds to @@ -364,6 +413,7 @@ impl Stylist { where E: TElement<'a> + TElementAttributes, N: TNode<'a,E>, V: VecLike<DeclarationBlock> { + assert!(!self.is_dirty); assert!(element.is_element()); assert!(style_attribute.is_none() || pseudo_element.is_none(), "Style attributes do not apply to pseudo-elements"); diff --git a/components/style/stylesheets.rs b/components/style/stylesheets.rs index db1f13a8240..a30c5762a1a 100644 --- a/components/style/stylesheets.rs +++ b/components/style/stylesheets.rs @@ -17,12 +17,14 @@ use namespaces::{NamespaceMap, parse_namespace_rule}; use media_queries::{Device, MediaRule, parse_media_rule}; use media_queries; use font_face::{FontFaceRule, Source, parse_font_face_rule, iter_font_face_rules_inner}; +use selector_matching::StylesheetOrigin; pub struct Stylesheet { /// List of rules in the order they were found (important for /// cascading order) rules: Vec<CSSRule>, + pub origin: StylesheetOrigin, } @@ -42,25 +44,25 @@ pub struct StyleRule { impl Stylesheet { pub fn from_bytes_iter<I: Iterator<Vec<u8>>>( mut input: I, base_url: Url, protocol_encoding_label: Option<&str>, - environment_encoding: Option<EncodingRef>) -> Stylesheet { + environment_encoding: Option<EncodingRef>, origin: StylesheetOrigin) -> Stylesheet { let mut bytes = vec!(); // TODO: incremental decoding and tokinization/parsing for chunk in input { bytes.push_all(chunk.as_slice()) } - Stylesheet::from_bytes(bytes.as_slice(), base_url, protocol_encoding_label, environment_encoding) + Stylesheet::from_bytes(bytes.as_slice(), base_url, protocol_encoding_label, environment_encoding, origin) } pub fn from_bytes( bytes: &[u8], base_url: Url, protocol_encoding_label: Option<&str>, - environment_encoding: Option<EncodingRef>) -> Stylesheet { + environment_encoding: Option<EncodingRef>, origin: StylesheetOrigin) -> Stylesheet { // TODO: bytes.as_slice could be bytes.container_as_bytes() let (string, _) = decode_stylesheet_bytes( bytes.as_slice(), protocol_encoding_label, environment_encoding); - Stylesheet::from_str(string.as_slice(), base_url) + Stylesheet::from_str(string.as_slice(), base_url, origin) } - pub fn from_str(css: &str, base_url: Url) -> Stylesheet { + pub fn from_str(css: &str, base_url: Url, origin: StylesheetOrigin) -> Stylesheet { static STATE_CHARSET: uint = 1; static STATE_IMPORTS: uint = 2; static STATE_NAMESPACES: uint = 3; @@ -119,7 +121,10 @@ impl Stylesheet { } state = next_state; } - Stylesheet{ rules: rules } + Stylesheet { + rules: rules, + origin: origin, + } } } @@ -165,7 +170,6 @@ pub fn iter_style_rules<'a>(rules: &[CSSRule], device: &media_queries::Device, } } -#[cfg(test)] pub fn iter_stylesheet_media_rules(stylesheet: &Stylesheet, callback: |&MediaRule|) { for rule in stylesheet.rules.iter() { match *rule { |