Make media queries work with resize and page zoom.

This commit is contained in:
Glenn Watson 2014-11-04 13:25:07 -08:00
parent e483a189a3
commit 11cf538ff4
5 changed files with 134 additions and 86 deletions

View file

@ -25,6 +25,7 @@ use encoding::all::UTF_8;
use geom::point::Point2D; use geom::point::Point2D;
use geom::rect::Rect; use geom::rect::Rect;
use geom::size::Size2D; use geom::size::Size2D;
use geom::scale_factor::ScaleFactor;
use gfx::display_list::{ContentStackingLevel, DisplayItem, DisplayList}; use gfx::display_list::{ContentStackingLevel, DisplayItem, DisplayList};
use gfx::display_list::{OpaqueNode}; use gfx::display_list::{OpaqueNode};
use gfx::render_task::{RenderInitMsg, RenderChan, RenderLayer}; 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. /// This can be used to easily check for invalid stale data.
pub generation: uint, 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. /// A queued response for the union of the content boxes of a node.
pub content_box_response: Rect<Au>, pub content_box_response: Rect<Au>,
@ -147,10 +144,6 @@ pub struct LayoutTask {
/// ///
/// All the other elements of this struct are read-only. /// All the other elements of this struct are read-only.
pub rw_data: Arc<Mutex<LayoutTaskData>>, 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 { struct LayoutImageResponder {
@ -259,7 +252,7 @@ impl LayoutTask {
let local_image_cache = let local_image_cache =
Arc::new(Mutex::new(LocalImageCache::new(image_cache_task.clone()))); Arc::new(Mutex::new(LocalImageCache::new(image_cache_task.clone())));
let screen_size = Size2D(Au(0), Au(0)); 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 { let parallel_traversal = if opts::get().layout_threads != 1 {
Some(WorkQueue::new("LayoutWorker", task_state::Layout, Some(WorkQueue::new("LayoutWorker", task_state::Layout,
opts::get().layout_threads, ptr::null())) opts::get().layout_threads, ptr::null()))
@ -280,17 +273,15 @@ impl LayoutTask {
image_cache_task: image_cache_task.clone(), image_cache_task: image_cache_task.clone(),
font_cache_task: font_cache_task, font_cache_task: font_cache_task,
first_reflow: Cell::new(true), first_reflow: Cell::new(true),
device: device,
rw_data: Arc::new(Mutex::new( rw_data: Arc::new(Mutex::new(
LayoutTaskData { LayoutTaskData {
local_image_cache: local_image_cache, local_image_cache: local_image_cache,
screen_size: screen_size, screen_size: screen_size,
display_list: None, display_list: None,
stylist: box Stylist::new(&device), stylist: box Stylist::new(device),
parallel_traversal: parallel_traversal, parallel_traversal: parallel_traversal,
dirty: Rect::zero(), dirty: Rect::zero(),
generation: 0, generation: 0,
stylesheet_dirty: false,
content_box_response: Rect::zero(), content_box_response: Rect::zero(),
content_boxes_response: Vec::new(), content_boxes_response: Vec::new(),
})), })),
@ -491,7 +482,8 @@ impl LayoutTask {
let sheet = Stylesheet::from_bytes_iter(iter, let sheet = Stylesheet::from_bytes_iter(iter,
final_url, final_url,
protocol_encoding_label, protocol_encoding_label,
Some(environment_encoding)); Some(environment_encoding),
AuthorOrigin);
self.handle_add_stylesheet(sheet, possibly_locked_rw_data); self.handle_add_stylesheet(sheet, possibly_locked_rw_data);
} }
@ -501,12 +493,11 @@ impl LayoutTask {
&mut Option<MutexGuard<'a, LayoutTaskData>>) { &mut Option<MutexGuard<'a, LayoutTaskData>>) {
// Find all font-face rules and notify the font cache of them. // 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 (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()); 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);
rw_data.stylist.add_stylesheet(sheet, AuthorOrigin, &self.device);
rw_data.stylesheet_dirty = true;
LayoutTask::return_rw_data(possibly_locked_rw_data, rw_data); LayoutTask::return_rw_data(possibly_locked_rw_data, rw_data);
} }
@ -757,12 +748,17 @@ impl LayoutTask {
&data.url); &data.url);
// Handle conditions where the entire flow tree is invalid. // 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. // 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 { unsafe {
if needs_dirtying { if needs_dirtying {
@ -775,8 +771,6 @@ impl LayoutTask {
|mut flow| LayoutTask::reflow_all_nodes(flow.deref_mut())); |mut flow| LayoutTask::reflow_all_nodes(flow.deref_mut()));
} }
rw_data.stylesheet_dirty = false;
let mut layout_root = profile(time::LayoutStyleRecalcCategory, let mut layout_root = profile(time::LayoutStyleRecalcCategory,
Some((&data.url, Some((&data.url,
data.iframe, data.iframe,

View file

@ -15,7 +15,7 @@ use dom::node::{Node, NodeHelpers, ElementNodeTypeId, window_from_node};
use dom::virtualmethods::VirtualMethods; use dom::virtualmethods::VirtualMethods;
use layout_interface::{AddStylesheetMsg, LayoutChan}; use layout_interface::{AddStylesheetMsg, LayoutChan};
use servo_util::str::DOMString; use servo_util::str::DOMString;
use style::Stylesheet; use style::{AuthorOrigin, Stylesheet};
#[dom_struct] #[dom_struct]
pub struct HTMLStyleElement { pub struct HTMLStyleElement {
@ -55,7 +55,7 @@ impl<'a> StyleElementHelpers for JSRef<'a, HTMLStyleElement> {
let url = win.page().get_url(); let url = win.page().get_url();
let data = node.GetTextContent().expect("Element.textContent must be a string"); 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; let LayoutChan(ref layout_chan) = win.page().layout_chan;
layout_chan.send(AddStylesheetMsg(sheet)); layout_chan.send(AddStylesheetMsg(sheet));
} }

View file

@ -13,7 +13,7 @@ use namespaces::NamespaceMap;
use parsing_utils::{BufferedIter, ParserIter}; use parsing_utils::{BufferedIter, ParserIter};
use properties::common_types::*; use properties::common_types::*;
use properties::longhands; use properties::longhands;
use servo_util::geometry::ScreenPx; use servo_util::geometry::ViewportPx;
use url::Url; use url::Url;
pub struct MediaRule { pub struct MediaRule {
@ -83,11 +83,11 @@ pub enum MediaType {
pub struct Device { pub struct Device {
pub media_type: MediaType, pub media_type: MediaType,
pub viewport_size: TypedSize2D<ScreenPx, f32>, pub viewport_size: TypedSize2D<ViewportPx, f32>,
} }
impl Device { 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 { Device {
media_type: media_type, media_type: media_type,
viewport_size: viewport_size, viewport_size: viewport_size,

View file

@ -24,7 +24,7 @@ use node::{TElement, TElementAttributes, TNode};
use properties::{PropertyDeclaration, PropertyDeclarationBlock, SpecifiedValue, WidthDeclaration}; use properties::{PropertyDeclaration, PropertyDeclarationBlock, SpecifiedValue, WidthDeclaration};
use properties::{specified}; use properties::{specified};
use selectors::*; use selectors::*;
use stylesheets::{Stylesheet, iter_stylesheet_style_rules}; use stylesheets::{Stylesheet, iter_stylesheet_media_rules, iter_stylesheet_style_rules};
pub enum StylesheetOrigin { pub enum StylesheetOrigin {
UserAgentOrigin, UserAgentOrigin,
@ -264,6 +264,18 @@ impl SelectorMap {
pub static RECOMMENDED_SELECTOR_BLOOM_FILTER_SIZE: uint = 4096; pub static RECOMMENDED_SELECTOR_BLOOM_FILTER_SIZE: uint = 4096;
pub struct Stylist { 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, element_map: PerPseudoElementSelectorMap,
before_map: PerPseudoElementSelectorMap, before_map: PerPseudoElementSelectorMap,
after_map: PerPseudoElementSelectorMap, after_map: PerPseudoElementSelectorMap,
@ -272,8 +284,12 @@ pub struct Stylist {
impl Stylist { impl Stylist {
#[inline] #[inline]
pub fn new(device: &Device) -> Stylist { pub fn new(device: Device) -> Stylist {
let mut stylist = Stylist { let mut stylist = Stylist {
stylesheets: vec!(),
device: device,
is_dirty: true,
element_map: PerPseudoElementSelectorMap::new(), element_map: PerPseudoElementSelectorMap::new(),
before_map: PerPseudoElementSelectorMap::new(), before_map: PerPseudoElementSelectorMap::new(),
after_map: PerPseudoElementSelectorMap::new(), after_map: PerPseudoElementSelectorMap::new(),
@ -288,63 +304,96 @@ impl Stylist {
read_resource_file([filename]).unwrap().as_slice(), read_resource_file([filename]).unwrap().as_slice(),
Url::parse(format!("chrome:///{}", filename).as_slice()).unwrap(), Url::parse(format!("chrome:///{}", filename).as_slice()).unwrap(),
None, None,
None); None,
stylist.add_stylesheet(ua_stylesheet, UserAgentOrigin, device); UserAgentOrigin);
stylist.add_stylesheet(ua_stylesheet);
} }
stylist stylist
} }
pub fn add_stylesheet(&mut self, stylesheet: Stylesheet, origin: StylesheetOrigin, pub fn update(&mut self) -> bool {
device: &Device) { if self.is_dirty {
let (mut element_map, mut before_map, mut after_map) = match origin { self.element_map = PerPseudoElementSelectorMap::new();
UserAgentOrigin => ( self.before_map = PerPseudoElementSelectorMap::new();
&mut self.element_map.user_agent, self.after_map = PerPseudoElementSelectorMap::new();
&mut self.before_map.user_agent, self.rules_source_order = 0;
&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 for stylesheet in self.stylesheets.iter() {
// them into the SelectorMap of that priority. let (mut element_map, mut before_map, mut after_map) = match stylesheet.origin {
macro_rules! append( UserAgentOrigin => (
($style_rule: ident, $priority: ident) => { &mut self.element_map.user_agent,
if $style_rule.declarations.$priority.len() > 0 { &mut self.before_map.user_agent,
for selector in $style_rule.selectors.iter() { &mut self.after_map.user_agent,
let map = match selector.pseudo_element { ),
None => &mut element_map, AuthorOrigin => (
Some(Before) => &mut before_map, &mut self.element_map.author,
Some(After) => &mut after_map, &mut self.before_map.author,
}; &mut self.after_map.author,
map.$priority.insert(Rule { ),
selector: selector.compound_selectors.clone(), UserOrigin => (
declarations: DeclarationBlock { &mut self.element_map.user,
specificity: selector.specificity, &mut self.before_map.user,
declarations: $style_rule.declarations.$priority.clone(), &mut self.after_map.user,
source_order: rules_source_order, ),
}, };
}); let mut rules_source_order = self.rules_source_order;
}
}
};
);
iter_stylesheet_style_rules(&stylesheet, device, |style_rule| { // Take apart the StyleRule into individual Rules and insert
append!(style_rule, normal); // them into the SelectorMap of that priority.
append!(style_rule, important); macro_rules! append(
rules_source_order += 1; ($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;
}
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 /// Returns the applicable CSS declarations for the given element. This corresponds to
@ -364,6 +413,7 @@ impl Stylist {
where E: TElement<'a> + TElementAttributes, where E: TElement<'a> + TElementAttributes,
N: TNode<'a,E>, N: TNode<'a,E>,
V: VecLike<DeclarationBlock> { V: VecLike<DeclarationBlock> {
assert!(!self.is_dirty);
assert!(element.is_element()); assert!(element.is_element());
assert!(style_attribute.is_none() || pseudo_element.is_none(), assert!(style_attribute.is_none() || pseudo_element.is_none(),
"Style attributes do not apply to pseudo-elements"); "Style attributes do not apply to pseudo-elements");

View file

@ -17,12 +17,14 @@ use namespaces::{NamespaceMap, parse_namespace_rule};
use media_queries::{Device, MediaRule, parse_media_rule}; use media_queries::{Device, MediaRule, parse_media_rule};
use media_queries; use media_queries;
use font_face::{FontFaceRule, Source, parse_font_face_rule, iter_font_face_rules_inner}; use font_face::{FontFaceRule, Source, parse_font_face_rule, iter_font_face_rules_inner};
use selector_matching::StylesheetOrigin;
pub struct Stylesheet { pub struct Stylesheet {
/// List of rules in the order they were found (important for /// List of rules in the order they were found (important for
/// cascading order) /// cascading order)
rules: Vec<CSSRule>, rules: Vec<CSSRule>,
pub origin: StylesheetOrigin,
} }
@ -42,25 +44,25 @@ pub struct StyleRule {
impl Stylesheet { impl Stylesheet {
pub fn from_bytes_iter<I: Iterator<Vec<u8>>>( pub fn from_bytes_iter<I: Iterator<Vec<u8>>>(
mut input: I, base_url: Url, protocol_encoding_label: Option<&str>, 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!(); let mut bytes = vec!();
// TODO: incremental decoding and tokinization/parsing // TODO: incremental decoding and tokinization/parsing
for chunk in input { for chunk in input {
bytes.push_all(chunk.as_slice()) 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( pub fn from_bytes(
bytes: &[u8], base_url: Url, protocol_encoding_label: Option<&str>, 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() // TODO: bytes.as_slice could be bytes.container_as_bytes()
let (string, _) = decode_stylesheet_bytes( let (string, _) = decode_stylesheet_bytes(
bytes.as_slice(), protocol_encoding_label, environment_encoding); 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_CHARSET: uint = 1;
static STATE_IMPORTS: uint = 2; static STATE_IMPORTS: uint = 2;
static STATE_NAMESPACES: uint = 3; static STATE_NAMESPACES: uint = 3;
@ -119,7 +121,10 @@ impl Stylesheet {
} }
state = next_state; 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|) { pub fn iter_stylesheet_media_rules(stylesheet: &Stylesheet, callback: |&MediaRule|) {
for rule in stylesheet.rules.iter() { for rule in stylesheet.rules.iter() {
match *rule { match *rule {