aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock3
-rw-r--r--components/atoms/static_atoms.txt2
-rw-r--r--components/selectors/Cargo.toml1
-rw-r--r--components/selectors/bloom.rs65
-rw-r--r--components/selectors/lib.rs1
-rw-r--r--components/style/Cargo.toml6
-rw-r--r--components/style/cbindgen.toml2
-rw-r--r--components/style/gecko/conversions.rs6
-rw-r--r--components/style/gecko/data.rs48
-rw-r--r--components/style/gecko/media_features.rs610
-rw-r--r--components/style/gecko/media_queries.rs788
-rw-r--r--components/style/gecko/mod.rs1
-rw-r--r--components/style/gecko/pseudo_element_definition.mako.rs22
-rwxr-xr-xcomponents/style/gecko/regen_atoms.py113
-rw-r--r--components/style/gecko/snapshot.rs26
-rw-r--r--components/style/gecko/snapshot_helpers.rs122
-rw-r--r--components/style/gecko/wrapper.rs70
-rw-r--r--components/style/gecko_string_cache/mod.rs6
-rw-r--r--components/style/invalidation/element/state_and_attributes.rs112
-rw-r--r--components/style/lib.rs11
-rw-r--r--components/style/media_queries/media_condition.rs1
-rw-r--r--components/style/media_queries/media_feature.rs183
-rw-r--r--components/style/media_queries/media_feature_expression.rs580
-rw-r--r--components/style/media_queries/mod.rs8
-rw-r--r--components/style/properties/data.py18
-rw-r--r--components/style/properties/gecko.mako.rs21
-rw-r--r--components/style/properties/longhands/box.mako.rs20
-rw-r--r--components/style/properties/longhands/inherited_ui.mako.rs2
-rw-r--r--components/style/properties/longhands/ui.mako.rs11
-rw-r--r--components/style/properties/properties.mako.rs24
-rw-r--r--components/style/properties/shorthands/box.mako.rs62
-rw-r--r--components/style/servo/media_queries.rs167
-rw-r--r--components/style/stylist.rs5
-rw-r--r--components/style/values/computed/box.rs55
-rw-r--r--components/style/values/computed/mod.rs2
-rw-r--r--components/style/values/computed/resolution.rs6
-rw-r--r--components/style/values/specified/box.rs15
-rw-r--r--components/style/values/specified/font.rs6
-rw-r--r--components/style/values/specified/length.rs64
-rw-r--r--components/style/values/specified/mod.rs2
-rw-r--r--components/style/values/specified/text.rs2
-rw-r--r--tests/wpt/metadata/css/cssom/overflow-serialization.html.ini4
-rw-r--r--tests/wpt/metadata/css/cssom/shorthand-values.html.ini3
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
-