diff options
43 files changed, 1929 insertions, 1347 deletions
diff --git a/Cargo.lock b/Cargo.lock index 3b2c1e05e6e..55d5c936045 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3082,7 +3082,6 @@ version = "0.19.0" dependencies = [ "bitflags 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", "cssparser 0.24.0 (registry+https://github.com/rust-lang/crates.io-index)", - "fnv 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", "fxhash 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", "matches 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", @@ -3485,7 +3484,6 @@ dependencies = [ "html5ever 0.22.3 (registry+https://github.com/rust-lang/crates.io-index)", "itertools 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)", "itoa 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", - "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", "malloc_size_of 0.0.1", @@ -3493,6 +3491,7 @@ dependencies = [ "matches 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", "new-ordered-float 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", "new_debug_unreachable 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "num-derive 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "num-integer 0.1.38 (registry+https://github.com/rust-lang/crates.io-index)", "num-traits 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", "num_cpus 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/components/atoms/static_atoms.txt b/components/atoms/static_atoms.txt index f60c497cf2e..0798179c151 100644 --- a/components/atoms/static_atoms.txt +++ b/components/atoms/static_atoms.txt @@ -66,6 +66,7 @@ reftest-wait reset right sans-serif +scan screen scroll-position search @@ -86,3 +87,4 @@ url waiting webglcontextcreationerror week +width diff --git a/components/selectors/Cargo.toml b/components/selectors/Cargo.toml index 20ca61fb784..6d5a93b3485 100644 --- a/components/selectors/Cargo.toml +++ b/components/selectors/Cargo.toml @@ -24,7 +24,6 @@ bitflags = "1.0" matches = "0.1" cssparser = "0.24.0" log = "0.4" -fnv = "1.0" fxhash = "0.2" phf = "0.7.18" precomputed-hash = "0.1" diff --git a/components/selectors/bloom.rs b/components/selectors/bloom.rs index 16dabf498f9..d9056665ac4 100644 --- a/components/selectors/bloom.rs +++ b/components/selectors/bloom.rs @@ -5,9 +5,7 @@ //! Counting and non-counting Bloom filters tuned for use as ancestor filters //! for selector matching. -use fnv::FnvHasher; use std::fmt::{self, Debug}; -use std::hash::{Hash, Hasher}; // The top 8 bits of the 32-bit hash value are not used by the bloom filter. // Consumers may rely on this to pack hashes more efficiently. @@ -108,43 +106,27 @@ where unreachable!() } + /// Inserts an item with a particular hash into the bloom filter. #[inline] pub fn insert_hash(&mut self, hash: u32) { self.storage.adjust_first_slot(hash, true); self.storage.adjust_second_slot(hash, true); } - /// Inserts an item into the bloom filter. - #[inline] - pub fn insert<T: Hash>(&mut self, elem: &T) { - self.insert_hash(hash(elem)) - } - + /// Removes an item with a particular hash from the bloom filter. #[inline] pub fn remove_hash(&mut self, hash: u32) { self.storage.adjust_first_slot(hash, false); self.storage.adjust_second_slot(hash, false); } - /// Removes an item from the bloom filter. - #[inline] - pub fn remove<T: Hash>(&mut self, elem: &T) { - self.remove_hash(hash(elem)) - } - + /// Check whether the filter might contain an item with the given hash. + /// This can sometimes return true even if the item is not in the filter, + /// but will never return false for items that are actually in the filter. #[inline] pub fn might_contain_hash(&self, hash: u32) -> bool { !self.storage.first_slot_is_empty(hash) && !self.storage.second_slot_is_empty(hash) } - - /// Check whether the filter might contain an item. This can - /// sometimes return true even if the item is not in the filter, - /// but will never return false for items that are actually in the - /// filter. - #[inline] - pub fn might_contain<T: Hash>(&self, elem: &T) -> bool { - self.might_contain_hash(hash(elem)) - } } impl<S> Debug for CountingBloomFilter<S> @@ -296,16 +278,6 @@ impl Clone for BloomStorageBool { } } -fn hash<T: Hash>(elem: &T) -> u32 { - // We generally use FxHasher in Stylo because it's faster than FnvHasher, - // but the increased collision rate has outsized effect on the bloom - // filter, so we use FnvHasher instead here. - let mut hasher = FnvHasher::default(); - elem.hash(&mut hasher); - let hash: u64 = hasher.finish(); - (hash >> 32) as u32 ^ (hash as u32) -} - #[inline] fn hash1(hash: u32) -> u32 { hash & KEY_MASK @@ -318,8 +290,18 @@ fn hash2(hash: u32) -> u32 { #[test] fn create_and_insert_some_stuff() { + use fxhash::FxHasher; + use std::hash::{Hash, Hasher}; use std::mem::transmute; + fn hash_as_str(i: usize) -> u32 { + let mut hasher = FxHasher::default(); + let s = i.to_string(); + s.hash(&mut hasher); + let hash: u64 = hasher.finish(); + (hash >> 32) as u32 ^ (hash as u32) + } + let mut bf = BloomFilter::new(); // Statically assert that ARRAY_SIZE is a multiple of 8, which @@ -329,33 +311,34 @@ fn create_and_insert_some_stuff() { } for i in 0_usize..1000 { - bf.insert(&i); + bf.insert_hash(hash_as_str(i)); } for i in 0_usize..1000 { - assert!(bf.might_contain(&i)); + assert!(bf.might_contain_hash(hash_as_str(i))); } - let false_positives = (1001_usize..2000).filter(|i| bf.might_contain(i)).count(); + let false_positives = + (1001_usize..2000).filter(|i| bf.might_contain_hash(hash_as_str(*i))).count(); - assert!(false_positives < 160, "{} is not < 160", false_positives); // 16%. + assert!(false_positives < 190, "{} is not < 190", false_positives); // 19%. for i in 0_usize..100 { - bf.remove(&i); + bf.remove_hash(hash_as_str(i)); } for i in 100_usize..1000 { - assert!(bf.might_contain(&i)); + assert!(bf.might_contain_hash(hash_as_str(i))); } - let false_positives = (0_usize..100).filter(|i| bf.might_contain(i)).count(); + let false_positives = (0_usize..100).filter(|i| bf.might_contain_hash(hash_as_str(*i))).count(); assert!(false_positives < 20, "{} is not < 20", false_positives); // 20%. bf.clear(); for i in 0_usize..2000 { - assert!(!bf.might_contain(&i)); + assert!(!bf.might_contain_hash(hash_as_str(i))); } } diff --git a/components/selectors/lib.rs b/components/selectors/lib.rs index aa30604bbd1..43e274915cb 100644 --- a/components/selectors/lib.rs +++ b/components/selectors/lib.rs @@ -9,7 +9,6 @@ extern crate bitflags; #[macro_use] extern crate cssparser; -extern crate fnv; extern crate fxhash; #[macro_use] extern crate log; diff --git a/components/style/Cargo.toml b/components/style/Cargo.toml index 8e1b5906158..0795bb7803f 100644 --- a/components/style/Cargo.toml +++ b/components/style/Cargo.toml @@ -47,8 +47,9 @@ malloc_size_of = { path = "../malloc_size_of" } malloc_size_of_derive = { path = "../malloc_size_of_derive" } matches = "0.1" num_cpus = {version = "1.1.0", optional = true} -num-integer = "0.1.32" +num-integer = "0.1" num-traits = "0.2" +num-derive = "0.2" new-ordered-float = "1.0" owning_ref = "0.3.3" parking_lot = "0.6" @@ -72,9 +73,6 @@ unicode-bidi = "0.3" unicode-segmentation = "1.0" void = "1.0.2" -[target.'cfg(windows)'.dependencies] -kernel32-sys = "0.2" - [build-dependencies] lazy_static = "1" log = "0.4" diff --git a/components/style/cbindgen.toml b/components/style/cbindgen.toml index 86c3dddec4c..5a0e5e19ea7 100644 --- a/components/style/cbindgen.toml +++ b/components/style/cbindgen.toml @@ -22,5 +22,5 @@ derive_helper_methods = true [export] prefix = "Style" -include = ["StyleDisplay", "StyleAppearance"] +include = ["StyleDisplay", "StyleAppearance", "StyleDisplayMode"] item_types = ["enums"] diff --git a/components/style/gecko/conversions.rs b/components/style/gecko/conversions.rs index 14fee693a03..3b1f31a1b08 100644 --- a/components/style/gecko/conversions.rs +++ b/components/style/gecko/conversions.rs @@ -659,7 +659,7 @@ pub mod basic_shape { StyleShapeSourceType::None => Some(ShapeSource::None), StyleShapeSourceType::Box => Some(ShapeSource::Box(self.mReferenceBox.into())), StyleShapeSourceType::Shape => { - let other_shape = unsafe { &*self.mBasicShape.mPtr }; + let other_shape = unsafe { &*self.__bindgen_anon_1.mBasicShape.as_ref().mPtr }; let shape = other_shape.into(); let reference_box = if self.mReferenceBox == StyleGeometryBox::NoBox { None @@ -677,7 +677,7 @@ pub mod basic_shape { fn from(other: &'a StyleShapeSource) -> Self { match other.mType { StyleShapeSourceType::URL => unsafe { - let shape_image = &*other.mShapeImage.mPtr; + let shape_image = &*other.__bindgen_anon_1.mShapeImage.as_ref().mPtr; let other_url = RefPtr::new(*shape_image.__bindgen_anon_1.mURLValue.as_ref()); let url = ComputedUrl::from_url_value(other_url); ShapeSource::ImageOrUrl(url) @@ -699,7 +699,7 @@ pub mod basic_shape { unreachable!("FloatAreaShape doesn't support URL!"); }, StyleShapeSourceType::Image => unsafe { - let shape_image = &*other.mShapeImage.mPtr; + let shape_image = &*other.__bindgen_anon_1.mShapeImage.as_ref().mPtr; let image = shape_image.into_image().expect("Cannot convert to Image"); ShapeSource::ImageOrUrl(image) }, diff --git a/components/style/gecko/data.rs b/components/style/gecko/data.rs index 6b9005c7560..0bd938c67e8 100644 --- a/components/style/gecko/data.rs +++ b/components/style/gecko/data.rs @@ -8,7 +8,7 @@ use atomic_refcell::{AtomicRef, AtomicRefCell, AtomicRefMut}; use context::QuirksMode; use dom::TElement; use gecko_bindings::bindings::{self, RawServoStyleSet}; -use gecko_bindings::structs::{self, RawGeckoPresContextOwned, ServoStyleSetSizes, StyleSheet as DomStyleSheet}; +use gecko_bindings::structs::{RawGeckoPresContextOwned, ServoStyleSetSizes, StyleSheet as DomStyleSheet}; use gecko_bindings::structs::{StyleSheetInfo, nsIDocument}; use gecko_bindings::sugar::ownership::{HasArcFFI, HasBoxFFI, HasFFI, HasSimpleFFI}; use invalidation::media_queries::{MediaListKey, ToMediaListKey}; @@ -185,52 +185,20 @@ impl PerDocumentStyleDataImpl { .flush(&StylesheetGuards::same(guard), document_element, snapshots) } - /// Returns whether private browsing is enabled. - fn is_private_browsing_enabled(&self) -> bool { - let doc = self.stylist - .device() - .pres_context() - .mDocument - .raw::<nsIDocument>(); - unsafe { bindings::Gecko_IsPrivateBrowsingEnabled(doc) } - } - - /// Returns whether the document is being used as an image. - fn is_being_used_as_an_image(&self) -> bool { - let doc = self.stylist - .device() - .pres_context() - .mDocument - .raw::<nsIDocument>(); - - unsafe { (*doc).mIsBeingUsedAsImage() } - } - /// Get the default computed values for this document. pub fn default_computed_values(&self) -> &Arc<ComputedValues> { self.stylist.device().default_computed_values_arc() } - /// Returns whether visited links are enabled. - fn visited_links_enabled(&self) -> bool { - unsafe { structs::StaticPrefs_sVarCache_layout_css_visited_links_enabled } - } - /// Returns whether visited styles are enabled. + #[inline] pub fn visited_styles_enabled(&self) -> bool { - if !self.visited_links_enabled() { - return false; - } - - if self.is_private_browsing_enabled() { - return false; - } - - if self.is_being_used_as_an_image() { - return false; - } - - true + let doc = self.stylist + .device() + .pres_context() + .mDocument + .raw::<nsIDocument>(); + unsafe { bindings::Gecko_VisitedStylesEnabled(doc) } } /// Measure heap usage. diff --git a/components/style/gecko/media_features.rs b/components/style/gecko/media_features.rs new file mode 100644 index 00000000000..386d0ad5b43 --- /dev/null +++ b/components/style/gecko/media_features.rs @@ -0,0 +1,610 @@ +/* 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/. */ + +//! Gecko's media feature list and evaluator. + +use Atom; +use app_units::Au; +use euclid::Size2D; +use gecko_bindings::bindings; +use media_queries::Device; +use media_queries::media_feature::{AllowsRanges, ParsingRequirements}; +use media_queries::media_feature::{MediaFeatureDescription, Evaluator}; +use media_queries::media_feature_expression::{AspectRatio, RangeOrOperator}; +use values::computed::CSSPixelLength; +use values::computed::Resolution; + +fn viewport_size(device: &Device) -> Size2D<Au> { + let pc = device.pres_context(); + if pc.mIsRootPaginatedDocument() != 0 { + // We want the page size, including unprintable areas and margins. + // FIXME(emilio, bug 1414600): Not quite! + let area = &pc.mPageSize; + return Size2D::new(Au(area.width), Au(area.height)) + } + device.au_viewport_size() +} + +fn device_size(device: &Device) -> Size2D<Au> { + let mut width = 0; + let mut height = 0; + unsafe { + bindings::Gecko_MediaFeatures_GetDeviceSize( + device.document(), + &mut width, + &mut height, + ); + } + Size2D::new(Au(width), Au(height)) +} + +/// https://drafts.csswg.org/mediaqueries-4/#width +fn eval_width( + device: &Device, + value: Option<CSSPixelLength>, + range_or_operator: Option<RangeOrOperator>, +) -> bool { + RangeOrOperator::evaluate( + range_or_operator, + value.map(Au::from), + viewport_size(device).width, + ) +} + +/// https://drafts.csswg.org/mediaqueries-4/#device-width +fn eval_device_width( + device: &Device, + value: Option<CSSPixelLength>, + range_or_operator: Option<RangeOrOperator>, +) -> bool { + RangeOrOperator::evaluate( + range_or_operator, + value.map(Au::from), + device_size(device).width, + ) +} + +/// https://drafts.csswg.org/mediaqueries-4/#height +fn eval_height( + device: &Device, + value: Option<CSSPixelLength>, + range_or_operator: Option<RangeOrOperator>, +) -> bool { + RangeOrOperator::evaluate( + range_or_operator, + value.map(Au::from), + viewport_size(device).height, + ) +} + +/// https://drafts.csswg.org/mediaqueries-4/#device-height +fn eval_device_height( + device: &Device, + value: Option<CSSPixelLength>, + range_or_operator: Option<RangeOrOperator>, +) -> bool { + RangeOrOperator::evaluate( + range_or_operator, + value.map(Au::from), + device_size(device).height, + ) +} + +fn eval_aspect_ratio_for<F>( + device: &Device, + query_value: Option<AspectRatio>, + range_or_operator: Option<RangeOrOperator>, + get_size: F, +) -> bool +where + F: FnOnce(&Device) -> Size2D<Au>, +{ + let query_value = match query_value { + Some(v) => v, + None => return true, + }; + + let size = get_size(device); + let value = AspectRatio(size.width.0 as u32, size.height.0 as u32); + RangeOrOperator::evaluate_with_query_value(range_or_operator, query_value, value) +} + +/// https://drafts.csswg.org/mediaqueries-4/#aspect-ratio +fn eval_aspect_ratio( + device: &Device, + query_value: Option<AspectRatio>, + range_or_operator: Option<RangeOrOperator>, +) -> bool { + eval_aspect_ratio_for(device, query_value, range_or_operator, viewport_size) +} + +/// https://drafts.csswg.org/mediaqueries-4/#device-aspect-ratio +fn eval_device_aspect_ratio( + device: &Device, + query_value: Option<AspectRatio>, + range_or_operator: Option<RangeOrOperator>, +) -> bool { + eval_aspect_ratio_for(device, query_value, range_or_operator, device_size) +} + +/// https://compat.spec.whatwg.org/#css-media-queries-webkit-device-pixel-ratio +/// +/// FIXME(emilio): This should be an alias of `resolution`, according to the +/// spec, and also according to the code in Chromium. Unify with +/// `eval_resolution`. +fn eval_device_pixel_ratio( + device: &Device, + query_value: Option<f32>, + range_or_operator: Option<RangeOrOperator>, +) -> bool { + eval_resolution( + device, + query_value.map(Resolution::from_dppx), + range_or_operator, + ) +} + +#[derive(Clone, Copy, Debug, FromPrimitive, Parse, ToCss)] +#[repr(u8)] +enum Orientation { + Landscape, + Portrait, +} + +fn eval_orientation_for<F>( + device: &Device, + value: Option<Orientation>, + get_size: F, +) -> bool +where + F: FnOnce(&Device) -> Size2D<Au>, +{ + let query_orientation = match value { + Some(v) => v, + None => return true, + }; + + let size = get_size(device); + + // Per spec, square viewports should be 'portrait' + let is_landscape = size.width > size.height; + match query_orientation { + Orientation::Landscape => is_landscape, + Orientation::Portrait => !is_landscape, + } +} + +/// https://drafts.csswg.org/mediaqueries-4/#orientation +fn eval_orientation( + device: &Device, + value: Option<Orientation>, +) -> bool { + eval_orientation_for(device, value, viewport_size) +} + +/// FIXME: There's no spec for `-moz-device-orientation`. +fn eval_device_orientation( + device: &Device, + value: Option<Orientation>, +) -> bool { + eval_orientation_for(device, value, device_size) +} + +/// Values for the display-mode media feature. +#[derive(Clone, Copy, Debug, FromPrimitive, Parse, ToCss)] +#[repr(u8)] +#[allow(missing_docs)] +pub enum DisplayMode { + Browser = 0, + MinimalUi, + Standalone, + Fullscreen, +} + +/// https://w3c.github.io/manifest/#the-display-mode-media-feature +fn eval_display_mode( + device: &Device, + query_value: Option<DisplayMode>, +) -> bool { + let query_value = match query_value { + Some(v) => v, + None => return true, + }; + + let gecko_display_mode = unsafe { + bindings::Gecko_MediaFeatures_GetDisplayMode(device.document()) + }; + + // NOTE: cbindgen guarantees the same representation. + gecko_display_mode as u8 == query_value as u8 +} + +/// https://drafts.csswg.org/mediaqueries-4/#grid +fn eval_grid(_: &Device, query_value: Option<bool>, _: Option<RangeOrOperator>) -> bool { + // Gecko doesn't support grid devices (e.g., ttys), so the 'grid' feature + // is always 0. + let supports_grid = false; + query_value.map_or(supports_grid, |v| v == supports_grid) +} + +/// https://compat.spec.whatwg.org/#css-media-queries-webkit-transform-3d +fn eval_transform_3d( + _: &Device, + query_value: Option<bool>, + _: Option<RangeOrOperator>, +) -> bool { + let supports_transforms = true; + query_value.map_or(supports_transforms, |v| v == supports_transforms) +} + +#[derive(Clone, Copy, Debug, FromPrimitive, Parse, ToCss)] +#[repr(u8)] +enum Scan { + Progressive, + Interlace, +} + +/// https://drafts.csswg.org/mediaqueries-4/#scan +fn eval_scan(_: &Device, _: Option<Scan>) -> bool { + // Since Gecko doesn't support the 'tv' media type, the 'scan' feature never + // matches. + false +} + +/// https://drafts.csswg.org/mediaqueries-4/#color +fn eval_color( + device: &Device, + query_value: Option<u32>, + range_or_operator: Option<RangeOrOperator>, +) -> bool { + let color_bits_per_channel = + unsafe { bindings::Gecko_MediaFeatures_GetColorDepth(device.document()) }; + RangeOrOperator::evaluate( + range_or_operator, + query_value, + color_bits_per_channel, + ) +} + +/// https://drafts.csswg.org/mediaqueries-4/#color-index +fn eval_color_index( + _: &Device, + query_value: Option<u32>, + range_or_operator: Option<RangeOrOperator>, +) -> bool { + // We should return zero if the device does not use a color lookup table. + let index = 0; + RangeOrOperator::evaluate( + range_or_operator, + query_value, + index, + ) +} + +/// https://drafts.csswg.org/mediaqueries-4/#monochrome +fn eval_monochrome( + _: &Device, + query_value: Option<u32>, + range_or_operator: Option<RangeOrOperator>, +) -> bool { + // For color devices we should return 0. + // FIXME: On a monochrome device, return the actual color depth, not 0! + let depth = 0; + RangeOrOperator::evaluate( + range_or_operator, + query_value, + depth, + ) +} + +/// https://drafts.csswg.org/mediaqueries-4/#resolution +fn eval_resolution( + device: &Device, + query_value: Option<Resolution>, + range_or_operator: Option<RangeOrOperator>, +) -> bool { + let resolution_dppx = + unsafe { bindings::Gecko_MediaFeatures_GetResolution(device.document()) }; + RangeOrOperator::evaluate( + range_or_operator, + query_value.map(|r| r.dppx()), + resolution_dppx, + ) +} + +#[derive(Clone, Copy, Debug, FromPrimitive, Parse, ToCss)] +#[repr(u8)] +enum PrefersReducedMotion { + NoPreference, + Reduce, +} + +/// https://drafts.csswg.org/mediaqueries-5/#prefers-reduced-motion +fn eval_prefers_reduced_motion( + device: &Device, + query_value: Option<PrefersReducedMotion>, +) -> bool { + let prefers_reduced = + unsafe { bindings::Gecko_MediaFeatures_PrefersReducedMotion(device.document()) }; + let query_value = match query_value { + Some(v) => v, + None => return prefers_reduced, + }; + + match query_value { + PrefersReducedMotion::NoPreference => !prefers_reduced, + PrefersReducedMotion::Reduce => prefers_reduced, + } +} + +fn eval_moz_is_glyph( + device: &Device, + query_value: Option<bool>, + _: Option<RangeOrOperator>, +) -> bool { + let is_glyph = unsafe { (*device.document()).mIsSVGGlyphsDocument() }; + query_value.map_or(is_glyph, |v| v == is_glyph) +} + +fn eval_moz_is_resource_document( + device: &Device, + query_value: Option<bool>, + _: Option<RangeOrOperator>, +) -> bool { + let is_resource_doc = unsafe { + bindings::Gecko_MediaFeatures_IsResourceDocument(device.document()) + }; + query_value.map_or(is_resource_doc, |v| v == is_resource_doc) +} + +fn eval_system_metric( + device: &Device, + query_value: Option<bool>, + metric: Atom, + accessible_from_content: bool, +) -> bool { + let supports_metric = unsafe { + bindings::Gecko_MediaFeatures_HasSystemMetric( + device.document(), + metric.as_ptr(), + accessible_from_content, + ) + }; + query_value.map_or(supports_metric, |v| v == supports_metric) +} + +fn eval_moz_touch_enabled( + device: &Device, + query_value: Option<bool>, + _: Option<RangeOrOperator>, +) -> bool { + eval_system_metric( + device, + query_value, + atom!("-moz-touch-enabled"), + /* accessible_from_content = */ true, + ) +} + +fn eval_moz_os_version( + device: &Device, + query_value: Option<Atom>, + _: Option<RangeOrOperator>, +) -> bool { + let query_value = match query_value { + Some(v) => v, + None => return false, + }; + + let os_version = unsafe { + bindings::Gecko_MediaFeatures_GetOperatingSystemVersion(device.document()) + }; + + query_value.as_ptr() == os_version +} + +macro_rules! system_metric_feature { + ($feature_name:expr) => { + { + fn __eval( + device: &Device, + query_value: Option<bool>, + _: Option<RangeOrOperator>, + ) -> bool { + eval_system_metric( + device, + query_value, + $feature_name, + /* accessible_from_content = */ false, + ) + } + + feature!( + $feature_name, + AllowsRanges::No, + Evaluator::BoolInteger(__eval), + ParsingRequirements::CHROME_AND_UA_ONLY, + ) + } + } +} + +lazy_static! { + /// Adding new media features requires (1) adding the new feature to this + /// array, with appropriate entries (and potentially any new code needed + /// to support new types in these entries and (2) ensuring that either + /// nsPresContext::MediaFeatureValuesChanged is called when the value that + /// would be returned by the evaluator function could change. + pub static ref MEDIA_FEATURES: [MediaFeatureDescription; 43] = [ + feature!( + atom!("width"), + AllowsRanges::Yes, + Evaluator::Length(eval_width), + ParsingRequirements::empty(), + ), + feature!( + atom!("height"), + AllowsRanges::Yes, + Evaluator::Length(eval_height), + ParsingRequirements::empty(), + ), + feature!( + atom!("aspect-ratio"), + AllowsRanges::Yes, + Evaluator::IntRatio(eval_aspect_ratio), + ParsingRequirements::empty(), + ), + feature!( + atom!("orientation"), + AllowsRanges::No, + keyword_evaluator!(eval_orientation, Orientation), + ParsingRequirements::empty(), + ), + feature!( + atom!("device-width"), + AllowsRanges::Yes, + Evaluator::Length(eval_device_width), + ParsingRequirements::empty(), + ), + feature!( + atom!("device-height"), + AllowsRanges::Yes, + Evaluator::Length(eval_device_height), + ParsingRequirements::empty(), + ), + feature!( + atom!("device-aspect-ratio"), + AllowsRanges::Yes, + Evaluator::IntRatio(eval_device_aspect_ratio), + ParsingRequirements::empty(), + ), + feature!( + atom!("-moz-device-orientation"), + AllowsRanges::No, + keyword_evaluator!(eval_device_orientation, Orientation), + ParsingRequirements::empty(), + ), + // Webkit extensions that we support for de-facto web compatibility. + // -webkit-{min|max}-device-pixel-ratio (controlled with its own pref): + feature!( + atom!("device-pixel-ratio"), + AllowsRanges::Yes, + Evaluator::Float(eval_device_pixel_ratio), + ParsingRequirements::WEBKIT_PREFIX | + ParsingRequirements::WEBKIT_DEVICE_PIXEL_RATIO_PREF_ENABLED, + ), + // -webkit-transform-3d. + feature!( + atom!("transform-3d"), + AllowsRanges::No, + Evaluator::BoolInteger(eval_transform_3d), + ParsingRequirements::WEBKIT_PREFIX, + ), + feature!( + atom!("-moz-device-pixel-ratio"), + AllowsRanges::Yes, + Evaluator::Float(eval_device_pixel_ratio), + ParsingRequirements::empty(), + ), + feature!( + atom!("resolution"), + AllowsRanges::Yes, + Evaluator::Resolution(eval_resolution), + ParsingRequirements::empty(), + ), + feature!( + atom!("display-mode"), + AllowsRanges::No, + keyword_evaluator!(eval_display_mode, DisplayMode), + ParsingRequirements::empty(), + ), + feature!( + atom!("grid"), + AllowsRanges::No, + Evaluator::BoolInteger(eval_grid), + ParsingRequirements::empty(), + ), + feature!( + atom!("scan"), + AllowsRanges::No, + keyword_evaluator!(eval_scan, Scan), + ParsingRequirements::empty(), + ), + feature!( + atom!("color"), + AllowsRanges::Yes, + Evaluator::Integer(eval_color), + ParsingRequirements::empty(), + ), + feature!( + atom!("color-index"), + AllowsRanges::Yes, + Evaluator::Integer(eval_color_index), + ParsingRequirements::empty(), + ), + feature!( + atom!("monochrome"), + AllowsRanges::Yes, + Evaluator::Integer(eval_monochrome), + ParsingRequirements::empty(), + ), + feature!( + atom!("prefers-reduced-motion"), + AllowsRanges::No, + keyword_evaluator!(eval_prefers_reduced_motion, PrefersReducedMotion), + ParsingRequirements::empty(), + ), + + // Internal -moz-is-glyph media feature: applies only inside SVG glyphs. + // Internal because it is really only useful in the user agent anyway + // and therefore not worth standardizing. + feature!( + atom!("-moz-is-glyph"), + AllowsRanges::No, + Evaluator::BoolInteger(eval_moz_is_glyph), + ParsingRequirements::CHROME_AND_UA_ONLY, + ), + feature!( + atom!("-moz-is-resource-document"), + AllowsRanges::No, + Evaluator::BoolInteger(eval_moz_is_resource_document), + ParsingRequirements::CHROME_AND_UA_ONLY, + ), + feature!( + atom!("-moz-os-version"), + AllowsRanges::No, + Evaluator::Ident(eval_moz_os_version), + ParsingRequirements::CHROME_AND_UA_ONLY, + ), + system_metric_feature!(atom!("-moz-scrollbar-start-backward")), + system_metric_feature!(atom!("-moz-scrollbar-start-forward")), + system_metric_feature!(atom!("-moz-scrollbar-end-backward")), + system_metric_feature!(atom!("-moz-scrollbar-end-forward")), + system_metric_feature!(atom!("-moz-scrollbar-thumb-proportional")), + system_metric_feature!(atom!("-moz-overlay-scrollbars")), + system_metric_feature!(atom!("-moz-windows-default-theme")), + system_metric_feature!(atom!("-moz-mac-graphite-theme")), + system_metric_feature!(atom!("-moz-mac-yosemite-theme")), + system_metric_feature!(atom!("-moz-windows-accent-color-in-titlebar")), + system_metric_feature!(atom!("-moz-windows-compositor")), + system_metric_feature!(atom!("-moz-windows-classic")), + system_metric_feature!(atom!("-moz-windows-glass")), + system_metric_feature!(atom!("-moz-menubar-drag")), + system_metric_feature!(atom!("-moz-swipe-animation-enabled")), + system_metric_feature!(atom!("-moz-gtk-csd-available")), + system_metric_feature!(atom!("-moz-gtk-csd-minimize-button")), + system_metric_feature!(atom!("-moz-gtk-csd-maximize-button")), + system_metric_feature!(atom!("-moz-gtk-csd-close-button")), + system_metric_feature!(atom!("-moz-system-dark-theme")), + // This is the only system-metric media feature that's accessible to + // content as of today. + // FIXME(emilio): Restrict (or remove?) when bug 1035774 lands. + feature!( + atom!("-moz-touch-enabled"), + AllowsRanges::No, + Evaluator::BoolInteger(eval_moz_touch_enabled), + ParsingRequirements::empty(), + ), + ]; +} diff --git a/components/style/gecko/media_queries.rs b/components/style/gecko/media_queries.rs index ff4ca2b47cd..c41bc4ffd73 100644 --- a/components/style/gecko/media_queries.rs +++ b/components/style/gecko/media_queries.rs @@ -6,34 +6,23 @@ use app_units::AU_PER_PX; use app_units::Au; -use context::QuirksMode; -use cssparser::{Parser, RGBA, Token}; +use cssparser::RGBA; use euclid::Size2D; use euclid::TypedScale; use gecko::values::{convert_nscolor_to_rgba, convert_rgba_to_nscolor}; use gecko_bindings::bindings; use gecko_bindings::structs; -use gecko_bindings::structs::{nsCSSKTableEntry, nsCSSKeyword, nsCSSUnit, nsCSSValue}; -use gecko_bindings::structs::{nsMediaFeature, nsMediaFeature_RangeType}; -use gecko_bindings::structs::{nsMediaFeature_ValueType, nsPresContext}; -use gecko_bindings::structs::RawGeckoPresContextOwned; -use gecko_bindings::structs::nsCSSKeywordAndBoolTableEntry; +use gecko_bindings::structs::{nsPresContext, RawGeckoPresContextOwned}; use media_queries::MediaType; -use parser::{Parse, ParserContext}; use properties::ComputedValues; use servo_arc::Arc; -use std::fmt::{self, Write}; +use std::fmt; use std::sync::atomic::{AtomicBool, AtomicIsize, AtomicUsize, Ordering}; -use str::{starts_with_ignore_ascii_case, string_as_ascii_lowercase}; use string_cache::Atom; -use style_traits::{CSSPixel, CssWriter, DevicePixel}; -use style_traits::{ParseError, StyleParseErrorKind, ToCss}; +use style_traits::{CSSPixel, DevicePixel}; use style_traits::viewport::ViewportConstraints; -use stylesheets::Origin; -use values::{serialize_atom_identifier, CSSFloat, CustomIdent, KeyframesName}; -use values::computed::{self, ToComputedValue}; +use values::{CustomIdent, KeyframesName}; use values::computed::font::FontSize; -use values::specified::{Integer, Length, Number, Resolution}; /// The `Device` in Gecko wraps a pres context, has a default values computed, /// and contains all the viewport rule state. @@ -71,11 +60,8 @@ impl fmt::Debug for Device { let mut doc_uri = nsCString::new(); unsafe { - let doc = - &*self.pres_context().mDocument.raw::<structs::nsIDocument>(); - bindings::Gecko_nsIURI_Debug( - doc.mDocumentURI.raw::<structs::nsIURI>(), + (*self.document()).mDocumentURI.raw::<structs::nsIURI>(), &mut doc_uri, ) }; @@ -157,10 +143,17 @@ impl Device { } /// Gets the pres context associated with this document. + #[inline] pub fn pres_context(&self) -> &nsPresContext { unsafe { &*self.pres_context } } + /// Gets the document pointer. + #[inline] + pub fn document(&self) -> *mut structs::nsIDocument { + self.pres_context().mDocument.raw::<structs::nsIDocument>() + } + /// Recreates the default computed values. pub fn reset_computed_values(&mut self) { self.default_values = ComputedValues::default_values(self.pres_context()); @@ -248,758 +241,3 @@ impl Device { size.scale_by(1. / self.pres_context().mEffectiveTextZoom) } } - -/// The kind of matching that should be performed on a media feature value. -#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq)] -pub enum Range { - /// At least the specified value. - Min, - /// At most the specified value. - Max, -} - -/// The operator that was specified in this media feature. -#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq)] -enum Operator { - Equal, - GreaterThan, - GreaterThanEqual, - LessThan, - LessThanEqual, -} - -impl ToCss for Operator { - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result - where - W: fmt::Write, - { - dest.write_str(match *self { - Operator::Equal => "=", - Operator::LessThan => "<", - Operator::LessThanEqual => "<=", - Operator::GreaterThan => ">", - Operator::GreaterThanEqual => ">=", - }) - } -} - -/// Either a `Range` or an `Operator`. -/// -/// Ranged media features are not allowed with operations (that'd make no -/// sense). -#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq)] -enum RangeOrOperator { - Range(Range), - Operator(Operator), -} - -/// A feature expression for gecko contains a reference to the media feature, -/// the value the media query contained, and the range to evaluate. -#[derive(Clone, Debug, MallocSizeOf)] -pub struct MediaFeatureExpression { - feature: &'static nsMediaFeature, - value: Option<MediaExpressionValue>, - range_or_operator: Option<RangeOrOperator>, -} - -impl ToCss for MediaFeatureExpression { - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result - where - W: fmt::Write, - { - dest.write_str("(")?; - - if (self.feature.mReqFlags & structs::nsMediaFeature_RequirementFlags_eHasWebkitPrefix) != 0 - { - dest.write_str("-webkit-")?; - } - - if let Some(RangeOrOperator::Range(range)) = self.range_or_operator { - match range { - Range::Min => dest.write_str("min-")?, - Range::Max => dest.write_str("max-")?, - } - } - - // NB: CssStringWriter not needed, feature names are under control. - write!(dest, "{}", unsafe { - Atom::from_static(*self.feature.mName) - })?; - - if let Some(RangeOrOperator::Operator(op)) = self.range_or_operator { - dest.write_char(' ')?; - op.to_css(dest)?; - dest.write_char(' ')?; - } else if self.value.is_some() { - dest.write_str(": ")?; - } - - if let Some(ref val) = self.value { - val.to_css(dest, self)?; - } - - dest.write_str(")") - } -} - -impl PartialEq for MediaFeatureExpression { - fn eq(&self, other: &Self) -> bool { - self.feature.mName == other.feature.mName && self.value == other.value && - self.range_or_operator == other.range_or_operator - } -} - -/// A value found or expected in a media expression. -/// -/// FIXME(emilio): How should calc() serialize in the Number / Integer / -/// BoolInteger / IntRatio case, as computed or as specified value? -/// -/// If the first, this would need to store the relevant values. -/// -/// See: https://github.com/w3c/csswg-drafts/issues/1968 -#[derive(Clone, Debug, MallocSizeOf, PartialEq)] -pub enum MediaExpressionValue { - /// A length. - Length(Length), - /// A (non-negative) integer. - Integer(u32), - /// A floating point value. - Float(CSSFloat), - /// A boolean value, specified as an integer (i.e., either 0 or 1). - BoolInteger(bool), - /// Two integers separated by '/', with optional whitespace on either side - /// of the '/'. - IntRatio(u32, u32), - /// A resolution. - Resolution(Resolution), - /// An enumerated value, defined by the variant keyword table in the - /// feature's `mData` member. - Enumerated(i16), - /// Similar to the above Enumerated but the variant keyword table has an - /// additional field what the keyword value means in the Boolean Context. - BoolEnumerated(i16), - /// An identifier. - Ident(Atom), -} - -impl MediaExpressionValue { - fn from_css_value( - for_expr: &MediaFeatureExpression, - css_value: &nsCSSValue, - ) -> Option<Self> { - // NB: If there's a null value, that means that we don't support the - // feature. - if css_value.mUnit == nsCSSUnit::eCSSUnit_Null { - return None; - } - - match for_expr.feature.mValueType { - nsMediaFeature_ValueType::eLength => { - debug_assert_eq!(css_value.mUnit, nsCSSUnit::eCSSUnit_Pixel); - let pixels = css_value.float_unchecked(); - Some(MediaExpressionValue::Length(Length::from_px(pixels))) - }, - nsMediaFeature_ValueType::eInteger => { - let i = css_value.integer_unchecked(); - debug_assert!(i >= 0); - Some(MediaExpressionValue::Integer(i as u32)) - }, - nsMediaFeature_ValueType::eFloat => { - debug_assert_eq!(css_value.mUnit, nsCSSUnit::eCSSUnit_Number); - Some(MediaExpressionValue::Float(css_value.float_unchecked())) - }, - nsMediaFeature_ValueType::eBoolInteger => { - debug_assert_eq!(css_value.mUnit, nsCSSUnit::eCSSUnit_Integer); - let i = css_value.integer_unchecked(); - debug_assert!(i == 0 || i == 1); - Some(MediaExpressionValue::BoolInteger(i == 1)) - }, - nsMediaFeature_ValueType::eResolution => { - debug_assert_eq!(css_value.mUnit, nsCSSUnit::eCSSUnit_Pixel); - Some(MediaExpressionValue::Resolution(Resolution::Dppx( - css_value.float_unchecked(), - ))) - }, - nsMediaFeature_ValueType::eEnumerated => { - let value = css_value.integer_unchecked() as i16; - Some(MediaExpressionValue::Enumerated(value)) - }, - nsMediaFeature_ValueType::eBoolEnumerated => { - let value = css_value.integer_unchecked() as i16; - Some(MediaExpressionValue::BoolEnumerated(value)) - }, - nsMediaFeature_ValueType::eIdent => { - debug_assert_eq!(css_value.mUnit, nsCSSUnit::eCSSUnit_AtomIdent); - Some(MediaExpressionValue::Ident(unsafe { - Atom::from_raw(*css_value.mValue.mAtom.as_ref()) - })) - }, - nsMediaFeature_ValueType::eIntRatio => { - let array = unsafe { css_value.array_unchecked() }; - debug_assert_eq!(array.len(), 2); - let first = array[0].integer_unchecked(); - let second = array[1].integer_unchecked(); - - debug_assert!(first >= 0 && second >= 0); - Some(MediaExpressionValue::IntRatio(first as u32, second as u32)) - }, - } - } -} - -impl MediaExpressionValue { - fn to_css<W>(&self, dest: &mut CssWriter<W>, for_expr: &MediaFeatureExpression) -> fmt::Result - where - W: fmt::Write, - { - match *self { - MediaExpressionValue::Length(ref l) => l.to_css(dest), - MediaExpressionValue::Integer(v) => v.to_css(dest), - MediaExpressionValue::Float(v) => v.to_css(dest), - MediaExpressionValue::BoolInteger(v) => dest.write_str(if v { "1" } else { "0" }), - MediaExpressionValue::IntRatio(a, b) => { - a.to_css(dest)?; - dest.write_char('/')?; - b.to_css(dest) - }, - MediaExpressionValue::Resolution(ref r) => r.to_css(dest), - MediaExpressionValue::Ident(ref ident) => serialize_atom_identifier(ident, dest), - MediaExpressionValue::Enumerated(value) => unsafe { - let keyword = find_in_table( - *for_expr.feature.mData.mKeywordTable.as_ref(), - |_kw, val| val == value, - |e| e.keyword(), - ).expect("Value not found in the keyword table?"); - - MediaExpressionValue::keyword_to_css(keyword, dest) - }, - MediaExpressionValue::BoolEnumerated(value) => unsafe { - let keyword = find_in_table( - *for_expr.feature.mData.mKeywordAndBoolTable.as_ref(), - |_kw, val| val == value, - |e| e.keyword(), - ).expect("Value not found in the keyword table?"); - - MediaExpressionValue::keyword_to_css(keyword, dest) - } - } - } - - fn keyword_to_css<W>(keyword: nsCSSKeyword, dest: &mut CssWriter<W>) -> fmt::Result - where - W: fmt::Write, - { - use std::{slice, str}; - use std::os::raw::c_char; - - // NB: All the keywords on nsMediaFeatures are static, - // well-formed utf-8. - let mut length = 0; - unsafe { - let buffer: *const c_char = bindings::Gecko_CSSKeywordString(keyword, &mut length); - let buffer = slice::from_raw_parts(buffer as *const u8, length as usize); - - let string = str::from_utf8_unchecked(buffer); - - dest.write_str(string) - } - } -} - -fn find_feature<F>(mut f: F) -> Option<&'static nsMediaFeature> -where - F: FnMut(&'static nsMediaFeature) -> bool, -{ - unsafe { - let mut features = structs::nsMediaFeatures_features.as_ptr(); - while !(*features).mName.is_null() { - if f(&*features) { - return Some(&*features); - } - features = features.offset(1); - } - } - None -} - -trait TableEntry { - fn value(&self) -> i16; - fn keyword(&self) -> nsCSSKeyword; -} - -impl TableEntry for nsCSSKTableEntry { - fn value(&self) -> i16 { - self.mValue - } - fn keyword(&self) -> nsCSSKeyword { - self.mKeyword - } -} - -impl TableEntry for nsCSSKeywordAndBoolTableEntry { - fn value(&self) -> i16 { - self._base.mValue - } - fn keyword(&self) -> nsCSSKeyword { - self._base.mKeyword - } -} - -unsafe fn find_in_table<T, R, FindFunc, ResultFunc>( - current_entry: *const T, - find: FindFunc, - result_func: ResultFunc, -) -> Option<R> -where - T: TableEntry, - FindFunc: Fn(nsCSSKeyword, i16) -> bool, - ResultFunc: Fn(&T) -> R, -{ - let mut current_entry = current_entry; - - loop { - let value = (*current_entry).value(); - let keyword = (*current_entry).keyword(); - - if value == -1 { - return None; // End of the table. - } - - if find(keyword, value) { - return Some(result_func(&*current_entry)); - } - - current_entry = current_entry.offset(1); - } -} - -fn parse_feature_value<'i, 't>( - feature: &nsMediaFeature, - feature_value_type: nsMediaFeature_ValueType, - context: &ParserContext, - input: &mut Parser<'i, 't>, -) -> Result<MediaExpressionValue, ParseError<'i>> { - let value = match feature_value_type { - nsMediaFeature_ValueType::eLength => { - let length = Length::parse_non_negative(context, input)?; - MediaExpressionValue::Length(length) - }, - nsMediaFeature_ValueType::eInteger => { - let integer = Integer::parse_non_negative(context, input)?; - MediaExpressionValue::Integer(integer.value() as u32) - }, - nsMediaFeature_ValueType::eBoolInteger => { - let integer = Integer::parse_non_negative(context, input)?; - let value = integer.value(); - if value > 1 { - return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); - } - MediaExpressionValue::BoolInteger(value == 1) - }, - nsMediaFeature_ValueType::eFloat => { - let number = Number::parse(context, input)?; - MediaExpressionValue::Float(number.get()) - }, - nsMediaFeature_ValueType::eIntRatio => { - let a = Integer::parse_positive(context, input)?; - input.expect_delim('/')?; - let b = Integer::parse_positive(context, input)?; - MediaExpressionValue::IntRatio(a.value() as u32, b.value() as u32) - }, - nsMediaFeature_ValueType::eResolution => { - MediaExpressionValue::Resolution(Resolution::parse(context, input)?) - }, - nsMediaFeature_ValueType::eEnumerated => { - let first_table_entry: *const nsCSSKTableEntry = - unsafe { *feature.mData.mKeywordTable.as_ref() }; - - let value = parse_keyword(input, first_table_entry)?; - - MediaExpressionValue::Enumerated(value) - }, - nsMediaFeature_ValueType::eBoolEnumerated => { - let first_table_entry: *const nsCSSKeywordAndBoolTableEntry = - unsafe { *feature.mData.mKeywordAndBoolTable.as_ref() }; - - let value = parse_keyword(input, first_table_entry)?; - - MediaExpressionValue::BoolEnumerated(value) - }, - nsMediaFeature_ValueType::eIdent => { - MediaExpressionValue::Ident(Atom::from(input.expect_ident()?.as_ref())) - }, - }; - - Ok(value) -} - -/// Parse a keyword and returns the corresponding i16 value. -fn parse_keyword<'i, 't, T>( - input: &mut Parser<'i, 't>, - first_table_entry: *const T, -) -> Result<i16, ParseError<'i>> -where - T: TableEntry, -{ - let location = input.current_source_location(); - let keyword = input.expect_ident()?; - let keyword = unsafe { - bindings::Gecko_LookupCSSKeyword(keyword.as_bytes().as_ptr(), keyword.len() as u32) - }; - - let value = unsafe { - find_in_table(first_table_entry, |kw, _| kw == keyword, |e| e.value()) - }; - - match value { - Some(value) => Ok(value), - None => Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError)), - } -} - -/// Consumes an operation or a colon, or returns an error. -fn consume_operation_or_colon( - input: &mut Parser, -) -> Result<Option<Operator>, ()> { - let first_delim = { - let next_token = match input.next() { - Ok(t) => t, - Err(..) => return Err(()), - }; - - match *next_token { - Token::Colon => return Ok(None), - Token::Delim(oper) => oper, - _ => return Err(()), - } - }; - Ok(Some(match first_delim { - '=' => Operator::Equal, - '>' => { - if input.try(|i| i.expect_delim('=')).is_ok() { - Operator::GreaterThanEqual - } else { - Operator::GreaterThan - } - } - '<' => { - if input.try(|i| i.expect_delim('=')).is_ok() { - Operator::LessThanEqual - } else { - Operator::LessThan - } - } - _ => return Err(()), - })) -} - -impl MediaFeatureExpression { - /// Trivially construct a new expression. - fn new( - feature: &'static nsMediaFeature, - value: Option<MediaExpressionValue>, - range_or_operator: Option<RangeOrOperator>, - ) -> Self { - Self { - feature, - value, - range_or_operator, - } - } - - /// Parse a media expression of the form: - /// - /// ``` - /// (media-feature: media-value) - /// ``` - pub fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - input.expect_parenthesis_block()?; - input.parse_nested_block(|input| { - Self::parse_in_parenthesis_block(context, input) - }) - } - - /// Parse a media feature expression where we've already consumed the - /// parenthesis. - pub fn parse_in_parenthesis_block<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - // FIXME: remove extra indented block when lifetimes are non-lexical - let feature; - let range; - { - let location = input.current_source_location(); - let ident = input.expect_ident()?; - - let mut flags = 0; - - if context.chrome_rules_enabled() || context.stylesheet_origin == Origin::UserAgent - { - flags |= structs::nsMediaFeature_RequirementFlags_eUserAgentAndChromeOnly; - } - - let result = { - let mut feature_name = &**ident; - - if unsafe { structs::StaticPrefs_sVarCache_layout_css_prefixes_webkit } && - starts_with_ignore_ascii_case(feature_name, "-webkit-") - { - feature_name = &feature_name[8..]; - flags |= structs::nsMediaFeature_RequirementFlags_eHasWebkitPrefix; - if unsafe { - structs::StaticPrefs_sVarCache_layout_css_prefixes_device_pixel_ratio_webkit - } { - flags |= structs::nsMediaFeature_RequirementFlags_eWebkitDevicePixelRatioPrefEnabled; - } - } - - let range = if starts_with_ignore_ascii_case(feature_name, "min-") { - feature_name = &feature_name[4..]; - Some(Range::Min) - } else if starts_with_ignore_ascii_case(feature_name, "max-") { - feature_name = &feature_name[4..]; - Some(Range::Max) - } else { - None - }; - - let atom = Atom::from(string_as_ascii_lowercase(feature_name)); - match find_feature(|f| atom.as_ptr() == unsafe { *f.mName as *mut _ }) { - Some(f) => Ok((f, range)), - None => Err(()), - } - }; - - match result { - Ok((f, r)) => { - feature = f; - range = r; - }, - Err(()) => { - return Err(location.new_custom_error( - StyleParseErrorKind::MediaQueryExpectedFeatureName(ident.clone()), - )) - }, - } - - if (feature.mReqFlags & !flags) != 0 { - return Err(location.new_custom_error( - StyleParseErrorKind::MediaQueryExpectedFeatureName(ident.clone()), - )); - } - - if range.is_some() && - feature.mRangeType != nsMediaFeature_RangeType::eMinMaxAllowed - { - return Err(location.new_custom_error( - StyleParseErrorKind::MediaQueryExpectedFeatureName(ident.clone()), - )); - } - } - - let feature_allows_ranges = - feature.mRangeType == nsMediaFeature_RangeType::eMinMaxAllowed; - - let operator = input.try(consume_operation_or_colon); - let operator = match operator { - Err(..) => { - // If there's no colon, this is a media query of the - // form '(<feature>)', that is, there's no value - // specified. - // - // Gecko doesn't allow ranged expressions without a - // value, so just reject them here too. - if range.is_some() { - return Err(input.new_custom_error( - StyleParseErrorKind::RangedExpressionWithNoValue - )); - } - - return Ok(Self::new(feature, None, None)); - } - Ok(operator) => operator, - }; - - let range_or_operator = match range { - Some(range) => { - if operator.is_some() { - return Err(input.new_custom_error( - StyleParseErrorKind::MediaQueryUnexpectedOperator - )); - } - Some(RangeOrOperator::Range(range)) - } - None => { - match operator { - Some(operator) => { - if !feature_allows_ranges { - return Err(input.new_custom_error( - StyleParseErrorKind::MediaQueryUnexpectedOperator - )); - } - Some(RangeOrOperator::Operator(operator)) - } - None => None, - } - } - }; - - let value = - parse_feature_value(feature, feature.mValueType, context, input).map_err(|err| { - err.location - .new_custom_error(StyleParseErrorKind::MediaQueryExpectedFeatureValue) - })?; - - Ok(Self::new(feature, Some(value), range_or_operator)) - } - - /// Returns whether this media query evaluates to true for the given device. - pub fn matches(&self, device: &Device, quirks_mode: QuirksMode) -> bool { - let mut css_value = nsCSSValue::null(); - unsafe { - (self.feature.mGetter.unwrap())( - device - .pres_context() - .mDocument - .raw::<structs::nsIDocument>(), - self.feature, - &mut css_value, - ) - }; - - let value = match MediaExpressionValue::from_css_value(self, &css_value) { - Some(v) => v, - None => return false, - }; - - self.evaluate_against(device, &value, quirks_mode) - } - - fn evaluate_against( - &self, - device: &Device, - actual_value: &MediaExpressionValue, - quirks_mode: QuirksMode, - ) -> bool { - use self::MediaExpressionValue::*; - use std::cmp::Ordering; - - debug_assert!( - self.feature.mRangeType == nsMediaFeature_RangeType::eMinMaxAllowed || - self.range_or_operator.is_none(), - "Whoops, wrong range" - ); - - // http://dev.w3.org/csswg/mediaqueries3/#units - // em units are relative to the initial font-size. - let required_value = match self.value { - Some(ref v) => v, - None => { - // If there's no value, always match unless it's a zero length - // or a zero integer or boolean. - return match *actual_value { - BoolInteger(v) => v, - Integer(v) => v != 0, - Length(ref l) => computed::Context::for_media_query_evaluation( - device, - quirks_mode, - |context| l.to_computed_value(&context).px() != 0., - ), - BoolEnumerated(value) => { - let value = unsafe { - find_in_table( - *self.feature.mData.mKeywordAndBoolTable.as_ref(), - |_kw, val| val == value, - |e| e.mValueInBooleanContext, - ) - }; - value.expect("Value not found in the keyword table?") - }, - _ => true, - }; - }, - }; - - // FIXME(emilio): Handle the possible floating point errors? - let cmp = match (actual_value, required_value) { - (&Length(ref one), &Length(ref other)) => { - computed::Context::for_media_query_evaluation(device, quirks_mode, |context| { - one.to_computed_value(&context) - .to_i32_au() - .cmp(&other.to_computed_value(&context).to_i32_au()) - }) - }, - (&Integer(one), &Integer(ref other)) => one.cmp(other), - (&BoolInteger(one), &BoolInteger(ref other)) => one.cmp(other), - (&Float(one), &Float(ref other)) => one.partial_cmp(other).unwrap(), - (&IntRatio(one_num, one_den), &IntRatio(other_num, other_den)) => { - // Extend to avoid overflow. - (one_num as u64 * other_den as u64).cmp(&(other_num as u64 * one_den as u64)) - }, - (&Resolution(ref one), &Resolution(ref other)) => { - let actual_dpi = unsafe { - if (*device.pres_context).mOverrideDPPX > 0.0 { - self::Resolution::Dppx((*device.pres_context).mOverrideDPPX).to_dpi() - } else { - one.to_dpi() - } - }; - - actual_dpi.partial_cmp(&other.to_dpi()).unwrap() - }, - (&Ident(ref one), &Ident(ref other)) => { - debug_assert_ne!( - self.feature.mRangeType, - nsMediaFeature_RangeType::eMinMaxAllowed - ); - return one == other; - }, - (&Enumerated(one), &Enumerated(other)) => { - debug_assert_ne!( - self.feature.mRangeType, - nsMediaFeature_RangeType::eMinMaxAllowed - ); - return one == other; - }, - (&BoolEnumerated(one), &BoolEnumerated(other)) => { - debug_assert_ne!( - self.feature.mRangeType, - nsMediaFeature_RangeType::eMinMaxAllowed - ); - return one == other; - }, - _ => unreachable!(), - }; - - let range_or_op = match self.range_or_operator { - Some(r) => r, - None => return cmp == Ordering::Equal, - }; - - match range_or_op { - RangeOrOperator::Range(range) => { - cmp == Ordering::Equal || match range { - Range::Min => cmp == Ordering::Greater, - Range::Max => cmp == Ordering::Less, - } - } - RangeOrOperator::Operator(op) => { - match op { - Operator::Equal => cmp == Ordering::Equal, - Operator::GreaterThan => cmp == Ordering::Greater, - Operator::GreaterThanEqual => { - cmp == Ordering::Equal || cmp == Ordering::Greater - } - Operator::LessThan => cmp == Ordering::Less, - Operator::LessThanEqual => { - cmp == Ordering::Equal || cmp == Ordering::Less - } - } - } - } - } -} diff --git a/components/style/gecko/mod.rs b/components/style/gecko/mod.rs index cfdf45d43b8..b9b6886518c 100644 --- a/components/style/gecko/mod.rs +++ b/components/style/gecko/mod.rs @@ -11,6 +11,7 @@ pub mod arc_types; pub mod conversions; pub mod data; pub mod global_style_data; +pub mod media_features; pub mod media_queries; pub mod pseudo_element; pub mod restyle_damage; diff --git a/components/style/gecko/pseudo_element_definition.mako.rs b/components/style/gecko/pseudo_element_definition.mako.rs index b507fafd865..be0318eadea 100644 --- a/components/style/gecko/pseudo_element_definition.mako.rs +++ b/components/style/gecko/pseudo_element_definition.mako.rs @@ -8,9 +8,9 @@ pub enum PseudoElement { % for pseudo in PSEUDOS: /// ${pseudo.value} % if pseudo.is_tree_pseudo_element(): - ${pseudo.capitalized()}(ThinBoxedSlice<Atom>), + ${pseudo.capitalized_pseudo()}(ThinBoxedSlice<Atom>), % else: - ${pseudo.capitalized()}, + ${pseudo.capitalized_pseudo()}, % endif % endfor } @@ -41,7 +41,7 @@ pub const EAGER_PSEUDOS: [PseudoElement; EAGER_PSEUDO_COUNT] = [ ]; <%def name="pseudo_element_variant(pseudo, tree_arg='..')">\ -PseudoElement::${pseudo.capitalized()}${"({})".format(tree_arg) if pseudo.is_tree_pseudo_element() else ""}\ +PseudoElement::${pseudo.capitalized_pseudo()}${"({})".format(tree_arg) if pseudo.is_tree_pseudo_element() else ""}\ </%def> impl PseudoElement { @@ -120,7 +120,7 @@ impl PseudoElement { % elif pseudo.is_anon_box(): structs::CSS_PSEUDO_ELEMENT_ENABLED_IN_UA_SHEETS, % else: - structs::SERVO_CSS_PSEUDO_ELEMENT_FLAGS_${pseudo.original_ident}, + structs::SERVO_CSS_PSEUDO_ELEMENT_FLAGS_${pseudo.pseudo_ident}, % endif % endfor } @@ -132,7 +132,7 @@ impl PseudoElement { match type_ { % for pseudo in PSEUDOS: % if not pseudo.is_anon_box(): - CSSPseudoElementType::${pseudo.original_ident} => { + CSSPseudoElementType::${pseudo.pseudo_ident} => { Some(${pseudo_element_variant(pseudo)}) }, % endif @@ -149,13 +149,13 @@ impl PseudoElement { match *self { % for pseudo in PSEUDOS: % if not pseudo.is_anon_box(): - PseudoElement::${pseudo.capitalized()} => CSSPseudoElementType::${pseudo.original_ident}, + PseudoElement::${pseudo.capitalized_pseudo()} => CSSPseudoElementType::${pseudo.pseudo_ident}, % elif pseudo.is_tree_pseudo_element(): - PseudoElement::${pseudo.capitalized()}(..) => CSSPseudoElementType::XULTree, + PseudoElement::${pseudo.capitalized_pseudo()}(..) => CSSPseudoElementType::XULTree, % elif pseudo.is_inheriting_anon_box(): - PseudoElement::${pseudo.capitalized()} => CSSPseudoElementType_InheritingAnonBox, + PseudoElement::${pseudo.capitalized_pseudo()} => CSSPseudoElementType_InheritingAnonBox, % else: - PseudoElement::${pseudo.capitalized()} => CSSPseudoElementType::NonInheritingAnonBox, + PseudoElement::${pseudo.capitalized_pseudo()} => CSSPseudoElementType::NonInheritingAnonBox, % endif % endfor } @@ -171,7 +171,7 @@ impl PseudoElement { pub fn tree_pseudo_args(&self) -> Option<<&[Atom]> { match *self { % for pseudo in TREE_PSEUDOS: - PseudoElement::${pseudo.capitalized()}(ref args) => Some(args), + PseudoElement::${pseudo.capitalized_pseudo()}(ref args) => Some(args), % endfor _ => None, } @@ -213,7 +213,7 @@ impl PseudoElement { % for pseudo in PSEUDOS: % if pseudo.is_tree_pseudo_element(): if atom == &atom!("${pseudo.value}") { - return Some(PseudoElement::${pseudo.capitalized()}(args.into())); + return Some(PseudoElement::${pseudo.capitalized_pseudo()}(args.into())); } % endif % endfor diff --git a/components/style/gecko/regen_atoms.py b/components/style/gecko/regen_atoms.py index 496c7be720a..13f0a8c0005 100755 --- a/components/style/gecko/regen_atoms.py +++ b/components/style/gecko/regen_atoms.py @@ -20,55 +20,29 @@ PRELUDE = """ * 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/. */ -/* Autogenerated file created by components/style/gecko/binding_tools/regen_atoms.py, DO NOT EDIT DIRECTLY */ +/* Autogenerated file created by components/style/gecko/regen_atoms.py, DO NOT EDIT DIRECTLY */ """[1:] # NOQA: E501 -def gnu_symbolify(source, ident): - return "_ZN{}{}{}{}E".format(len(source.CLASS), source.CLASS, len(ident), ident) +# Matches lines like `GK_ATOM(foo, "foo", 0x12345678, nsStaticAtom, PseudoElementAtom)`. +PATTERN = re.compile('^GK_ATOM\(([^,]*),[^"]*"([^"]*)",\s*(0x[0-9a-f]+),\s*([^,]*),\s*([^)]*)\)', + re.MULTILINE) +FILE = "include/nsGkAtomList.h" +CLASS = "nsGkAtoms" -def msvc64_symbolify(source, ident): - return "?{}@{}@@2PEAV{}@@EA".format(ident, source.CLASS, source.TYPE) +def gnu_symbolify(ident): + return "_ZN{}{}{}{}E".format(len(CLASS), CLASS, len(ident), ident) -def msvc32_symbolify(source, ident): - # Prepend "\x01" to avoid LLVM prefixing the mangled name with "_". - # See https://github.com/rust-lang/rust/issues/36097 - return "\\x01?{}@{}@@2PAV{}@@A".format(ident, source.CLASS, source.TYPE) - - -class GkAtomSource: - PATTERN = re.compile('^(GK_ATOM)\(([^,]*),[^"]*"([^"]*)"\)', - re.MULTILINE) - FILE = "include/nsGkAtomList.h" - CLASS = "nsGkAtoms" - TYPE = "nsStaticAtom" - - -class CSSPseudoElementsAtomSource: - PATTERN = re.compile('^(CSS_PSEUDO_ELEMENT)\(([^,]*),[^"]*"([^"]*)",', - re.MULTILINE) - FILE = "include/nsCSSPseudoElementList.h" - CLASS = "nsCSSPseudoElements" - # NB: nsICSSPseudoElement is effectively the same as a nsStaticAtom, but we need - # this for MSVC name mangling. - TYPE = "nsICSSPseudoElement" +def msvc64_symbolify(ident, ty): + return "?{}@{}@@2PEAV{}@@EA".format(ident, CLASS, ty) -class CSSAnonBoxesAtomSource: - PATTERN = re.compile('^(CSS_ANON_BOX|CSS_NON_INHERITING_ANON_BOX|CSS_WRAPPER_ANON_BOX)\(([^,]*),[^"]*"([^"]*)"\)', # NOQA: E501 - re.MULTILINE) - FILE = "include/nsCSSAnonBoxList.h" - CLASS = "nsCSSAnonBoxes" - TYPE = "nsICSSAnonBoxPseudo" - - -SOURCES = [ - GkAtomSource, - CSSPseudoElementsAtomSource, - CSSAnonBoxesAtomSource, -] +def msvc32_symbolify(ident, ty): + # Prepend "\x01" to avoid LLVM prefixing the mangled name with "_". + # See https://github.com/rust-lang/rust/issues/36097 + return "\\x01?{}@{}@@2PAV{}@@A".format(ident, CLASS, ty) def map_atom(ident): @@ -79,42 +53,47 @@ def map_atom(ident): class Atom: - def __init__(self, source, macro_name, ident, value): - self.ident = "{}_{}".format(source.CLASS, ident) + def __init__(self, ident, value, hash, ty, atom_type): + self.ident = "{}_{}".format(CLASS, ident) self.original_ident = ident self.value = value - self.source = source - self.macro = macro_name + self.hash = hash + # The Gecko type: "nsStaticAtom", "nsICSSPseudoElement", or "nsIAnonBoxPseudo" + self.ty = ty + # The type of atom: "Atom", "PseudoElement", "NonInheritingAnonBox", + # or "InheritingAnonBox" + self.atom_type = atom_type + if self.is_pseudo() or self.is_anon_box(): + self.pseudo_ident = (ident.split("_", 1))[1] if self.is_anon_box(): assert self.is_inheriting_anon_box() or self.is_non_inheriting_anon_box() - def cpp_class(self): - return self.source.CLASS - def gnu_symbol(self): - return gnu_symbolify(self.source, self.original_ident) + return gnu_symbolify(self.original_ident) def msvc32_symbol(self): - return msvc32_symbolify(self.source, self.original_ident) + return msvc32_symbolify(self.original_ident, self.ty) def msvc64_symbol(self): - return msvc64_symbolify(self.source, self.original_ident) + return msvc64_symbolify(self.original_ident, self.ty) def type(self): - return self.source.TYPE + return self.ty + + def capitalized_pseudo(self): + return self.pseudo_ident[0].upper() + self.pseudo_ident[1:] - def capitalized(self): - return self.original_ident[0].upper() + self.original_ident[1:] + def is_pseudo(self): + return self.atom_type == "PseudoElementAtom" def is_anon_box(self): - return self.type() == "nsICSSAnonBoxPseudo" + return self.is_non_inheriting_anon_box() or self.is_inheriting_anon_box() def is_non_inheriting_anon_box(self): - return self.macro == "CSS_NON_INHERITING_ANON_BOX" + return self.atom_type == "NonInheritingAnonBoxAtom" def is_inheriting_anon_box(self): - return (self.macro == "CSS_ANON_BOX" or - self.macro == "CSS_WRAPPER_ANON_BOX") + return self.atom_type == "InheritingAnonBoxAtom" def is_tree_pseudo_element(self): return self.value.startswith(":-moz-tree-") @@ -122,13 +101,13 @@ class Atom: def collect_atoms(objdir): atoms = [] - for source in SOURCES: - path = os.path.abspath(os.path.join(objdir, source.FILE)) - print("cargo:rerun-if-changed={}".format(path)) - with open(path) as f: - content = f.read() - for result in source.PATTERN.finditer(content): - atoms.append(Atom(source, result.group(1), result.group(2), result.group(3))) + path = os.path.abspath(os.path.join(objdir, FILE)) + print("cargo:rerun-if-changed={}".format(path)) + with open(path) as f: + content = f.read() + for result in PATTERN.finditer(content): + atoms.append(Atom(result.group(1), result.group(2), result.group(3), + result.group(4), result.group(5))) return atoms @@ -219,9 +198,9 @@ def write_atom_macro(atoms, file_name): f.write(PRELUDE) f.write(IMPORTS) - for source in SOURCES: - if source.TYPE != "nsStaticAtom": - f.write("pub enum {} {{}}\n\n".format(source.TYPE)) + for ty in sorted(set([atom.type() for atom in atoms])): + if ty != "nsStaticAtom": + f.write("pub enum {} {{}}\n\n".format(ty)) f.write(UNSAFE_STATIC) diff --git a/components/style/gecko/snapshot.rs b/components/style/gecko/snapshot.rs index d70c6259bbf..fc4a9121ae4 100644 --- a/components/style/gecko/snapshot.rs +++ b/components/style/gecko/snapshot.rs @@ -51,10 +51,6 @@ impl GeckoElementSnapshot { (self.mContains as u8 & flags as u8) != 0 } - fn as_ptr(&self) -> *const Self { - self - } - /// Returns true if the snapshot has stored state for pseudo-classes /// that depend on things other than `ElementState`. #[inline] @@ -184,14 +180,7 @@ impl ElementSnapshot for GeckoElementSnapshot { return None; } - let ptr = unsafe { bindings::Gecko_SnapshotAtomAttrValue(self, atom!("id").as_ptr()) }; - - // FIXME(emilio): This should assert, since this flag is exact. - if ptr.is_null() { - None - } else { - Some(unsafe { WeakAtom::new(ptr) }) - } + snapshot_helpers::get_id(&*self.mAttrs) } #[inline] @@ -200,12 +189,7 @@ impl ElementSnapshot for GeckoElementSnapshot { return false; } - snapshot_helpers::has_class( - self.as_ptr(), - name, - case_sensitivity, - bindings::Gecko_SnapshotHasClass, - ) + snapshot_helpers::has_class(name, case_sensitivity, &self.mClass) } #[inline] @@ -217,11 +201,7 @@ impl ElementSnapshot for GeckoElementSnapshot { return; } - snapshot_helpers::each_class( - self.as_ptr(), - callback, - bindings::Gecko_SnapshotClassOrClassList, - ) + snapshot_helpers::each_class(&self.mClass, callback) } #[inline] diff --git a/components/style/gecko/snapshot_helpers.rs b/components/style/gecko/snapshot_helpers.rs index 3f84de7fd50..6c40f242d5f 100644 --- a/components/style/gecko/snapshot_helpers.rs +++ b/components/style/gecko/snapshot_helpers.rs @@ -4,58 +4,114 @@ //! Element an snapshot common logic. -use gecko_bindings::structs::nsAtom; +use CaseSensitivityExt; +use gecko_bindings::bindings; +use gecko_bindings::structs::{self, nsAtom}; use selectors::attr::CaseSensitivity; -use std::{ptr, slice}; -use string_cache::Atom; +use string_cache::{Atom, WeakAtom}; /// A function that, given an element of type `T`, allows you to get a single /// class or a class list. -pub type ClassOrClassList<T> = - unsafe extern "C" fn(T, *mut *mut nsAtom, *mut *mut *mut nsAtom) -> u32; +enum Class<'a> { + None, + One(*const nsAtom), + More(&'a [structs::RefPtr<nsAtom>]), +} + +#[inline(always)] +fn base_type(attr: &structs::nsAttrValue) -> structs::nsAttrValue_ValueBaseType { + (attr.mBits & structs::NS_ATTRVALUE_BASETYPE_MASK) as structs::nsAttrValue_ValueBaseType +} + +#[inline(always)] +unsafe fn ptr<T>(attr: &structs::nsAttrValue) -> *const T { + (attr.mBits & !structs::NS_ATTRVALUE_BASETYPE_MASK) as *const T +} + +#[inline(always)] +unsafe fn get_class_from_attr(attr: &structs::nsAttrValue) -> Class { + debug_assert!(bindings::Gecko_AssertClassAttrValueIsSane(attr)); + let base_type = base_type(attr); + if base_type == structs::nsAttrValue_ValueBaseType_eStringBase { + return Class::None; + } + if base_type == structs::nsAttrValue_ValueBaseType_eAtomBase { + return Class::One(ptr::<nsAtom>(attr)); + } + debug_assert_eq!(base_type, structs::nsAttrValue_ValueBaseType_eOtherBase); + + let container = ptr::<structs::MiscContainer>(attr); + debug_assert_eq!((*container).mType, structs::nsAttrValue_ValueType_eAtomArray); + let array = + (*container).__bindgen_anon_1.mValue.as_ref().__bindgen_anon_1.mAtomArray.as_ref(); + Class::More(&***array) +} + +#[inline(always)] +unsafe fn get_id_from_attr(attr: &structs::nsAttrValue) -> &WeakAtom { + debug_assert_eq!(base_type(attr), structs::nsAttrValue_ValueBaseType_eAtomBase); + WeakAtom::new(ptr::<nsAtom>(attr)) +} -/// A function to return whether an element of type `T` has a given class. -/// -/// The `bool` argument represents whether it should compare case-insensitively -/// or not. -pub type HasClass<T> = unsafe extern "C" fn(T, *mut nsAtom, bool) -> bool; +/// Find an attribute value with a given name and no namespace. +#[inline(always)] +pub fn find_attr<'a>( + attrs: &'a [structs::AttrArray_InternalAttr], + name: &Atom, +) -> Option<&'a structs::nsAttrValue> { + attrs.iter() + .find(|attr| attr.mName.mBits == name.as_ptr() as usize) + .map(|attr| &attr.mValue) +} + +/// Finds the id attribute from a list of attributes. +#[inline(always)] +pub fn get_id(attrs: &[structs::AttrArray_InternalAttr]) -> Option<&WeakAtom> { + Some(unsafe { get_id_from_attr(find_attr(attrs, &atom!("id"))?) }) +} -/// Given an item `T`, a class name, and a getter function, return whether that -/// element has the class that `name` represents. +/// Given a class name, a case sensitivity, and an array of attributes, returns +/// whether the class has the attribute that name represents. #[inline(always)] -pub fn has_class<T>( - item: T, +pub fn has_class( name: &Atom, case_sensitivity: CaseSensitivity, - getter: HasClass<T>, + attr: &structs::nsAttrValue, ) -> bool { - let ignore_case = match case_sensitivity { - CaseSensitivity::CaseSensitive => false, - CaseSensitivity::AsciiCaseInsensitive => true, - }; - - unsafe { getter(item, name.as_ptr(), ignore_case) } + match unsafe { get_class_from_attr(attr) } { + Class::None => false, + Class::One(atom) => unsafe { + case_sensitivity.eq_atom(name, WeakAtom::new(atom)) + }, + Class::More(atoms) => { + match case_sensitivity { + CaseSensitivity::CaseSensitive => { + atoms.iter().any(|atom| atom.mRawPtr == name.as_ptr()) + } + CaseSensitivity::AsciiCaseInsensitive => unsafe { + atoms.iter().any(|atom| WeakAtom::new(atom.mRawPtr).eq_ignore_ascii_case(name)) + } + } + } + } } /// Given an item, a callback, and a getter, execute `callback` for each class /// this `item` has. -pub fn each_class<F, T>(item: T, mut callback: F, getter: ClassOrClassList<T>) +#[inline(always)] +pub fn each_class<F>(attr: &structs::nsAttrValue, mut callback: F) where F: FnMut(&Atom), { unsafe { - let mut class: *mut nsAtom = ptr::null_mut(); - let mut list: *mut *mut nsAtom = ptr::null_mut(); - let length = getter(item, &mut class, &mut list); - match length { - 0 => {}, - 1 => Atom::with(class, callback), - n => { - let classes = slice::from_raw_parts(list, n as usize); - for c in classes { - Atom::with(*c, &mut callback) + match get_class_from_attr(attr) { + Class::None => {}, + Class::One(atom) => Atom::with(atom, callback), + Class::More(atoms) => { + for atom in atoms { + Atom::with(atom.mRawPtr, &mut callback) } - }, + } } } } diff --git a/components/style/gecko/wrapper.rs b/components/style/gecko/wrapper.rs index cf1a2b56ab6..1ba850696d1 100644 --- a/components/style/gecko/wrapper.rs +++ b/components/style/gecko/wrapper.rs @@ -32,7 +32,6 @@ use gecko_bindings::bindings; use gecko_bindings::bindings::{Gecko_ElementState, Gecko_GetDocumentLWTheme}; use gecko_bindings::bindings::{Gecko_GetLastChild, Gecko_GetPreviousSibling, Gecko_GetNextStyleChild}; use gecko_bindings::bindings::{Gecko_SetNodeFlags, Gecko_UnsetNodeFlags}; -use gecko_bindings::bindings::Gecko_ClassOrClassList; use gecko_bindings::bindings::Gecko_ElementHasAnimations; use gecko_bindings::bindings::Gecko_ElementHasCSSAnimations; use gecko_bindings::bindings::Gecko_ElementHasCSSTransitions; @@ -581,6 +580,34 @@ impl<'le> fmt::Debug for GeckoElement<'le> { } impl<'le> GeckoElement<'le> { + #[inline(always)] + fn attrs(&self) -> &[structs::AttrArray_InternalAttr] { + unsafe { + let attrs = match self.0._base.mAttrs.mImpl.mPtr.as_ref() { + Some(attrs) => attrs, + None => return &[], + }; + + attrs.mBuffer.as_slice(attrs.mAttrCount as usize) + } + } + + #[inline(always)] + fn get_class_attr(&self) -> Option<&structs::nsAttrValue> { + if !self.may_have_class() { + return None; + } + + if self.is_svg_element() { + let svg_class = unsafe { bindings::Gecko_GetSVGAnimatedClass(self.0).as_ref() }; + if let Some(c) = svg_class { + return Some(c) + } + } + + snapshot_helpers::find_attr(self.attrs(), &atom!("class")) + } + #[inline] fn closest_anon_subtree_root_parent(&self) -> Option<Self> { debug_assert!(self.is_in_native_anonymous_subtree()); @@ -1281,26 +1308,19 @@ impl<'le> TElement for GeckoElement<'le> { return None; } - let ptr = unsafe { bindings::Gecko_AtomAttrValue(self.0, atom!("id").as_ptr()) }; - - // FIXME(emilio): Pretty sure the has_id flag is exact and we could - // assert here. - if ptr.is_null() { - None - } else { - Some(unsafe { WeakAtom::new(ptr) }) - } + snapshot_helpers::get_id(self.attrs()) } fn each_class<F>(&self, callback: F) where F: FnMut(&Atom), { - if !self.may_have_class() { - return; - } + let attr = match self.get_class_attr() { + Some(c) => c, + None => return, + }; - snapshot_helpers::each_class(self.0, callback, Gecko_ClassOrClassList) + snapshot_helpers::each_class(attr, callback) } #[inline] @@ -2265,24 +2285,22 @@ impl<'le> ::selectors::Element for GeckoElement<'le> { return false; } - unsafe { - let ptr = bindings::Gecko_AtomAttrValue(self.0, atom!("id").as_ptr()); + let element_id = match snapshot_helpers::get_id(self.attrs()) { + Some(id) => id, + None => return false, + }; - if ptr.is_null() { - false - } else { - case_sensitivity.eq_atom(WeakAtom::new(ptr), id) - } - } + case_sensitivity.eq_atom(element_id, id) } #[inline(always)] fn has_class(&self, name: &Atom, case_sensitivity: CaseSensitivity) -> bool { - if !self.may_have_class() { - return false; - } + let attr = match self.get_class_attr() { + Some(c) => c, + None => return false, + }; - snapshot_helpers::has_class(self.0, name, case_sensitivity, bindings::Gecko_HasClass) + snapshot_helpers::has_class(name, case_sensitivity, attr) } #[inline] diff --git a/components/style/gecko_string_cache/mod.rs b/components/style/gecko_string_cache/mod.rs index b0c8750265b..70217f233de 100644 --- a/components/style/gecko_string_cache/mod.rs +++ b/components/style/gecko_string_cache/mod.rs @@ -49,10 +49,6 @@ pub struct Atom(*mut WeakAtom); /// where `'a` is the lifetime of something that holds a strong reference to that atom. pub struct WeakAtom(nsAtom); -/// A BorrowedAtom for Gecko is just a weak reference to a `nsAtom`, that -/// hasn't been bumped. -pub type BorrowedAtom<'a> = &'a WeakAtom; - impl Deref for Atom { type Target = WeakAtom; @@ -267,7 +263,7 @@ impl fmt::Display for WeakAtom { impl Atom { /// Execute a callback with the atom represented by `ptr`. - pub unsafe fn with<F, R>(ptr: *mut nsAtom, callback: F) -> R + pub unsafe fn with<F, R>(ptr: *const nsAtom, callback: F) -> R where F: FnOnce(&Atom) -> R, { diff --git a/components/style/invalidation/element/state_and_attributes.rs b/components/style/invalidation/element/state_and_attributes.rs index 88b012a2524..1eb022b298a 100644 --- a/components/style/invalidation/element/state_and_attributes.rs +++ b/components/style/invalidation/element/state_and_attributes.rs @@ -24,12 +24,6 @@ use selectors::matching::matches_selector; use smallvec::SmallVec; use stylesheets::origin::{Origin, OriginSet}; -#[derive(Debug, PartialEq)] -enum VisitedDependent { - Yes, - No, -} - /// The collector implementation. struct Collector<'a, 'b: 'a, 'selectors: 'a, E> where @@ -167,8 +161,13 @@ where // do for this case. if state_changes.intersects(ElementState::IN_VISITED_OR_UNVISITED_STATE) { trace!(" > visitedness change, force subtree restyle"); + // If we get here with visited links disabled, we should probably + // just avoid the restyle and remove the state change here, not only + // as an optimization, but also because it kind of would kill the + // point of disabling visited links. + debug_assert!(self.shared_context.visited_styles_enabled); // We can't just return here because there may also be attribute - // changes as well that imply additional hints. + // changes as well that imply additional hints for siblings. self.data.hint.insert(RestyleHint::restyle_subtree()); } @@ -347,7 +346,7 @@ where if let Some(ref id) = removed_id { if let Some(deps) = map.id_to_selector.get(id, quirks_mode) { for dep in deps { - self.scan_dependency(dep, VisitedDependent::No); + self.scan_dependency(dep); } } } @@ -356,7 +355,7 @@ where if let Some(ref id) = added_id { if let Some(deps) = map.id_to_selector.get(id, quirks_mode) { for dep in deps { - self.scan_dependency(dep, VisitedDependent::No); + self.scan_dependency(dep); } } } @@ -364,7 +363,7 @@ where for class in self.classes_added.iter().chain(self.classes_removed.iter()) { if let Some(deps) = map.class_to_selector.get(class, quirks_mode) { for dep in deps { - self.scan_dependency(dep, VisitedDependent::No); + self.scan_dependency(dep); } } } @@ -390,7 +389,7 @@ where self.removed_id, self.classes_removed, |dependency| { - self.scan_dependency(dependency, VisitedDependent::No); + self.scan_dependency(dependency); true }, ); @@ -410,98 +409,53 @@ where if !dependency.state.intersects(state_changes) { return true; } - let visited_dependent = if dependency - .state - .intersects(ElementState::IN_VISITED_OR_UNVISITED_STATE) - { - VisitedDependent::Yes - } else { - VisitedDependent::No - }; - self.scan_dependency(&dependency.dep, visited_dependent); + self.scan_dependency(&dependency.dep); true }, ); } - /// Check whether a dependency should be taken into account, using a given - /// visited handling mode. + /// Check whether a dependency should be taken into account. fn check_dependency( &mut self, - visited_handling_mode: VisitedHandlingMode, dependency: &Dependency, ) -> bool { let element = &self.element; let wrapper = &self.wrapper; - self.matching_context - .with_visited_handling_mode(visited_handling_mode, |mut context| { - let matches_now = matches_selector( - &dependency.selector, - dependency.selector_offset, - None, - element, - &mut context, - &mut |_, _| {}, - ); + let matches_now = matches_selector( + &dependency.selector, + dependency.selector_offset, + None, + element, + &mut self.matching_context, + &mut |_, _| {}, + ); - let matched_then = matches_selector( - &dependency.selector, - dependency.selector_offset, - None, - wrapper, - &mut context, - &mut |_, _| {}, - ); + let matched_then = matches_selector( + &dependency.selector, + dependency.selector_offset, + None, + wrapper, + &mut self.matching_context, + &mut |_, _| {}, + ); - matched_then != matches_now - }) + matched_then != matches_now } - fn scan_dependency( - &mut self, - dependency: &'selectors Dependency, - is_visited_dependent: VisitedDependent, - ) { + fn scan_dependency(&mut self, dependency: &'selectors Dependency) { debug!( - "TreeStyleInvalidator::scan_dependency({:?}, {:?}, {:?})", - self.element, dependency, is_visited_dependent, + "TreeStyleInvalidator::scan_dependency({:?}, {:?})", + self.element, dependency ); if !self.dependency_may_be_relevant(dependency) { return; } - let should_account_for_dependency = - self.check_dependency(VisitedHandlingMode::AllLinksVisitedAndUnvisited, dependency); - - if should_account_for_dependency { + if self.check_dependency(dependency) { return self.note_dependency(dependency); } - - // If there is a relevant link, then we also matched in visited - // mode. - // - // Match again in this mode to ensure this also matches. - // - // Note that we never actually match directly against the element's true - // visited state at all, since that would expose us to timing attacks. - // - // The matching process only considers the relevant link state and - // visited handling mode when deciding if visited matches. Instead, we - // are rematching here in case there is some :visited selector whose - // matching result changed for some other state or attribute change of - // this element (for example, for things like [foo]:visited). - // - // NOTE: This thing is actually untested because testing it is flaky, - // see the tests that were added and then backed out in bug 1328509. - if is_visited_dependent == VisitedDependent::Yes && self.element.is_link() { - let should_account_for_dependency = - self.check_dependency(VisitedHandlingMode::RelevantLinkVisited, dependency); - - if should_account_for_dependency { - return self.note_dependency(dependency); - } - } } fn note_dependency(&mut self, dependency: &'selectors Dependency) { diff --git a/components/style/lib.rs b/components/style/lib.rs index a43f3f87c45..1c3630b0986 100644 --- a/components/style/lib.rs +++ b/components/style/lib.rs @@ -67,6 +67,8 @@ extern crate matches; pub extern crate nsstring; #[cfg(feature = "gecko")] extern crate num_cpus; +#[macro_use] +extern crate num_derive; extern crate num_integer; extern crate num_traits; extern crate ordered_float; @@ -128,15 +130,13 @@ pub mod font_face; pub mod font_metrics; #[cfg(feature = "gecko")] #[allow(unsafe_code)] -pub mod gecko; -#[cfg(feature = "gecko")] -#[allow(unsafe_code)] pub mod gecko_bindings; pub mod hash; pub mod invalidation; #[allow(missing_docs)] // TODO. pub mod logical_geometry; pub mod matching; +#[macro_use] pub mod media_queries; pub mod parallel; pub mod parser; @@ -190,11 +190,16 @@ pub mod properties { include!(concat!(env!("OUT_DIR"), "/properties.rs")); } +#[cfg(feature = "gecko")] +#[allow(unsafe_code)] +pub mod gecko; + // uses a macro from properties #[cfg(feature = "servo")] #[allow(unsafe_code)] pub mod servo; + #[cfg(feature = "gecko")] #[allow(unsafe_code, missing_docs)] pub mod gecko_properties { diff --git a/components/style/media_queries/media_condition.rs b/components/style/media_queries/media_condition.rs index 4b80794af39..dbd677d0aee 100644 --- a/components/style/media_queries/media_condition.rs +++ b/components/style/media_queries/media_condition.rs @@ -13,7 +13,6 @@ use std::fmt::{self, Write}; use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss}; use super::{Device, MediaFeatureExpression}; - /// A binary `and` or `or` operator. #[derive(Clone, Copy, Debug, Eq, MallocSizeOf, Parse, PartialEq, ToCss)] #[allow(missing_docs)] diff --git a/components/style/media_queries/media_feature.rs b/components/style/media_queries/media_feature.rs new file mode 100644 index 00000000000..5a5bc88863e --- /dev/null +++ b/components/style/media_queries/media_feature.rs @@ -0,0 +1,183 @@ +/* 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/. */ + +//! Media features. + +use Atom; +use cssparser::Parser; +use parser::ParserContext; +use std::fmt; +use style_traits::ParseError; +use super::Device; +use super::media_feature_expression::{AspectRatio, RangeOrOperator}; +use values::computed::{CSSPixelLength, Resolution}; + +/// A generic discriminant for an enum value. +pub type KeywordDiscriminant = u8; + +type MediaFeatureEvaluator<T> = fn( + device: &Device, + // null == no value was given in the query. + value: Option<T>, + range_or_operator: Option<RangeOrOperator>, +) -> bool; + +/// Serializes a given discriminant. +/// +/// FIXME(emilio): we could prevent this allocation if the ToCss code would +/// generate a method for keywords to get the static string or something. +pub type KeywordSerializer = fn(KeywordDiscriminant) -> String; + +/// Parses a given identifier. +pub type KeywordParser = for <'a, 'i, 't> fn( + context: &'a ParserContext, + input: &'a mut Parser<'i, 't>, +) -> Result<KeywordDiscriminant, ParseError<'i>>; + +/// An evaluator for a given media feature. +/// +/// This determines the kind of values that get parsed, too. +#[allow(missing_docs)] +pub enum Evaluator { + Length(MediaFeatureEvaluator<CSSPixelLength>), + Integer(MediaFeatureEvaluator<u32>), + Float(MediaFeatureEvaluator<f32>), + BoolInteger(MediaFeatureEvaluator<bool>), + /// An integer ratio, such as the one from device-pixel-ratio. + IntRatio(MediaFeatureEvaluator<AspectRatio>), + /// A resolution. + Resolution(MediaFeatureEvaluator<Resolution>), + /// A keyword value. + Enumerated { + /// The parser to get a discriminant given a string. + parser: KeywordParser, + /// The serializer to get a string from a discriminant. + /// + /// This is guaranteed to be called with a keyword that `parser` has + /// produced. + serializer: KeywordSerializer, + /// The evaluator itself. This is guaranteed to be called with a + /// keyword that `parser` has produced. + evaluator: MediaFeatureEvaluator<KeywordDiscriminant>, + }, + Ident(MediaFeatureEvaluator<Atom>), +} + +/// A simple helper macro to create a keyword evaluator. +/// +/// This assumes that keyword feature expressions don't accept ranges, and +/// asserts if that's not true. As of today there's nothing like that (does that +/// even make sense?). +macro_rules! keyword_evaluator { + ($actual_evaluator:ident, $keyword_type:ty) => { + { + fn __parse<'i, 't>( + context: &$crate::parser::ParserContext, + input: &mut $crate::cssparser::Parser<'i, 't>, + ) -> Result< + $crate::media_queries::media_feature::KeywordDiscriminant, + ::style_traits::ParseError<'i>, + > { + let kw = <$keyword_type as $crate::parser::Parse>::parse(context, input)?; + Ok(kw as $crate::media_queries::media_feature::KeywordDiscriminant) + } + + fn __serialize(kw: $crate::media_queries::media_feature::KeywordDiscriminant) -> String { + // This unwrap is ok because the only discriminants that get + // back to us is the ones that `parse` produces. + let value: $keyword_type = + ::num_traits::cast::FromPrimitive::from_u8(kw).unwrap(); + <$keyword_type as ::style_traits::ToCss>::to_css_string(&value) + } + + fn __evaluate( + device: &$crate::media_queries::Device, + value: Option<$crate::media_queries::media_feature::KeywordDiscriminant>, + range_or_operator: Option<$crate::media_queries::media_feature_expression::RangeOrOperator>, + ) -> bool { + debug_assert!( + range_or_operator.is_none(), + "Since when do keywords accept ranges?" + ); + // This unwrap is ok because the only discriminants that get + // back to us is the ones that `parse` produces. + let value: Option<$keyword_type> = + value.map(|kw| ::num_traits::cast::FromPrimitive::from_u8(kw).unwrap()); + $actual_evaluator(device, value) + } + + $crate::media_queries::media_feature::Evaluator::Enumerated { + parser: __parse, + serializer: __serialize, + evaluator: __evaluate, + } + } + } +} + +bitflags! { + /// Different requirements or toggles that change how a expression is + /// parsed. + pub struct ParsingRequirements: u8 { + /// The feature should only be parsed in chrome and ua sheets. + const CHROME_AND_UA_ONLY = 1 << 0; + /// The feature requires a -webkit- prefix. + const WEBKIT_PREFIX = 1 << 1; + /// The feature requires the webkit-device-pixel-ratio preference to be + /// enabled. + const WEBKIT_DEVICE_PIXEL_RATIO_PREF_ENABLED = 1 << 2; + } +} + +/// Whether a media feature allows ranges or not. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[allow(missing_docs)] +pub enum AllowsRanges { + Yes, + No, +} + +/// A description of a media feature. +pub struct MediaFeatureDescription { + /// The media feature name, in ascii lowercase. + pub name: Atom, + /// Whether min- / max- prefixes are allowed or not. + pub allows_ranges: AllowsRanges, + /// The evaluator, which we also use to determine which kind of value to + /// parse. + pub evaluator: Evaluator, + /// Different requirements that need to hold for the feature to be + /// successfully parsed. + pub requirements: ParsingRequirements, +} + +impl MediaFeatureDescription { + /// Whether this media feature allows ranges. + #[inline] + pub fn allows_ranges(&self) -> bool { + self.allows_ranges == AllowsRanges::Yes + } +} + +/// A simple helper to construct a `MediaFeatureDescription`. +macro_rules! feature { + ($name:expr, $allows_ranges:expr, $evaluator:expr, $reqs:expr,) => { + $crate::media_queries::media_feature::MediaFeatureDescription { + name: $name, + allows_ranges: $allows_ranges, + evaluator: $evaluator, + requirements: $reqs, + } + } +} + +impl fmt::Debug for MediaFeatureDescription { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("MediaFeatureExpression") + .field("name", &self.name) + .field("allows_ranges", &self.allows_ranges) + .field("requirements", &self.requirements) + .finish() + } +} diff --git a/components/style/media_queries/media_feature_expression.rs b/components/style/media_queries/media_feature_expression.rs new file mode 100644 index 00000000000..f07b8cc27e4 --- /dev/null +++ b/components/style/media_queries/media_feature_expression.rs @@ -0,0 +1,580 @@ +/* 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/. */ + +//! Parsing for media feature expressions, like `(foo: bar)` or +//! `(width >= 400px)`. + +use Atom; +use context::QuirksMode; +use cssparser::{Parser, Token}; +#[cfg(feature = "gecko")] +use gecko_bindings::structs; +use num_traits::Zero; +use parser::{Parse, ParserContext}; +use std::cmp::{PartialOrd, Ordering}; +use std::fmt::{self, Write}; +use str::{starts_with_ignore_ascii_case, string_as_ascii_lowercase}; +use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss}; +use stylesheets::Origin; +use super::Device; +use super::media_feature::{Evaluator, MediaFeatureDescription}; +use super::media_feature::{ParsingRequirements, KeywordDiscriminant}; +use values::{serialize_atom_identifier, CSSFloat}; +use values::computed::{self, ToComputedValue}; +use values::specified::{Integer, Length, Number, Resolution}; + +/// An aspect ratio, with a numerator and denominator. +#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq)] +pub struct AspectRatio(pub u32, pub u32); + +impl ToCss for AspectRatio { + fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result + where + W: fmt::Write, + { + self.0.to_css(dest)?; + dest.write_char('/')?; + self.1.to_css(dest) + } +} + +impl PartialOrd for AspectRatio { + fn partial_cmp(&self, other: &AspectRatio) -> Option<Ordering> { + u64::partial_cmp( + &(self.0 as u64 * other.1 as u64), + &(self.1 as u64 * other.0 as u64), + ) + } +} + +/// The kind of matching that should be performed on a media feature value. +#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq)] +pub enum Range { + /// At least the specified value. + Min, + /// At most the specified value. + Max, +} + +/// The operator that was specified in this media feature. +#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq)] +pub enum Operator { + /// = + Equal, + /// > + GreaterThan, + /// >= + GreaterThanEqual, + /// < + LessThan, + /// <= + LessThanEqual, +} + +impl ToCss for Operator { + fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result + where + W: fmt::Write, + { + dest.write_str(match *self { + Operator::Equal => "=", + Operator::LessThan => "<", + Operator::LessThanEqual => "<=", + Operator::GreaterThan => ">", + Operator::GreaterThanEqual => ">=", + }) + } +} + +/// Either a `Range` or an `Operator`. +/// +/// Ranged media features are not allowed with operations (that'd make no +/// sense). +#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq)] +pub enum RangeOrOperator { + /// A `Range`. + Range(Range), + /// An `Operator`. + Operator(Operator), +} + +impl RangeOrOperator { + /// Evaluate a given range given an optional query value and a value from + /// the browser. + pub fn evaluate<T>( + range_or_op: Option<Self>, + query_value: Option<T>, + value: T, + ) -> bool + where + T: PartialOrd + Zero + { + match query_value { + Some(v) => Self::evaluate_with_query_value(range_or_op, v, value), + None => !value.is_zero(), + } + } + + /// Evaluate a given range given a non-optional query value and a value from + /// the browser. + pub fn evaluate_with_query_value<T>( + range_or_op: Option<Self>, + query_value: T, + value: T, + ) -> bool + where + T: PartialOrd, + { + let cmp = match value.partial_cmp(&query_value) { + Some(c) => c, + None => return false, + }; + + let range_or_op = match range_or_op { + Some(r) => r, + None => return cmp == Ordering::Equal, + }; + + match range_or_op { + RangeOrOperator::Range(range) => { + cmp == Ordering::Equal || match range { + Range::Min => cmp == Ordering::Greater, + Range::Max => cmp == Ordering::Less, + } + } + RangeOrOperator::Operator(op) => { + match op { + Operator::Equal => cmp == Ordering::Equal, + Operator::GreaterThan => cmp == Ordering::Greater, + Operator::GreaterThanEqual => { + cmp == Ordering::Equal || cmp == Ordering::Greater + } + Operator::LessThan => cmp == Ordering::Less, + Operator::LessThanEqual => { + cmp == Ordering::Equal || cmp == Ordering::Less + } + } + } + } + } +} + +/// A feature expression contains a reference to the media feature, the value +/// the media query contained, and the range to evaluate. +#[derive(Clone, Debug, MallocSizeOf)] +pub struct MediaFeatureExpression { + feature: &'static MediaFeatureDescription, + value: Option<MediaExpressionValue>, + range_or_operator: Option<RangeOrOperator>, +} + +impl PartialEq for MediaFeatureExpression { + fn eq(&self, other: &Self) -> bool { + self.feature as *const _ == other.feature as *const _ && + self.value == other.value && + self.range_or_operator == other.range_or_operator + } +} + +impl ToCss for MediaFeatureExpression { + fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result + where + W: fmt::Write, + { + dest.write_str("(")?; + + if self.feature.requirements.contains(ParsingRequirements::WEBKIT_PREFIX) { + dest.write_str("-webkit-")?; + } + + if let Some(RangeOrOperator::Range(range)) = self.range_or_operator { + match range { + Range::Min => dest.write_str("min-")?, + Range::Max => dest.write_str("max-")?, + } + } + + // NB: CssStringWriter not needed, feature names are under control. + write!(dest, "{}", self.feature.name)?; + + if let Some(RangeOrOperator::Operator(op)) = self.range_or_operator { + dest.write_char(' ')?; + op.to_css(dest)?; + dest.write_char(' ')?; + } else if self.value.is_some() { + dest.write_str(": ")?; + } + + if let Some(ref val) = self.value { + val.to_css(dest, self)?; + } + + dest.write_str(")") + } +} + +/// Consumes an operation or a colon, or returns an error. +fn consume_operation_or_colon( + input: &mut Parser, +) -> Result<Option<Operator>, ()> { + let first_delim = { + let next_token = match input.next() { + Ok(t) => t, + Err(..) => return Err(()), + }; + + match *next_token { + Token::Colon => return Ok(None), + Token::Delim(oper) => oper, + _ => return Err(()), + } + }; + Ok(Some(match first_delim { + '=' => Operator::Equal, + '>' => { + if input.try(|i| i.expect_delim('=')).is_ok() { + Operator::GreaterThanEqual + } else { + Operator::GreaterThan + } + } + '<' => { + if input.try(|i| i.expect_delim('=')).is_ok() { + Operator::LessThanEqual + } else { + Operator::LessThan + } + } + _ => return Err(()), + })) +} + +impl MediaFeatureExpression { + fn new( + feature: &'static MediaFeatureDescription, + value: Option<MediaExpressionValue>, + range_or_operator: Option<RangeOrOperator>, + ) -> Self { + Self { feature, value, range_or_operator } + } + + /// Parse a media expression of the form: + /// + /// ``` + /// (media-feature: media-value) + /// ``` + pub fn parse<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result<Self, ParseError<'i>> { + input.expect_parenthesis_block()?; + input.parse_nested_block(|input| { + Self::parse_in_parenthesis_block(context, input) + }) + } + + /// Parse a media feature expression where we've already consumed the + /// parenthesis. + pub fn parse_in_parenthesis_block<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result<Self, ParseError<'i>> { + #[cfg(feature = "gecko")] + use gecko::media_features::MEDIA_FEATURES; + #[cfg(feature = "servo")] + use servo::media_queries::MEDIA_FEATURES; + + // FIXME: remove extra indented block when lifetimes are non-lexical + let feature; + let range; + { + let location = input.current_source_location(); + let ident = input.expect_ident()?; + + let mut requirements = ParsingRequirements::empty(); + + if context.chrome_rules_enabled() || + context.stylesheet_origin == Origin::UserAgent + { + requirements.insert(ParsingRequirements::CHROME_AND_UA_ONLY); + } + + let result = { + let mut feature_name = &**ident; + + #[cfg(feature = "gecko")] + { + if unsafe { structs::StaticPrefs_sVarCache_layout_css_prefixes_webkit } && + starts_with_ignore_ascii_case(feature_name, "-webkit-") + { + feature_name = &feature_name[8..]; + requirements.insert(ParsingRequirements::WEBKIT_PREFIX); + if unsafe { + structs::StaticPrefs_sVarCache_layout_css_prefixes_device_pixel_ratio_webkit + } { + requirements.insert(ParsingRequirements::WEBKIT_DEVICE_PIXEL_RATIO_PREF_ENABLED); + } + } + } + + let range = if starts_with_ignore_ascii_case(feature_name, "min-") { + feature_name = &feature_name[4..]; + Some(Range::Min) + } else if starts_with_ignore_ascii_case(feature_name, "max-") { + feature_name = &feature_name[4..]; + Some(Range::Max) + } else { + None + }; + + let atom = Atom::from(string_as_ascii_lowercase(feature_name)); + match MEDIA_FEATURES.iter().find(|f| f.name == atom) { + Some(f) => Ok((f, range)), + None => Err(()), + } + }; + + match result { + Ok((f, r)) => { + feature = f; + range = r; + }, + Err(()) => { + return Err(location.new_custom_error( + StyleParseErrorKind::MediaQueryExpectedFeatureName(ident.clone()), + )) + }, + } + + if !(feature.requirements & !requirements).is_empty() { + return Err(location.new_custom_error( + StyleParseErrorKind::MediaQueryExpectedFeatureName(ident.clone()), + )); + } + + if range.is_some() && !feature.allows_ranges() { + return Err(location.new_custom_error( + StyleParseErrorKind::MediaQueryExpectedFeatureName(ident.clone()), + )); + } + } + + let operator = input.try(consume_operation_or_colon); + let operator = match operator { + Err(..) => { + // If there's no colon, this is a media query of the + // form '(<feature>)', that is, there's no value + // specified. + // + // Gecko doesn't allow ranged expressions without a + // value, so just reject them here too. + if range.is_some() { + return Err(input.new_custom_error( + StyleParseErrorKind::RangedExpressionWithNoValue + )); + } + + return Ok(Self::new(feature, None, None)); + } + Ok(operator) => operator, + }; + + let range_or_operator = match range { + Some(range) => { + if operator.is_some() { + return Err(input.new_custom_error( + StyleParseErrorKind::MediaQueryUnexpectedOperator + )); + } + Some(RangeOrOperator::Range(range)) + } + None => { + match operator { + Some(operator) => { + if !feature.allows_ranges() { + return Err(input.new_custom_error( + StyleParseErrorKind::MediaQueryUnexpectedOperator + )); + } + Some(RangeOrOperator::Operator(operator)) + } + None => None, + } + } + }; + + let value = + MediaExpressionValue::parse(feature, context, input).map_err(|err| { + err.location + .new_custom_error(StyleParseErrorKind::MediaQueryExpectedFeatureValue) + })?; + + Ok(Self::new(feature, Some(value), range_or_operator)) + } + + /// Returns whether this media query evaluates to true for the given device. + pub fn matches(&self, device: &Device, quirks_mode: QuirksMode) -> bool { + let value = self.value.as_ref(); + + macro_rules! expect { + ($variant:ident) => { + value.map(|value| { + match *value { + MediaExpressionValue::$variant(ref v) => v, + _ => unreachable!("Unexpected MediaExpressionValue"), + } + }) + } + } + + match self.feature.evaluator { + Evaluator::Length(eval) => { + let computed = expect!(Length).map(|specified| { + computed::Context::for_media_query_evaluation(device, quirks_mode, |context| { + specified.to_computed_value(context) + }) + }); + eval(device, computed, self.range_or_operator) + } + Evaluator::Integer(eval) => { + eval(device, expect!(Integer).cloned(), self.range_or_operator) + } + Evaluator::Float(eval) => { + eval(device, expect!(Float).cloned(), self.range_or_operator) + } + Evaluator::IntRatio(eval) => { + eval(device, expect!(IntRatio).cloned(), self.range_or_operator) + }, + Evaluator::Resolution(eval) => { + let computed = expect!(Resolution).map(|specified| { + computed::Context::for_media_query_evaluation(device, quirks_mode, |context| { + specified.to_computed_value(context) + }) + }); + eval(device, computed, self.range_or_operator) + } + Evaluator::Enumerated { evaluator, .. } => { + evaluator( + device, + expect!(Enumerated).cloned(), + self.range_or_operator, + ) + } + Evaluator::Ident(eval) => { + eval(device, expect!(Ident).cloned(), self.range_or_operator) + } + Evaluator::BoolInteger(eval) => { + eval(device, expect!(BoolInteger).cloned(), self.range_or_operator) + } + } + } +} + +/// A value found or expected in a media expression. +/// +/// FIXME(emilio): How should calc() serialize in the Number / Integer / +/// BoolInteger / IntRatio case, as computed or as specified value? +/// +/// If the first, this would need to store the relevant values. +/// +/// See: https://github.com/w3c/csswg-drafts/issues/1968 +#[derive(Clone, Debug, MallocSizeOf, PartialEq)] +pub enum MediaExpressionValue { + /// A length. + Length(Length), + /// A (non-negative) integer. + Integer(u32), + /// A floating point value. + Float(CSSFloat), + /// A boolean value, specified as an integer (i.e., either 0 or 1). + BoolInteger(bool), + /// Two integers separated by '/', with optional whitespace on either side + /// of the '/'. + IntRatio(AspectRatio), + /// A resolution. + Resolution(Resolution), + /// An enumerated value, defined by the variant keyword table in the + /// feature's `mData` member. + Enumerated(KeywordDiscriminant), + /// An identifier. + Ident(Atom), +} + +impl MediaExpressionValue { + fn to_css<W>( + &self, + dest: &mut CssWriter<W>, + for_expr: &MediaFeatureExpression, + ) -> fmt::Result + where + W: fmt::Write, + { + match *self { + MediaExpressionValue::Length(ref l) => l.to_css(dest), + MediaExpressionValue::Integer(v) => v.to_css(dest), + MediaExpressionValue::Float(v) => v.to_css(dest), + MediaExpressionValue::BoolInteger(v) => dest.write_str(if v { "1" } else { "0" }), + MediaExpressionValue::IntRatio(ratio) => { + ratio.to_css(dest) + }, + MediaExpressionValue::Resolution(ref r) => r.to_css(dest), + MediaExpressionValue::Ident(ref ident) => serialize_atom_identifier(ident, dest), + MediaExpressionValue::Enumerated(value) => { + match for_expr.feature.evaluator { + Evaluator::Enumerated { serializer, .. } => { + dest.write_str(&*serializer(value)) + } + _ => unreachable!(), + } + }, + } + } + + fn parse<'i, 't>( + for_feature: &MediaFeatureDescription, + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result<MediaExpressionValue, ParseError<'i>> { + Ok(match for_feature.evaluator { + Evaluator::Length(..) => { + let length = Length::parse_non_negative(context, input)?; + MediaExpressionValue::Length(length) + } + Evaluator::Integer(..) => { + let integer = Integer::parse_non_negative(context, input)?; + MediaExpressionValue::Integer(integer.value() as u32) + } + Evaluator::BoolInteger(..) => { + let integer = Integer::parse_non_negative(context, input)?; + let value = integer.value(); + if value > 1 { + return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); + } + MediaExpressionValue::BoolInteger(value == 1) + } + Evaluator::Float(..) => { + let number = Number::parse(context, input)?; + MediaExpressionValue::Float(number.get()) + } + Evaluator::IntRatio(..) => { + let a = Integer::parse_positive(context, input)?; + input.expect_delim('/')?; + let b = Integer::parse_positive(context, input)?; + MediaExpressionValue::IntRatio(AspectRatio( + a.value() as u32, + b.value() as u32 + )) + } + Evaluator::Resolution(..) => { + MediaExpressionValue::Resolution(Resolution::parse(context, input)?) + } + Evaluator::Enumerated { parser, .. } => { + MediaExpressionValue::Enumerated(parser(context, input)?) + } + Evaluator::Ident(..) => { + MediaExpressionValue::Ident(Atom::from(input.expect_ident()?.as_ref())) + } + }) + } +} diff --git a/components/style/media_queries/mod.rs b/components/style/media_queries/mod.rs index d27e33cc64c..b59ec32443d 100644 --- a/components/style/media_queries/mod.rs +++ b/components/style/media_queries/mod.rs @@ -9,12 +9,16 @@ mod media_condition; mod media_list; mod media_query; +#[macro_use] +pub mod media_feature; +pub mod media_feature_expression; pub use self::media_condition::MediaCondition; pub use self::media_list::MediaList; pub use self::media_query::{MediaQuery, MediaQueryType, MediaType, Qualifier}; +pub use self::media_feature_expression::MediaFeatureExpression; #[cfg(feature = "servo")] -pub use servo::media_queries::{Device, MediaFeatureExpression}; +pub use servo::media_queries::Device; #[cfg(feature = "gecko")] -pub use gecko::media_queries::{Device, MediaFeatureExpression}; +pub use gecko::media_queries::Device; diff --git a/components/style/properties/data.py b/components/style/properties/data.py index ca05309b4b6..4d3e4ad2177 100644 --- a/components/style/properties/data.py +++ b/components/style/properties/data.py @@ -290,14 +290,25 @@ class Longhand(object): "AlignContent", "AlignItems", "AlignSelf", + "Appearance", "BackgroundRepeat", "BorderImageRepeat", "BorderStyle", + "Clear", "ColumnCount", "Contain", + "Display", + "Float", + "FontSizeAdjust", + "FontStretch", + "FontStyle", "FontStyleAdjust", "FontSynthesis", + "FontVariantEastAsian", + "FontVariantLigatures", + "FontVariantNumeric", "FontWeight", + "GreaterThanOrEqualToOneNumber", "GridAutoFlow", "InitialLetter", "Integer", @@ -311,17 +322,24 @@ class Longhand(object): "NonNegativeNumber", "Opacity", "OutlineStyle", + "OverflowClipBox", "OverscrollBehavior", "Percentage", + "Resize", + "SVGOpacity", "SVGPaintOrder", "ScrollSnapType", + "TextAlign", "TextDecorationLine", + "TextEmphasisPosition", "TouchAction", "TransformStyle", "XSpan", "XTextZoom", "ZIndex", } + if self.name == "overflow-y": + return True return bool(self.keyword) def animated_type(self): diff --git a/components/style/properties/gecko.mako.rs b/components/style/properties/gecko.mako.rs index 312f43878ae..33a66498b2d 100644 --- a/components/style/properties/gecko.mako.rs +++ b/components/style/properties/gecko.mako.rs @@ -2124,7 +2124,7 @@ fn static_assert() { }; for (servo, gecko) in v.0.areas.into_iter().zip(refptr.mNamedAreas.iter_mut()) { - gecko.mName.assign_utf8(&*servo.name); + gecko.mName.assign_str(&*servo.name); gecko.mColumnStart = servo.columns.start; gecko.mColumnEnd = servo.columns.end; gecko.mRowStart = servo.rows.start; @@ -2132,7 +2132,7 @@ fn static_assert() { } for (servo, gecko) in v.0.strings.into_iter().zip(refptr.mTemplates.iter_mut()) { - gecko.assign_utf8(&*servo); + gecko.assign_str(&*servo); } self.gecko.mGridTemplateAreas.set_move(refptr.get()) @@ -3106,6 +3106,9 @@ fn static_assert() { ) %> ${impl_keyword('clear', 'mBreakType', clear_keyword)} + <% resize_keyword = Keyword("resize", "None Both Horizontal Vertical") %> + ${impl_keyword('resize', 'mResize', resize_keyword)} + <% overflow_x = data.longhands_by_name["overflow-x"] %> pub fn set_overflow_y(&mut self, v: longhands::overflow_y::computed_value::T) { use properties::longhands::overflow_x::computed_value::T as BaseType; @@ -4186,8 +4189,8 @@ fn static_assert() { }; for (servo, gecko) in other.0.into_iter().zip(refptr.mQuotePairs.iter_mut()) { - gecko.first.assign_utf8(&servo.0); - gecko.second.assign_utf8(&servo.1); + gecko.first.assign_str(&servo.0); + gecko.second.assign_str(&servo.1); } self.gecko.mQuotes.set_move(refptr.get()) @@ -4725,7 +4728,7 @@ fn static_assert() { (structs::NS_STYLE_TEXT_EMPHASIS_STYLE_STRING, &**s) }, }; - self.gecko.mTextEmphasisStyleString.assign_utf8(s); + self.gecko.mTextEmphasisStyleString.assign_str(s); self.gecko.mTextEmphasisStyle = te as u8; } @@ -4826,7 +4829,7 @@ fn static_assert() { TextOverflowSide::Clip => structs::NS_STYLE_TEXT_OVERFLOW_CLIP, TextOverflowSide::Ellipsis => structs::NS_STYLE_TEXT_OVERFLOW_ELLIPSIS, TextOverflowSide::String(ref s) => { - side.mString.assign_utf8(s); + side.mString.assign_str(s); structs::NS_STYLE_TEXT_OVERFLOW_STRING } }; @@ -4960,7 +4963,7 @@ fn static_assert() { ShapeSource::ImageOrUrl(image) => { unsafe { bindings::Gecko_NewShapeImage(${ident}); - let style_image = &mut *${ident}.mShapeImage.mPtr; + let style_image = &mut *${ident}.__bindgen_anon_1.mShapeImage.as_mut().mPtr; style_image.set(image); } } @@ -4980,7 +4983,7 @@ fn static_assert() { // Create StyleBasicShape in StyleShapeSource. mReferenceBox and mType // will be set manually later. Gecko_NewBasicShape(${ident}, basic_shape_type); - &mut *${ident}.mBasicShape.mPtr + &mut *${ident}.__bindgen_anon_1.mBasicShape.as_mut().mPtr } } match servo_shape { @@ -5458,7 +5461,7 @@ clip-path }; counter_func.mIdent.assign(name.0.as_slice()); if content_type == StyleContentType::Counters { - counter_func.mSeparator.assign_utf8(sep); + counter_func.mSeparator.assign_str(sep); } style.to_gecko_value(&mut counter_func.mCounterStyle, device); } diff --git a/components/style/properties/longhands/box.mako.rs b/components/style/properties/longhands/box.mako.rs index 765731c13f2..6b6bcf9cbe3 100644 --- a/components/style/properties/longhands/box.mako.rs +++ b/components/style/properties/longhands/box.mako.rs @@ -422,17 +422,21 @@ ${helpers.single_keyword("page-break-inside", // CSS Basic User Interface Module Level 3 // http://dev.w3.org/csswg/css-ui -// FIXME support logical values `block` and `inline` (https://drafts.csswg.org/css-logical-props/#resize) // // This is APPLIES_TO_PLACEHOLDER so we can override, in the UA sheet, the // 'resize' property we'd inherit from textarea otherwise. Basically, just // makes the UA rules easier to write. -${helpers.single_keyword("resize", - "none both horizontal vertical", - products="gecko", - spec="https://drafts.csswg.org/css-ui/#propdef-resize", - flags="APPLIES_TO_PLACEHOLDER", - animation_value_type="discrete")} +${helpers.predefined_type( + "resize", + "Resize", + "computed::Resize::None", + products="gecko", + animation_value_type="discrete", + needs_context=False, + gecko_ffi_name="mResize", + flags="APPLIES_TO_PLACEHOLDER", + spec="https://drafts.csswg.org/css-ui/#propdef-resize", +)} ${helpers.predefined_type( "perspective", @@ -448,7 +452,7 @@ ${helpers.predefined_type( ${helpers.predefined_type( "perspective-origin", - "position::Position", + "Position", "computed::position::Position::center()", boxed=True, extra_prefixes=transform_extra_prefixes, diff --git a/components/style/properties/longhands/inherited_ui.mako.rs b/components/style/properties/longhands/inherited_ui.mako.rs index 9718e33a84d..e597f97ce8c 100644 --- a/components/style/properties/longhands/inherited_ui.mako.rs +++ b/components/style/properties/longhands/inherited_ui.mako.rs @@ -4,7 +4,7 @@ <%namespace name="helpers" file="/helpers.mako.rs" /> -<% data.new_style_struct("InheritedUI", inherited=True, gecko_name="UserInterface") %> +<% data.new_style_struct("InheritedUI", inherited=True, gecko_name="UI") %> ${helpers.predefined_type("cursor", "Cursor", diff --git a/components/style/properties/longhands/ui.mako.rs b/components/style/properties/longhands/ui.mako.rs index 13a940b2b96..419024862d4 100644 --- a/components/style/properties/longhands/ui.mako.rs +++ b/components/style/properties/longhands/ui.mako.rs @@ -16,6 +16,17 @@ ${helpers.single_keyword("ime-mode", "auto normal active disabled inactive", animation_value_type="discrete", spec="https://drafts.csswg.org/css-ui/#input-method-editor")} +${helpers.single_keyword( + "scrollbar-width", + "auto thin none", + products="gecko", + gecko_enum_prefix="StyleScrollbarWidth", + animation_value_type="discrete", + gecko_pref="layout.css.scrollbar-width.enabled", + enabled_in="chrome", + spec="https://drafts.csswg.org/css-scrollbars-1/#scrollbar-width" +)} + ${helpers.single_keyword("-moz-user-select", "auto text none all element elements" + " toggle tri-state -moz-all -moz-text", products="gecko", diff --git a/components/style/properties/properties.mako.rs b/components/style/properties/properties.mako.rs index fed6d52b7f7..64e99ba205d 100644 --- a/components/style/properties/properties.mako.rs +++ b/components/style/properties/properties.mako.rs @@ -305,6 +305,21 @@ impl Clone for PropertyDeclaration { } } + // This function ensures that all properties not handled above + // do not have a specified value implements Copy. If you hit + // compile error here, you may want to add the type name into + // Longhand.specified_is_copy in data.py. + fn _static_assert_others_are_not_copy() { + struct Helper<T>(T); + trait AssertCopy { fn assert() {} } + trait AssertNotCopy { fn assert() {} } + impl<T: Copy> AssertCopy for Helper<T> {} + % for ty in set(x["type"] for x in others): + impl AssertNotCopy for Helper<${ty}> {} + Helper::<${ty}>::assert(); + % endfor + } + match *self { ${" |\n".join("{}(..)".format(v["name"]) for v in copy)} => { unsafe { debug_unreachable!() } @@ -588,16 +603,17 @@ impl NonCustomPropertyId { /// See PropertyId::collect_property_completion_keywords. fn collect_property_completion_keywords(&self, f: KeywordsCollectFn) { - const COLLECT_FUNCTIONS: [&Fn(KeywordsCollectFn); + fn do_nothing(_: KeywordsCollectFn) {} + const COLLECT_FUNCTIONS: [fn(KeywordsCollectFn); ${len(data.longhands) + len(data.shorthands)}] = [ % for prop in data.longhands: - &<${prop.specified_type()} as SpecifiedValueInfo>::collect_completion_keywords, + <${prop.specified_type()} as SpecifiedValueInfo>::collect_completion_keywords, % endfor % for prop in data.shorthands: % if prop.name == "all": - &|_f| {}, // 'all' accepts no value other than CSS-wide keywords + do_nothing, // 'all' accepts no value other than CSS-wide keywords % else: - &<shorthands::${prop.ident}::Longhands as SpecifiedValueInfo>:: + <shorthands::${prop.ident}::Longhands as SpecifiedValueInfo>:: collect_completion_keywords, % endif % endfor diff --git a/components/style/properties/shorthands/box.mako.rs b/components/style/properties/shorthands/box.mako.rs index 1425f012854..960e608cea7 100644 --- a/components/style/properties/shorthands/box.mako.rs +++ b/components/style/properties/shorthands/box.mako.rs @@ -7,7 +7,7 @@ <%helpers:shorthand name="overflow" flags="SHORTHAND_IN_GETCS" - sub_properties="overflow-x overflow-y" + sub_properties="overflow-y overflow-x" spec="https://drafts.csswg.org/css-overflow/#propdef-overflow" > use properties::longhands::overflow_x::parse as parse_overflow; @@ -20,35 +20,41 @@ input: &mut Parser<'i, 't>, ) -> Result<Longhands, ParseError<'i>> { % if product == "gecko": - let moz_kw_found = input.try(|input| { - try_match_ident_ignore_ascii_case! { input, - "-moz-scrollbars-horizontal" => { - Ok(expanded! { - overflow_x: SpecifiedValue::Scroll, - overflow_y: SpecifiedValue::Hidden, - }) - } - "-moz-scrollbars-vertical" => { - Ok(expanded! { - overflow_x: SpecifiedValue::Hidden, - overflow_y: SpecifiedValue::Scroll, - }) - } - "-moz-scrollbars-none" => { - Ok(expanded! { - overflow_x: SpecifiedValue::Hidden, - overflow_y: SpecifiedValue::Hidden, - }) + use gecko_bindings::structs; + let moz_kw_enabled = unsafe { + structs::StaticPrefs_sVarCache_layout_css_overflow_moz_scrollbars_enabled + }; + if moz_kw_enabled { + let moz_kw_found = input.try(|input| { + try_match_ident_ignore_ascii_case! { input, + "-moz-scrollbars-horizontal" => { + Ok(expanded! { + overflow_x: SpecifiedValue::Scroll, + overflow_y: SpecifiedValue::Hidden, + }) + } + "-moz-scrollbars-vertical" => { + Ok(expanded! { + overflow_x: SpecifiedValue::Hidden, + overflow_y: SpecifiedValue::Scroll, + }) + } + "-moz-scrollbars-none" => { + Ok(expanded! { + overflow_x: SpecifiedValue::Hidden, + overflow_y: SpecifiedValue::Hidden, + }) + } } + }); + if moz_kw_found.is_ok() { + return moz_kw_found } - }); - if moz_kw_found.is_ok() { - return moz_kw_found } % endif - let overflow_x = parse_overflow(context, input)?; - let overflow_y = - input.try(|i| parse_overflow(context, i)).unwrap_or(overflow_x); + let overflow_y = parse_overflow(context, input)?; + let overflow_x = + input.try(|i| parse_overflow(context, i)).unwrap_or(overflow_y); Ok(expanded! { overflow_x: overflow_x, overflow_y: overflow_y, @@ -57,10 +63,10 @@ impl<'a> ToCss for LonghandsToSerialize<'a> { fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: fmt::Write { - self.overflow_x.to_css(dest)?; + self.overflow_y.to_css(dest)?; if self.overflow_x != self.overflow_y { dest.write_char(' ')?; - self.overflow_y.to_css(dest)?; + self.overflow_x.to_css(dest)?; } Ok(()) } diff --git a/components/style/servo/media_queries.rs b/components/style/servo/media_queries.rs index ba31cfcaf4b..98b1ec7ae6f 100644 --- a/components/style/servo/media_queries.rs +++ b/components/style/servo/media_queries.rs @@ -5,19 +5,18 @@ //! Servo's media-query device and expression representation. use app_units::Au; -use context::QuirksMode; -use cssparser::{Parser, RGBA}; +use cssparser::RGBA; use euclid::{Size2D, TypedScale, TypedSize2D}; use media_queries::MediaType; -use parser::ParserContext; +use media_queries::media_feature::{AllowsRanges, ParsingRequirements}; +use media_queries::media_feature::{MediaFeatureDescription, Evaluator}; +use media_queries::media_feature_expression::RangeOrOperator; use properties::ComputedValues; -use selectors::parser::SelectorParseErrorKind; -use std::fmt::{self, Write}; use std::sync::atomic::{AtomicBool, AtomicIsize, Ordering}; -use style_traits::{CSSPixel, CssWriter, DevicePixel, ParseError, ToCss}; +use style_traits::{CSSPixel, DevicePixel}; use style_traits::viewport::ViewportConstraints; -use values::{specified, KeyframesName}; -use values::computed::{self, ToComputedValue}; +use values::KeyframesName; +use values::computed::CSSPixelLength; use values::computed::font::FontSize; /// A device is a structure that represents the current media a given document @@ -155,125 +154,47 @@ impl Device { } } -/// A expression kind servo understands and parses. -/// -/// Only `pub` for unit testing, please don't use it directly! -#[derive(Clone, Debug, PartialEq)] -#[cfg_attr(feature = "servo", derive(MallocSizeOf))] -pub enum ExpressionKind { - /// <http://dev.w3.org/csswg/mediaqueries-3/#width> - Width(Range<specified::Length>), +/// https://drafts.csswg.org/mediaqueries-4/#width +fn eval_width( + device: &Device, + value: Option<CSSPixelLength>, + range_or_operator: Option<RangeOrOperator>, +) -> bool { + RangeOrOperator::evaluate( + range_or_operator, + value.map(Au::from), + device.au_viewport_size().width, + ) } -/// A single expression a per: -/// -/// <http://dev.w3.org/csswg/mediaqueries-3/#media1> -#[derive(Clone, Debug, PartialEq)] -#[cfg_attr(feature = "servo", derive(MallocSizeOf))] -pub struct MediaFeatureExpression(pub ExpressionKind); - -impl MediaFeatureExpression { - /// The kind of expression we're, just for unit testing. - /// - /// Eventually this will become servo-only. - pub fn kind_for_testing(&self) -> &ExpressionKind { - &self.0 - } - - /// Parse a media expression of the form: - /// - /// ``` - /// media-feature: media-value - /// ``` - /// - /// Only supports width ranges for now. - pub fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - input.expect_parenthesis_block()?; - input.parse_nested_block(|input| { - Self::parse_in_parenthesis_block(context, input) - }) - } - - /// Parse a media range expression where we've already consumed the - /// parenthesis. - pub fn parse_in_parenthesis_block<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - let name = input.expect_ident_cloned()?; - input.expect_colon()?; - // TODO: Handle other media features - Ok(MediaFeatureExpression(match_ignore_ascii_case! { &name, - "min-width" => { - ExpressionKind::Width(Range::Min(specified::Length::parse_non_negative(context, input)?)) - }, - "max-width" => { - ExpressionKind::Width(Range::Max(specified::Length::parse_non_negative(context, input)?)) - }, - "width" => { - ExpressionKind::Width(Range::Eq(specified::Length::parse_non_negative(context, input)?)) - }, - _ => return Err(input.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name.clone()))) - })) - } - - /// Evaluate this expression and return whether it matches the current - /// device. - pub fn matches(&self, device: &Device, quirks_mode: QuirksMode) -> bool { - let viewport_size = device.au_viewport_size(); - let value = viewport_size.width; - match self.0 { - ExpressionKind::Width(ref range) => { - match range.to_computed_range(device, quirks_mode) { - Range::Min(ref width) => value >= *width, - Range::Max(ref width) => value <= *width, - Range::Eq(ref width) => value == *width, - } - }, - } - } +#[derive(Clone, Copy, Debug, FromPrimitive, Parse, ToCss)] +#[repr(u8)] +enum Scan { + Progressive, + Interlace, } -impl ToCss for MediaFeatureExpression { - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result - where - W: Write, - { - let (s, l) = match self.0 { - ExpressionKind::Width(Range::Min(ref l)) => ("(min-width: ", l), - ExpressionKind::Width(Range::Max(ref l)) => ("(max-width: ", l), - ExpressionKind::Width(Range::Eq(ref l)) => ("(width: ", l), - }; - dest.write_str(s)?; - l.to_css(dest)?; - dest.write_char(')') - } +/// https://drafts.csswg.org/mediaqueries-4/#scan +fn eval_scan(_: &Device, _: Option<Scan>) -> bool { + // Since we doesn't support the 'tv' media type, the 'scan' feature never + // matches. + false } -/// An enumeration that represents a ranged value. -/// -/// Only public for testing, implementation details of `MediaFeatureExpression` -/// may change for Stylo. -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -#[cfg_attr(feature = "servo", derive(MallocSizeOf))] -pub enum Range<T> { - /// At least the inner value. - Min(T), - /// At most the inner value. - Max(T), - /// Exactly the inner value. - Eq(T), -} - -impl Range<specified::Length> { - fn to_computed_range(&self, device: &Device, quirks_mode: QuirksMode) -> Range<Au> { - computed::Context::for_media_query_evaluation(device, quirks_mode, |context| match *self { - Range::Min(ref width) => Range::Min(Au::from(width.to_computed_value(&context))), - Range::Max(ref width) => Range::Max(Au::from(width.to_computed_value(&context))), - Range::Eq(ref width) => Range::Eq(Au::from(width.to_computed_value(&context))), - }) - } +lazy_static! { + /// A list with all the media features that Servo supports. + pub static ref MEDIA_FEATURES: [MediaFeatureDescription; 2] = [ + feature!( + atom!("width"), + AllowsRanges::Yes, + Evaluator::Length(eval_width), + ParsingRequirements::empty(), + ), + feature!( + atom!("scan"), + AllowsRanges::No, + keyword_evaluator!(eval_scan, Scan), + ParsingRequirements::empty(), + ), + ]; } diff --git a/components/style/stylist.rs b/components/style/stylist.rs index 52feb19c411..dbddca725e1 100644 --- a/components/style/stylist.rs +++ b/components/style/stylist.rs @@ -1131,11 +1131,6 @@ impl Stylist { let rule_hash_target = element.rule_hash_target(); - debug!( - "Determining if style is shareable: pseudo: {}", - pseudo_element.is_some() - ); - let matches_user_rules = rule_hash_target.matches_user_and_author_rules(); let matches_author_rules = matches_user_rules && self.author_styles_enabled == AuthorStylesEnabled::Yes; diff --git a/components/style/values/computed/box.rs b/components/style/values/computed/box.rs index b0db8337608..15e447ea415 100644 --- a/components/style/values/computed/box.rs +++ b/components/style/values/computed/box.rs @@ -9,6 +9,7 @@ use values::computed::length::{LengthOrPercentage, NonNegativeLength}; use values::generics::box_::AnimationIterationCount as GenericAnimationIterationCount; use values::generics::box_::Perspective as GenericPerspective; use values::generics::box_::VerticalAlign as GenericVerticalAlign; +use values::specified::box_ as specified; pub use values::specified::box_::{AnimationName, Appearance, Contain, Display, OverflowClipBox}; pub use values::specified::box_::{Clear as SpecifiedClear, Float as SpecifiedFloat}; @@ -139,3 +140,57 @@ impl ToComputedValue for SpecifiedClear { } } } + +/// A computed value for the `resize` property. +#[allow(missing_docs)] +#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] +#[derive(Clone, Copy, Debug, Eq, Hash, MallocSizeOf, Parse, PartialEq, ToCss)] +pub enum Resize { + None, + Both, + Horizontal, + Vertical, +} + +impl ToComputedValue for specified::Resize { + type ComputedValue = Resize; + + #[inline] + fn to_computed_value(&self, context: &Context) -> Resize { + let is_vertical = context.style().writing_mode.is_vertical(); + match self { + specified::Resize::Inline => { + context.rule_cache_conditions.borrow_mut() + .set_writing_mode_dependency(context.builder.writing_mode); + if is_vertical { + Resize::Vertical + } else { + Resize::Horizontal + } + } + specified::Resize::Block => { + context.rule_cache_conditions.borrow_mut() + .set_writing_mode_dependency(context.builder.writing_mode); + if is_vertical { + Resize::Horizontal + } else { + Resize::Vertical + } + } + specified::Resize::None => Resize::None, + specified::Resize::Both => Resize::Both, + specified::Resize::Horizontal => Resize::Horizontal, + specified::Resize::Vertical => Resize::Vertical, + } + } + + #[inline] + fn from_computed_value(computed: &Resize) -> specified::Resize { + match computed { + Resize::None => specified::Resize::None, + Resize::Both => specified::Resize::Both, + Resize::Horizontal => specified::Resize::Horizontal, + Resize::Vertical => specified::Resize::Vertical, + } + } +} diff --git a/components/style/values/computed/mod.rs b/components/style/values/computed/mod.rs index fa2a9cdcf0d..9a6b5fd76b5 100644 --- a/components/style/values/computed/mod.rs +++ b/components/style/values/computed/mod.rs @@ -44,7 +44,7 @@ pub use self::font::{FontFeatureSettings, FontVariantLigatures, FontVariantNumer pub use self::font::{MozScriptLevel, MozScriptMinSize, MozScriptSizeMultiplier, XLang, XTextZoom}; pub use self::box_::{AnimationIterationCount, AnimationName, Contain, Display, TransitionProperty}; pub use self::box_::{Appearance, Clear, Float}; -pub use self::box_::{OverflowClipBox, OverscrollBehavior, Perspective}; +pub use self::box_::{OverflowClipBox, OverscrollBehavior, Perspective, Resize}; pub use self::box_::{ScrollSnapType, TouchAction, VerticalAlign, WillChange}; pub use self::color::{Color, ColorPropertyValue, RGBAColor}; pub use self::column::ColumnCount; diff --git a/components/style/values/computed/resolution.rs b/components/style/values/computed/resolution.rs index 817ba082236..d90bdf4867d 100644 --- a/components/style/values/computed/resolution.rs +++ b/components/style/values/computed/resolution.rs @@ -21,6 +21,12 @@ impl Resolution { pub fn dppx(&self) -> CSSFloat { self.0 } + + /// Return a computed `resolution` value from a dppx float value. + #[inline] + pub fn from_dppx(dppx: CSSFloat) -> Self { + Resolution(dppx) + } } impl ToComputedValue for specified::Resolution { diff --git a/components/style/values/specified/box.rs b/components/style/values/specified/box.rs index 11084ce7d44..6b474689df3 100644 --- a/components/style/values/specified/box.rs +++ b/components/style/values/specified/box.rs @@ -884,6 +884,21 @@ pub enum Clear { InlineEnd } +/// https://drafts.csswg.org/css-ui/#propdef-resize +#[allow(missing_docs)] +#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] +#[derive(Clone, Copy, Debug, Eq, Hash, MallocSizeOf, Parse, PartialEq, + SpecifiedValueInfo, ToCss)] +pub enum Resize { + None, + Both, + Horizontal, + Vertical, + // https://drafts.csswg.org/css-logical-1/#resize + Inline, + Block, +} + /// The value for the `appearance` property. /// /// https://developer.mozilla.org/en-US/docs/Web/CSS/-moz-appearance diff --git a/components/style/values/specified/font.rs b/components/style/values/specified/font.rs index f26f5ab74a6..8d8355821e8 100644 --- a/components/style/values/specified/font.rs +++ b/components/style/values/specified/font.rs @@ -1359,7 +1359,7 @@ impl VariantEastAsian { impl_gecko_keyword_conversions!(VariantEastAsian, u16); #[cfg_attr(feature = "gecko", derive(MallocSizeOf))] -#[derive(Clone, Debug, PartialEq, SpecifiedValueInfo, ToCss)] +#[derive(Clone, Copy, Debug, PartialEq, SpecifiedValueInfo, ToCss)] /// Allows control of glyph substitution and sizing in East Asian text. pub enum FontVariantEastAsian { /// Value variant with `variant-east-asian` @@ -1570,7 +1570,7 @@ impl VariantLigatures { impl_gecko_keyword_conversions!(VariantLigatures, u16); #[cfg_attr(feature = "gecko", derive(MallocSizeOf))] -#[derive(Clone, Debug, PartialEq, SpecifiedValueInfo, ToCss)] +#[derive(Clone, Copy, Debug, PartialEq, SpecifiedValueInfo, ToCss)] /// Ligatures and contextual forms are ways of combining glyphs /// to produce more harmonized forms pub enum FontVariantLigatures { @@ -1786,7 +1786,7 @@ impl VariantNumeric { impl_gecko_keyword_conversions!(VariantNumeric, u8); #[cfg_attr(feature = "gecko", derive(MallocSizeOf))] -#[derive(Clone, Debug, PartialEq, SpecifiedValueInfo, ToCss)] +#[derive(Clone, Copy, Debug, PartialEq, SpecifiedValueInfo, ToCss)] /// Specifies control over numerical forms. pub enum FontVariantNumeric { /// Value variant with `variant-numeric` diff --git a/components/style/values/specified/length.rs b/components/style/values/specified/length.rs index 27206a40969..b6fe9bc2346 100644 --- a/components/style/values/specified/length.rs +++ b/components/style/values/specified/length.rs @@ -421,46 +421,34 @@ impl NoCalcLength { value: CSSFloat, unit: &str, ) -> Result<Self, ()> { - match_ignore_ascii_case! { unit, - "px" => Ok(NoCalcLength::Absolute(AbsoluteLength::Px(value))), - "in" => Ok(NoCalcLength::Absolute(AbsoluteLength::In(value))), - "cm" => Ok(NoCalcLength::Absolute(AbsoluteLength::Cm(value))), - "mm" => Ok(NoCalcLength::Absolute(AbsoluteLength::Mm(value))), - "q" => Ok(NoCalcLength::Absolute(AbsoluteLength::Q(value))), - "pt" => Ok(NoCalcLength::Absolute(AbsoluteLength::Pt(value))), - "pc" => Ok(NoCalcLength::Absolute(AbsoluteLength::Pc(value))), + Ok(match_ignore_ascii_case! { unit, + "px" => NoCalcLength::Absolute(AbsoluteLength::Px(value)), + "in" => NoCalcLength::Absolute(AbsoluteLength::In(value)), + "cm" => NoCalcLength::Absolute(AbsoluteLength::Cm(value)), + "mm" => NoCalcLength::Absolute(AbsoluteLength::Mm(value)), + "q" => NoCalcLength::Absolute(AbsoluteLength::Q(value)), + "pt" => NoCalcLength::Absolute(AbsoluteLength::Pt(value)), + "pc" => NoCalcLength::Absolute(AbsoluteLength::Pc(value)), // font-relative - "em" => Ok(NoCalcLength::FontRelative(FontRelativeLength::Em(value))), - "ex" => Ok(NoCalcLength::FontRelative(FontRelativeLength::Ex(value))), - "ch" => Ok(NoCalcLength::FontRelative(FontRelativeLength::Ch(value))), - "rem" => Ok(NoCalcLength::FontRelative(FontRelativeLength::Rem(value))), + "em" => NoCalcLength::FontRelative(FontRelativeLength::Em(value)), + "ex" => NoCalcLength::FontRelative(FontRelativeLength::Ex(value)), + "ch" => NoCalcLength::FontRelative(FontRelativeLength::Ch(value)), + "rem" => NoCalcLength::FontRelative(FontRelativeLength::Rem(value)), // viewport percentages - "vw" => { - if context.in_page_rule() { - return Err(()) - } - Ok(NoCalcLength::ViewportPercentage(ViewportPercentageLength::Vw(value))) - }, - "vh" => { - if context.in_page_rule() { - return Err(()) - } - Ok(NoCalcLength::ViewportPercentage(ViewportPercentageLength::Vh(value))) - }, - "vmin" => { - if context.in_page_rule() { - return Err(()) - } - Ok(NoCalcLength::ViewportPercentage(ViewportPercentageLength::Vmin(value))) - }, - "vmax" => { - if context.in_page_rule() { - return Err(()) - } - Ok(NoCalcLength::ViewportPercentage(ViewportPercentageLength::Vmax(value))) - }, - _ => Err(()) - } + "vw" if !context.in_page_rule() => { + NoCalcLength::ViewportPercentage(ViewportPercentageLength::Vw(value)) + } + "vh" if !context.in_page_rule() => { + NoCalcLength::ViewportPercentage(ViewportPercentageLength::Vh(value)) + } + "vmin" if !context.in_page_rule() => { + NoCalcLength::ViewportPercentage(ViewportPercentageLength::Vmin(value)) + } + "vmax" if !context.in_page_rule() => { + NoCalcLength::ViewportPercentage(ViewportPercentageLength::Vmax(value)) + } + _ => return Err(()) + }) } #[inline] diff --git a/components/style/values/specified/mod.rs b/components/style/values/specified/mod.rs index 1eb59617008..2da4c7e93d3 100644 --- a/components/style/values/specified/mod.rs +++ b/components/style/values/specified/mod.rs @@ -39,7 +39,7 @@ pub use self::font::{FontFeatureSettings, FontVariantLigatures, FontVariantNumer pub use self::font::{MozScriptLevel, MozScriptMinSize, MozScriptSizeMultiplier, XLang, XTextZoom}; pub use self::box_::{AnimationIterationCount, AnimationName, Contain, Display}; pub use self::box_::{Appearance, Clear, Float}; -pub use self::box_::{OverflowClipBox, OverscrollBehavior, Perspective}; +pub use self::box_::{OverflowClipBox, OverscrollBehavior, Perspective, Resize}; pub use self::box_::{ScrollSnapType, TouchAction, TransitionProperty, VerticalAlign, WillChange}; pub use self::color::{Color, ColorPropertyValue, RGBAColor}; pub use self::counters::{Content, ContentItem, CounterIncrement, CounterReset}; diff --git a/components/style/values/specified/text.rs b/components/style/values/specified/text.rs index ef0e66672d0..ae01040e45f 100644 --- a/components/style/values/specified/text.rs +++ b/components/style/values/specified/text.rs @@ -744,7 +744,7 @@ pub enum TextEmphasisVerticalWritingModeValue { } /// Specified value of `text-emphasis-position` property. -#[derive(Clone, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, +#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToComputedValue, ToCss)] pub struct TextEmphasisPosition( pub TextEmphasisHorizontalWritingModeValue, diff --git a/tests/wpt/metadata/css/cssom/overflow-serialization.html.ini b/tests/wpt/metadata/css/cssom/overflow-serialization.html.ini deleted file mode 100644 index 3a51b8caa02..00000000000 --- a/tests/wpt/metadata/css/cssom/overflow-serialization.html.ini +++ /dev/null @@ -1,4 +0,0 @@ -[overflow-serialization.html] - [CSSOM - Overflow shorthand serialization] - expected: FAIL - diff --git a/tests/wpt/metadata/css/cssom/shorthand-values.html.ini b/tests/wpt/metadata/css/cssom/shorthand-values.html.ini index 427f39aa3e7..e0b5a48bdb5 100644 --- a/tests/wpt/metadata/css/cssom/shorthand-values.html.ini +++ b/tests/wpt/metadata/css/cssom/shorthand-values.html.ini @@ -29,6 +29,3 @@ [The serialization of list-style-type: circle; list-style-position: inside; list-style-image: initial; should be canonical.] expected: FAIL - [The serialization of overflow-x: scroll; overflow-y: hidden; should be canonical.] - expected: FAIL - |