mirror of
https://github.com/servo/servo.git
synced 2025-08-04 13:10:20 +01:00
Auto merge of #21452 - emilio:gecko-sync, r=emilio
style: Import changes from mozilla-central. See each individual commit for details. <!-- Reviewable:start --> --- This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/servo/servo/21452) <!-- Reviewable:end -->
This commit is contained in:
commit
e7791f9a00
43 changed files with 1930 additions and 1348 deletions
3
Cargo.lock
generated
3
Cargo.lock
generated
|
@ -3082,7 +3082,6 @@ version = "0.19.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
"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)",
|
"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)",
|
"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)",
|
"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)",
|
"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)",
|
"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)",
|
"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)",
|
"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)",
|
"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)",
|
"log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"malloc_size_of 0.0.1",
|
"malloc_size_of 0.0.1",
|
||||||
|
@ -3493,6 +3491,7 @@ dependencies = [
|
||||||
"matches 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
"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-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)",
|
"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-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-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)",
|
"num_cpus 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
|
|
@ -66,6 +66,7 @@ reftest-wait
|
||||||
reset
|
reset
|
||||||
right
|
right
|
||||||
sans-serif
|
sans-serif
|
||||||
|
scan
|
||||||
screen
|
screen
|
||||||
scroll-position
|
scroll-position
|
||||||
search
|
search
|
||||||
|
@ -86,3 +87,4 @@ url
|
||||||
waiting
|
waiting
|
||||||
webglcontextcreationerror
|
webglcontextcreationerror
|
||||||
week
|
week
|
||||||
|
width
|
||||||
|
|
|
@ -24,7 +24,6 @@ bitflags = "1.0"
|
||||||
matches = "0.1"
|
matches = "0.1"
|
||||||
cssparser = "0.24.0"
|
cssparser = "0.24.0"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
fnv = "1.0"
|
|
||||||
fxhash = "0.2"
|
fxhash = "0.2"
|
||||||
phf = "0.7.18"
|
phf = "0.7.18"
|
||||||
precomputed-hash = "0.1"
|
precomputed-hash = "0.1"
|
||||||
|
|
|
@ -5,9 +5,7 @@
|
||||||
//! Counting and non-counting Bloom filters tuned for use as ancestor filters
|
//! Counting and non-counting Bloom filters tuned for use as ancestor filters
|
||||||
//! for selector matching.
|
//! for selector matching.
|
||||||
|
|
||||||
use fnv::FnvHasher;
|
|
||||||
use std::fmt::{self, Debug};
|
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.
|
// 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.
|
// Consumers may rely on this to pack hashes more efficiently.
|
||||||
|
@ -108,43 +106,27 @@ where
|
||||||
unreachable!()
|
unreachable!()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Inserts an item with a particular hash into the bloom filter.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn insert_hash(&mut self, hash: u32) {
|
pub fn insert_hash(&mut self, hash: u32) {
|
||||||
self.storage.adjust_first_slot(hash, true);
|
self.storage.adjust_first_slot(hash, true);
|
||||||
self.storage.adjust_second_slot(hash, true);
|
self.storage.adjust_second_slot(hash, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Inserts an item into the bloom filter.
|
/// Removes an item with a particular hash from the bloom filter.
|
||||||
#[inline]
|
|
||||||
pub fn insert<T: Hash>(&mut self, elem: &T) {
|
|
||||||
self.insert_hash(hash(elem))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn remove_hash(&mut self, hash: u32) {
|
pub fn remove_hash(&mut self, hash: u32) {
|
||||||
self.storage.adjust_first_slot(hash, false);
|
self.storage.adjust_first_slot(hash, false);
|
||||||
self.storage.adjust_second_slot(hash, false);
|
self.storage.adjust_second_slot(hash, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Removes an item from the bloom filter.
|
/// Check whether the filter might contain an item with the given hash.
|
||||||
#[inline]
|
/// This can sometimes return true even if the item is not in the filter,
|
||||||
pub fn remove<T: Hash>(&mut self, elem: &T) {
|
/// but will never return false for items that are actually in the filter.
|
||||||
self.remove_hash(hash(elem))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn might_contain_hash(&self, hash: u32) -> bool {
|
pub fn might_contain_hash(&self, hash: u32) -> bool {
|
||||||
!self.storage.first_slot_is_empty(hash) && !self.storage.second_slot_is_empty(hash)
|
!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>
|
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]
|
#[inline]
|
||||||
fn hash1(hash: u32) -> u32 {
|
fn hash1(hash: u32) -> u32 {
|
||||||
hash & KEY_MASK
|
hash & KEY_MASK
|
||||||
|
@ -318,8 +290,18 @@ fn hash2(hash: u32) -> u32 {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn create_and_insert_some_stuff() {
|
fn create_and_insert_some_stuff() {
|
||||||
|
use fxhash::FxHasher;
|
||||||
|
use std::hash::{Hash, Hasher};
|
||||||
use std::mem::transmute;
|
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();
|
let mut bf = BloomFilter::new();
|
||||||
|
|
||||||
// Statically assert that ARRAY_SIZE is a multiple of 8, which
|
// 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 {
|
for i in 0_usize..1000 {
|
||||||
bf.insert(&i);
|
bf.insert_hash(hash_as_str(i));
|
||||||
}
|
}
|
||||||
|
|
||||||
for i in 0_usize..1000 {
|
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 {
|
for i in 0_usize..100 {
|
||||||
bf.remove(&i);
|
bf.remove_hash(hash_as_str(i));
|
||||||
}
|
}
|
||||||
|
|
||||||
for i in 100_usize..1000 {
|
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%.
|
assert!(false_positives < 20, "{} is not < 20", false_positives); // 20%.
|
||||||
|
|
||||||
bf.clear();
|
bf.clear();
|
||||||
|
|
||||||
for i in 0_usize..2000 {
|
for i in 0_usize..2000 {
|
||||||
assert!(!bf.might_contain(&i));
|
assert!(!bf.might_contain_hash(hash_as_str(i)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,6 @@
|
||||||
extern crate bitflags;
|
extern crate bitflags;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate cssparser;
|
extern crate cssparser;
|
||||||
extern crate fnv;
|
|
||||||
extern crate fxhash;
|
extern crate fxhash;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate log;
|
extern crate log;
|
||||||
|
|
|
@ -47,8 +47,9 @@ malloc_size_of = { path = "../malloc_size_of" }
|
||||||
malloc_size_of_derive = { path = "../malloc_size_of_derive" }
|
malloc_size_of_derive = { path = "../malloc_size_of_derive" }
|
||||||
matches = "0.1"
|
matches = "0.1"
|
||||||
num_cpus = {version = "1.1.0", optional = true}
|
num_cpus = {version = "1.1.0", optional = true}
|
||||||
num-integer = "0.1.32"
|
num-integer = "0.1"
|
||||||
num-traits = "0.2"
|
num-traits = "0.2"
|
||||||
|
num-derive = "0.2"
|
||||||
new-ordered-float = "1.0"
|
new-ordered-float = "1.0"
|
||||||
owning_ref = "0.3.3"
|
owning_ref = "0.3.3"
|
||||||
parking_lot = "0.6"
|
parking_lot = "0.6"
|
||||||
|
@ -72,9 +73,6 @@ unicode-bidi = "0.3"
|
||||||
unicode-segmentation = "1.0"
|
unicode-segmentation = "1.0"
|
||||||
void = "1.0.2"
|
void = "1.0.2"
|
||||||
|
|
||||||
[target.'cfg(windows)'.dependencies]
|
|
||||||
kernel32-sys = "0.2"
|
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
lazy_static = "1"
|
lazy_static = "1"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
|
|
|
@ -22,5 +22,5 @@ derive_helper_methods = true
|
||||||
|
|
||||||
[export]
|
[export]
|
||||||
prefix = "Style"
|
prefix = "Style"
|
||||||
include = ["StyleDisplay", "StyleAppearance"]
|
include = ["StyleDisplay", "StyleAppearance", "StyleDisplayMode"]
|
||||||
item_types = ["enums"]
|
item_types = ["enums"]
|
||||||
|
|
|
@ -659,7 +659,7 @@ pub mod basic_shape {
|
||||||
StyleShapeSourceType::None => Some(ShapeSource::None),
|
StyleShapeSourceType::None => Some(ShapeSource::None),
|
||||||
StyleShapeSourceType::Box => Some(ShapeSource::Box(self.mReferenceBox.into())),
|
StyleShapeSourceType::Box => Some(ShapeSource::Box(self.mReferenceBox.into())),
|
||||||
StyleShapeSourceType::Shape => {
|
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 shape = other_shape.into();
|
||||||
let reference_box = if self.mReferenceBox == StyleGeometryBox::NoBox {
|
let reference_box = if self.mReferenceBox == StyleGeometryBox::NoBox {
|
||||||
None
|
None
|
||||||
|
@ -677,7 +677,7 @@ pub mod basic_shape {
|
||||||
fn from(other: &'a StyleShapeSource) -> Self {
|
fn from(other: &'a StyleShapeSource) -> Self {
|
||||||
match other.mType {
|
match other.mType {
|
||||||
StyleShapeSourceType::URL => unsafe {
|
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 other_url = RefPtr::new(*shape_image.__bindgen_anon_1.mURLValue.as_ref());
|
||||||
let url = ComputedUrl::from_url_value(other_url);
|
let url = ComputedUrl::from_url_value(other_url);
|
||||||
ShapeSource::ImageOrUrl(url)
|
ShapeSource::ImageOrUrl(url)
|
||||||
|
@ -699,7 +699,7 @@ pub mod basic_shape {
|
||||||
unreachable!("FloatAreaShape doesn't support URL!");
|
unreachable!("FloatAreaShape doesn't support URL!");
|
||||||
},
|
},
|
||||||
StyleShapeSourceType::Image => unsafe {
|
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");
|
let image = shape_image.into_image().expect("Cannot convert to Image");
|
||||||
ShapeSource::ImageOrUrl(image)
|
ShapeSource::ImageOrUrl(image)
|
||||||
},
|
},
|
||||||
|
|
|
@ -8,7 +8,7 @@ use atomic_refcell::{AtomicRef, AtomicRefCell, AtomicRefMut};
|
||||||
use context::QuirksMode;
|
use context::QuirksMode;
|
||||||
use dom::TElement;
|
use dom::TElement;
|
||||||
use gecko_bindings::bindings::{self, RawServoStyleSet};
|
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::structs::{StyleSheetInfo, nsIDocument};
|
||||||
use gecko_bindings::sugar::ownership::{HasArcFFI, HasBoxFFI, HasFFI, HasSimpleFFI};
|
use gecko_bindings::sugar::ownership::{HasArcFFI, HasBoxFFI, HasFFI, HasSimpleFFI};
|
||||||
use invalidation::media_queries::{MediaListKey, ToMediaListKey};
|
use invalidation::media_queries::{MediaListKey, ToMediaListKey};
|
||||||
|
@ -185,52 +185,20 @@ impl PerDocumentStyleDataImpl {
|
||||||
.flush(&StylesheetGuards::same(guard), document_element, snapshots)
|
.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.
|
/// Get the default computed values for this document.
|
||||||
pub fn default_computed_values(&self) -> &Arc<ComputedValues> {
|
pub fn default_computed_values(&self) -> &Arc<ComputedValues> {
|
||||||
self.stylist.device().default_computed_values_arc()
|
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.
|
/// Returns whether visited styles are enabled.
|
||||||
|
#[inline]
|
||||||
pub fn visited_styles_enabled(&self) -> bool {
|
pub fn visited_styles_enabled(&self) -> bool {
|
||||||
if !self.visited_links_enabled() {
|
let doc = self.stylist
|
||||||
return false;
|
.device()
|
||||||
}
|
.pres_context()
|
||||||
|
.mDocument
|
||||||
if self.is_private_browsing_enabled() {
|
.raw::<nsIDocument>();
|
||||||
return false;
|
unsafe { bindings::Gecko_VisitedStylesEnabled(doc) }
|
||||||
}
|
|
||||||
|
|
||||||
if self.is_being_used_as_an_image() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Measure heap usage.
|
/// Measure heap usage.
|
||||||
|
|
610
components/style/gecko/media_features.rs
Normal file
610
components/style/gecko/media_features.rs
Normal file
|
@ -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(),
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
|
@ -6,34 +6,23 @@
|
||||||
|
|
||||||
use app_units::AU_PER_PX;
|
use app_units::AU_PER_PX;
|
||||||
use app_units::Au;
|
use app_units::Au;
|
||||||
use context::QuirksMode;
|
use cssparser::RGBA;
|
||||||
use cssparser::{Parser, RGBA, Token};
|
|
||||||
use euclid::Size2D;
|
use euclid::Size2D;
|
||||||
use euclid::TypedScale;
|
use euclid::TypedScale;
|
||||||
use gecko::values::{convert_nscolor_to_rgba, convert_rgba_to_nscolor};
|
use gecko::values::{convert_nscolor_to_rgba, convert_rgba_to_nscolor};
|
||||||
use gecko_bindings::bindings;
|
use gecko_bindings::bindings;
|
||||||
use gecko_bindings::structs;
|
use gecko_bindings::structs;
|
||||||
use gecko_bindings::structs::{nsCSSKTableEntry, nsCSSKeyword, nsCSSUnit, nsCSSValue};
|
use gecko_bindings::structs::{nsPresContext, RawGeckoPresContextOwned};
|
||||||
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 media_queries::MediaType;
|
use media_queries::MediaType;
|
||||||
use parser::{Parse, ParserContext};
|
|
||||||
use properties::ComputedValues;
|
use properties::ComputedValues;
|
||||||
use servo_arc::Arc;
|
use servo_arc::Arc;
|
||||||
use std::fmt::{self, Write};
|
use std::fmt;
|
||||||
use std::sync::atomic::{AtomicBool, AtomicIsize, AtomicUsize, Ordering};
|
use std::sync::atomic::{AtomicBool, AtomicIsize, AtomicUsize, Ordering};
|
||||||
use str::{starts_with_ignore_ascii_case, string_as_ascii_lowercase};
|
|
||||||
use string_cache::Atom;
|
use string_cache::Atom;
|
||||||
use style_traits::{CSSPixel, CssWriter, DevicePixel};
|
use style_traits::{CSSPixel, DevicePixel};
|
||||||
use style_traits::{ParseError, StyleParseErrorKind, ToCss};
|
|
||||||
use style_traits::viewport::ViewportConstraints;
|
use style_traits::viewport::ViewportConstraints;
|
||||||
use stylesheets::Origin;
|
use values::{CustomIdent, KeyframesName};
|
||||||
use values::{serialize_atom_identifier, CSSFloat, CustomIdent, KeyframesName};
|
|
||||||
use values::computed::{self, ToComputedValue};
|
|
||||||
use values::computed::font::FontSize;
|
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,
|
/// The `Device` in Gecko wraps a pres context, has a default values computed,
|
||||||
/// and contains all the viewport rule state.
|
/// and contains all the viewport rule state.
|
||||||
|
@ -71,11 +60,8 @@ impl fmt::Debug for Device {
|
||||||
|
|
||||||
let mut doc_uri = nsCString::new();
|
let mut doc_uri = nsCString::new();
|
||||||
unsafe {
|
unsafe {
|
||||||
let doc =
|
|
||||||
&*self.pres_context().mDocument.raw::<structs::nsIDocument>();
|
|
||||||
|
|
||||||
bindings::Gecko_nsIURI_Debug(
|
bindings::Gecko_nsIURI_Debug(
|
||||||
doc.mDocumentURI.raw::<structs::nsIURI>(),
|
(*self.document()).mDocumentURI.raw::<structs::nsIURI>(),
|
||||||
&mut doc_uri,
|
&mut doc_uri,
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
@ -157,10 +143,17 @@ impl Device {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets the pres context associated with this document.
|
/// Gets the pres context associated with this document.
|
||||||
|
#[inline]
|
||||||
pub fn pres_context(&self) -> &nsPresContext {
|
pub fn pres_context(&self) -> &nsPresContext {
|
||||||
unsafe { &*self.pres_context }
|
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.
|
/// Recreates the default computed values.
|
||||||
pub fn reset_computed_values(&mut self) {
|
pub fn reset_computed_values(&mut self) {
|
||||||
self.default_values = ComputedValues::default_values(self.pres_context());
|
self.default_values = ComputedValues::default_values(self.pres_context());
|
||||||
|
@ -248,758 +241,3 @@ impl Device {
|
||||||
size.scale_by(1. / self.pres_context().mEffectiveTextZoom)
|
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -11,6 +11,7 @@ pub mod arc_types;
|
||||||
pub mod conversions;
|
pub mod conversions;
|
||||||
pub mod data;
|
pub mod data;
|
||||||
pub mod global_style_data;
|
pub mod global_style_data;
|
||||||
|
pub mod media_features;
|
||||||
pub mod media_queries;
|
pub mod media_queries;
|
||||||
pub mod pseudo_element;
|
pub mod pseudo_element;
|
||||||
pub mod restyle_damage;
|
pub mod restyle_damage;
|
||||||
|
|
|
@ -8,9 +8,9 @@ pub enum PseudoElement {
|
||||||
% for pseudo in PSEUDOS:
|
% for pseudo in PSEUDOS:
|
||||||
/// ${pseudo.value}
|
/// ${pseudo.value}
|
||||||
% if pseudo.is_tree_pseudo_element():
|
% if pseudo.is_tree_pseudo_element():
|
||||||
${pseudo.capitalized()}(ThinBoxedSlice<Atom>),
|
${pseudo.capitalized_pseudo()}(ThinBoxedSlice<Atom>),
|
||||||
% else:
|
% else:
|
||||||
${pseudo.capitalized()},
|
${pseudo.capitalized_pseudo()},
|
||||||
% endif
|
% endif
|
||||||
% endfor
|
% endfor
|
||||||
}
|
}
|
||||||
|
@ -41,7 +41,7 @@ pub const EAGER_PSEUDOS: [PseudoElement; EAGER_PSEUDO_COUNT] = [
|
||||||
];
|
];
|
||||||
|
|
||||||
<%def name="pseudo_element_variant(pseudo, tree_arg='..')">\
|
<%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>
|
</%def>
|
||||||
|
|
||||||
impl PseudoElement {
|
impl PseudoElement {
|
||||||
|
@ -120,7 +120,7 @@ impl PseudoElement {
|
||||||
% elif pseudo.is_anon_box():
|
% elif pseudo.is_anon_box():
|
||||||
structs::CSS_PSEUDO_ELEMENT_ENABLED_IN_UA_SHEETS,
|
structs::CSS_PSEUDO_ELEMENT_ENABLED_IN_UA_SHEETS,
|
||||||
% else:
|
% else:
|
||||||
structs::SERVO_CSS_PSEUDO_ELEMENT_FLAGS_${pseudo.original_ident},
|
structs::SERVO_CSS_PSEUDO_ELEMENT_FLAGS_${pseudo.pseudo_ident},
|
||||||
% endif
|
% endif
|
||||||
% endfor
|
% endfor
|
||||||
}
|
}
|
||||||
|
@ -132,7 +132,7 @@ impl PseudoElement {
|
||||||
match type_ {
|
match type_ {
|
||||||
% for pseudo in PSEUDOS:
|
% for pseudo in PSEUDOS:
|
||||||
% if not pseudo.is_anon_box():
|
% if not pseudo.is_anon_box():
|
||||||
CSSPseudoElementType::${pseudo.original_ident} => {
|
CSSPseudoElementType::${pseudo.pseudo_ident} => {
|
||||||
Some(${pseudo_element_variant(pseudo)})
|
Some(${pseudo_element_variant(pseudo)})
|
||||||
},
|
},
|
||||||
% endif
|
% endif
|
||||||
|
@ -149,13 +149,13 @@ impl PseudoElement {
|
||||||
match *self {
|
match *self {
|
||||||
% for pseudo in PSEUDOS:
|
% for pseudo in PSEUDOS:
|
||||||
% if not pseudo.is_anon_box():
|
% 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():
|
% elif pseudo.is_tree_pseudo_element():
|
||||||
PseudoElement::${pseudo.capitalized()}(..) => CSSPseudoElementType::XULTree,
|
PseudoElement::${pseudo.capitalized_pseudo()}(..) => CSSPseudoElementType::XULTree,
|
||||||
% elif pseudo.is_inheriting_anon_box():
|
% elif pseudo.is_inheriting_anon_box():
|
||||||
PseudoElement::${pseudo.capitalized()} => CSSPseudoElementType_InheritingAnonBox,
|
PseudoElement::${pseudo.capitalized_pseudo()} => CSSPseudoElementType_InheritingAnonBox,
|
||||||
% else:
|
% else:
|
||||||
PseudoElement::${pseudo.capitalized()} => CSSPseudoElementType::NonInheritingAnonBox,
|
PseudoElement::${pseudo.capitalized_pseudo()} => CSSPseudoElementType::NonInheritingAnonBox,
|
||||||
% endif
|
% endif
|
||||||
% endfor
|
% endfor
|
||||||
}
|
}
|
||||||
|
@ -171,7 +171,7 @@ impl PseudoElement {
|
||||||
pub fn tree_pseudo_args(&self) -> Option<<&[Atom]> {
|
pub fn tree_pseudo_args(&self) -> Option<<&[Atom]> {
|
||||||
match *self {
|
match *self {
|
||||||
% for pseudo in TREE_PSEUDOS:
|
% for pseudo in TREE_PSEUDOS:
|
||||||
PseudoElement::${pseudo.capitalized()}(ref args) => Some(args),
|
PseudoElement::${pseudo.capitalized_pseudo()}(ref args) => Some(args),
|
||||||
% endfor
|
% endfor
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
|
@ -213,7 +213,7 @@ impl PseudoElement {
|
||||||
% for pseudo in PSEUDOS:
|
% for pseudo in PSEUDOS:
|
||||||
% if pseudo.is_tree_pseudo_element():
|
% if pseudo.is_tree_pseudo_element():
|
||||||
if atom == &atom!("${pseudo.value}") {
|
if atom == &atom!("${pseudo.value}") {
|
||||||
return Some(PseudoElement::${pseudo.capitalized()}(args.into()));
|
return Some(PseudoElement::${pseudo.capitalized_pseudo()}(args.into()));
|
||||||
}
|
}
|
||||||
% endif
|
% endif
|
||||||
% endfor
|
% endfor
|
||||||
|
|
|
@ -20,55 +20,29 @@ PRELUDE = """
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
* 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/. */
|
* 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
|
"""[1:] # NOQA: E501
|
||||||
|
|
||||||
|
|
||||||
def gnu_symbolify(source, ident):
|
# Matches lines like `GK_ATOM(foo, "foo", 0x12345678, nsStaticAtom, PseudoElementAtom)`.
|
||||||
return "_ZN{}{}{}{}E".format(len(source.CLASS), source.CLASS, len(ident), ident)
|
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):
|
def gnu_symbolify(ident):
|
||||||
return "?{}@{}@@2PEAV{}@@EA".format(ident, source.CLASS, source.TYPE)
|
return "_ZN{}{}{}{}E".format(len(CLASS), CLASS, len(ident), ident)
|
||||||
|
|
||||||
|
|
||||||
def msvc32_symbolify(source, ident):
|
def msvc64_symbolify(ident, ty):
|
||||||
|
return "?{}@{}@@2PEAV{}@@EA".format(ident, CLASS, ty)
|
||||||
|
|
||||||
|
|
||||||
|
def msvc32_symbolify(ident, ty):
|
||||||
# Prepend "\x01" to avoid LLVM prefixing the mangled name with "_".
|
# Prepend "\x01" to avoid LLVM prefixing the mangled name with "_".
|
||||||
# See https://github.com/rust-lang/rust/issues/36097
|
# See https://github.com/rust-lang/rust/issues/36097
|
||||||
return "\\x01?{}@{}@@2PAV{}@@A".format(ident, source.CLASS, source.TYPE)
|
return "\\x01?{}@{}@@2PAV{}@@A".format(ident, CLASS, ty)
|
||||||
|
|
||||||
|
|
||||||
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"
|
|
||||||
|
|
||||||
|
|
||||||
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 map_atom(ident):
|
def map_atom(ident):
|
||||||
|
@ -79,42 +53,47 @@ def map_atom(ident):
|
||||||
|
|
||||||
|
|
||||||
class Atom:
|
class Atom:
|
||||||
def __init__(self, source, macro_name, ident, value):
|
def __init__(self, ident, value, hash, ty, atom_type):
|
||||||
self.ident = "{}_{}".format(source.CLASS, ident)
|
self.ident = "{}_{}".format(CLASS, ident)
|
||||||
self.original_ident = ident
|
self.original_ident = ident
|
||||||
self.value = value
|
self.value = value
|
||||||
self.source = source
|
self.hash = hash
|
||||||
self.macro = macro_name
|
# 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():
|
if self.is_anon_box():
|
||||||
assert self.is_inheriting_anon_box() or self.is_non_inheriting_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):
|
def gnu_symbol(self):
|
||||||
return gnu_symbolify(self.source, self.original_ident)
|
return gnu_symbolify(self.original_ident)
|
||||||
|
|
||||||
def msvc32_symbol(self):
|
def msvc32_symbol(self):
|
||||||
return msvc32_symbolify(self.source, self.original_ident)
|
return msvc32_symbolify(self.original_ident, self.ty)
|
||||||
|
|
||||||
def msvc64_symbol(self):
|
def msvc64_symbol(self):
|
||||||
return msvc64_symbolify(self.source, self.original_ident)
|
return msvc64_symbolify(self.original_ident, self.ty)
|
||||||
|
|
||||||
def type(self):
|
def type(self):
|
||||||
return self.source.TYPE
|
return self.ty
|
||||||
|
|
||||||
def capitalized(self):
|
def capitalized_pseudo(self):
|
||||||
return self.original_ident[0].upper() + self.original_ident[1:]
|
return self.pseudo_ident[0].upper() + self.pseudo_ident[1:]
|
||||||
|
|
||||||
|
def is_pseudo(self):
|
||||||
|
return self.atom_type == "PseudoElementAtom"
|
||||||
|
|
||||||
def is_anon_box(self):
|
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):
|
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):
|
def is_inheriting_anon_box(self):
|
||||||
return (self.macro == "CSS_ANON_BOX" or
|
return self.atom_type == "InheritingAnonBoxAtom"
|
||||||
self.macro == "CSS_WRAPPER_ANON_BOX")
|
|
||||||
|
|
||||||
def is_tree_pseudo_element(self):
|
def is_tree_pseudo_element(self):
|
||||||
return self.value.startswith(":-moz-tree-")
|
return self.value.startswith(":-moz-tree-")
|
||||||
|
@ -122,13 +101,13 @@ class Atom:
|
||||||
|
|
||||||
def collect_atoms(objdir):
|
def collect_atoms(objdir):
|
||||||
atoms = []
|
atoms = []
|
||||||
for source in SOURCES:
|
path = os.path.abspath(os.path.join(objdir, FILE))
|
||||||
path = os.path.abspath(os.path.join(objdir, source.FILE))
|
print("cargo:rerun-if-changed={}".format(path))
|
||||||
print("cargo:rerun-if-changed={}".format(path))
|
with open(path) as f:
|
||||||
with open(path) as f:
|
content = f.read()
|
||||||
content = f.read()
|
for result in PATTERN.finditer(content):
|
||||||
for result in source.PATTERN.finditer(content):
|
atoms.append(Atom(result.group(1), result.group(2), result.group(3),
|
||||||
atoms.append(Atom(source, result.group(1), result.group(2), result.group(3)))
|
result.group(4), result.group(5)))
|
||||||
return atoms
|
return atoms
|
||||||
|
|
||||||
|
|
||||||
|
@ -219,9 +198,9 @@ def write_atom_macro(atoms, file_name):
|
||||||
f.write(PRELUDE)
|
f.write(PRELUDE)
|
||||||
f.write(IMPORTS)
|
f.write(IMPORTS)
|
||||||
|
|
||||||
for source in SOURCES:
|
for ty in sorted(set([atom.type() for atom in atoms])):
|
||||||
if source.TYPE != "nsStaticAtom":
|
if ty != "nsStaticAtom":
|
||||||
f.write("pub enum {} {{}}\n\n".format(source.TYPE))
|
f.write("pub enum {} {{}}\n\n".format(ty))
|
||||||
|
|
||||||
f.write(UNSAFE_STATIC)
|
f.write(UNSAFE_STATIC)
|
||||||
|
|
||||||
|
|
|
@ -51,10 +51,6 @@ impl GeckoElementSnapshot {
|
||||||
(self.mContains as u8 & flags as u8) != 0
|
(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
|
/// Returns true if the snapshot has stored state for pseudo-classes
|
||||||
/// that depend on things other than `ElementState`.
|
/// that depend on things other than `ElementState`.
|
||||||
#[inline]
|
#[inline]
|
||||||
|
@ -184,14 +180,7 @@ impl ElementSnapshot for GeckoElementSnapshot {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
let ptr = unsafe { bindings::Gecko_SnapshotAtomAttrValue(self, atom!("id").as_ptr()) };
|
snapshot_helpers::get_id(&*self.mAttrs)
|
||||||
|
|
||||||
// FIXME(emilio): This should assert, since this flag is exact.
|
|
||||||
if ptr.is_null() {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(unsafe { WeakAtom::new(ptr) })
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
@ -200,12 +189,7 @@ impl ElementSnapshot for GeckoElementSnapshot {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
snapshot_helpers::has_class(
|
snapshot_helpers::has_class(name, case_sensitivity, &self.mClass)
|
||||||
self.as_ptr(),
|
|
||||||
name,
|
|
||||||
case_sensitivity,
|
|
||||||
bindings::Gecko_SnapshotHasClass,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
@ -217,11 +201,7 @@ impl ElementSnapshot for GeckoElementSnapshot {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
snapshot_helpers::each_class(
|
snapshot_helpers::each_class(&self.mClass, callback)
|
||||||
self.as_ptr(),
|
|
||||||
callback,
|
|
||||||
bindings::Gecko_SnapshotClassOrClassList,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
|
|
@ -4,58 +4,114 @@
|
||||||
|
|
||||||
//! Element an snapshot common logic.
|
//! 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 selectors::attr::CaseSensitivity;
|
||||||
use std::{ptr, slice};
|
use string_cache::{Atom, WeakAtom};
|
||||||
use string_cache::Atom;
|
|
||||||
|
|
||||||
/// A function that, given an element of type `T`, allows you to get a single
|
/// A function that, given an element of type `T`, allows you to get a single
|
||||||
/// class or a class list.
|
/// class or a class list.
|
||||||
pub type ClassOrClassList<T> =
|
enum Class<'a> {
|
||||||
unsafe extern "C" fn(T, *mut *mut nsAtom, *mut *mut *mut nsAtom) -> u32;
|
None,
|
||||||
|
One(*const nsAtom),
|
||||||
|
More(&'a [structs::RefPtr<nsAtom>]),
|
||||||
|
}
|
||||||
|
|
||||||
/// 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;
|
|
||||||
|
|
||||||
/// Given an item `T`, a class name, and a getter function, return whether that
|
|
||||||
/// element has the class that `name` represents.
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn has_class<T>(
|
fn base_type(attr: &structs::nsAttrValue) -> structs::nsAttrValue_ValueBaseType {
|
||||||
item: T,
|
(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))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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 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(
|
||||||
name: &Atom,
|
name: &Atom,
|
||||||
case_sensitivity: CaseSensitivity,
|
case_sensitivity: CaseSensitivity,
|
||||||
getter: HasClass<T>,
|
attr: &structs::nsAttrValue,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
let ignore_case = match case_sensitivity {
|
match unsafe { get_class_from_attr(attr) } {
|
||||||
CaseSensitivity::CaseSensitive => false,
|
Class::None => false,
|
||||||
CaseSensitivity::AsciiCaseInsensitive => true,
|
Class::One(atom) => unsafe {
|
||||||
};
|
case_sensitivity.eq_atom(name, WeakAtom::new(atom))
|
||||||
|
},
|
||||||
unsafe { getter(item, name.as_ptr(), ignore_case) }
|
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
|
/// Given an item, a callback, and a getter, execute `callback` for each class
|
||||||
/// this `item` has.
|
/// 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
|
where
|
||||||
F: FnMut(&Atom),
|
F: FnMut(&Atom),
|
||||||
{
|
{
|
||||||
unsafe {
|
unsafe {
|
||||||
let mut class: *mut nsAtom = ptr::null_mut();
|
match get_class_from_attr(attr) {
|
||||||
let mut list: *mut *mut nsAtom = ptr::null_mut();
|
Class::None => {},
|
||||||
let length = getter(item, &mut class, &mut list);
|
Class::One(atom) => Atom::with(atom, callback),
|
||||||
match length {
|
Class::More(atoms) => {
|
||||||
0 => {},
|
for atom in atoms {
|
||||||
1 => Atom::with(class, callback),
|
Atom::with(atom.mRawPtr, &mut callback)
|
||||||
n => {
|
|
||||||
let classes = slice::from_raw_parts(list, n as usize);
|
|
||||||
for c in classes {
|
|
||||||
Atom::with(*c, &mut callback)
|
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,7 +32,6 @@ use gecko_bindings::bindings;
|
||||||
use gecko_bindings::bindings::{Gecko_ElementState, Gecko_GetDocumentLWTheme};
|
use gecko_bindings::bindings::{Gecko_ElementState, Gecko_GetDocumentLWTheme};
|
||||||
use gecko_bindings::bindings::{Gecko_GetLastChild, Gecko_GetPreviousSibling, Gecko_GetNextStyleChild};
|
use gecko_bindings::bindings::{Gecko_GetLastChild, Gecko_GetPreviousSibling, Gecko_GetNextStyleChild};
|
||||||
use gecko_bindings::bindings::{Gecko_SetNodeFlags, Gecko_UnsetNodeFlags};
|
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_ElementHasAnimations;
|
||||||
use gecko_bindings::bindings::Gecko_ElementHasCSSAnimations;
|
use gecko_bindings::bindings::Gecko_ElementHasCSSAnimations;
|
||||||
use gecko_bindings::bindings::Gecko_ElementHasCSSTransitions;
|
use gecko_bindings::bindings::Gecko_ElementHasCSSTransitions;
|
||||||
|
@ -581,6 +580,34 @@ impl<'le> fmt::Debug for GeckoElement<'le> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'le> 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]
|
#[inline]
|
||||||
fn closest_anon_subtree_root_parent(&self) -> Option<Self> {
|
fn closest_anon_subtree_root_parent(&self) -> Option<Self> {
|
||||||
debug_assert!(self.is_in_native_anonymous_subtree());
|
debug_assert!(self.is_in_native_anonymous_subtree());
|
||||||
|
@ -1281,26 +1308,19 @@ impl<'le> TElement for GeckoElement<'le> {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
let ptr = unsafe { bindings::Gecko_AtomAttrValue(self.0, atom!("id").as_ptr()) };
|
snapshot_helpers::get_id(self.attrs())
|
||||||
|
|
||||||
// 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) })
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn each_class<F>(&self, callback: F)
|
fn each_class<F>(&self, callback: F)
|
||||||
where
|
where
|
||||||
F: FnMut(&Atom),
|
F: FnMut(&Atom),
|
||||||
{
|
{
|
||||||
if !self.may_have_class() {
|
let attr = match self.get_class_attr() {
|
||||||
return;
|
Some(c) => c,
|
||||||
}
|
None => return,
|
||||||
|
};
|
||||||
|
|
||||||
snapshot_helpers::each_class(self.0, callback, Gecko_ClassOrClassList)
|
snapshot_helpers::each_class(attr, callback)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
@ -2265,24 +2285,22 @@ impl<'le> ::selectors::Element for GeckoElement<'le> {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe {
|
let element_id = match snapshot_helpers::get_id(self.attrs()) {
|
||||||
let ptr = bindings::Gecko_AtomAttrValue(self.0, atom!("id").as_ptr());
|
Some(id) => id,
|
||||||
|
None => return false,
|
||||||
|
};
|
||||||
|
|
||||||
if ptr.is_null() {
|
case_sensitivity.eq_atom(element_id, id)
|
||||||
false
|
|
||||||
} else {
|
|
||||||
case_sensitivity.eq_atom(WeakAtom::new(ptr), id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn has_class(&self, name: &Atom, case_sensitivity: CaseSensitivity) -> bool {
|
fn has_class(&self, name: &Atom, case_sensitivity: CaseSensitivity) -> bool {
|
||||||
if !self.may_have_class() {
|
let attr = match self.get_class_attr() {
|
||||||
return false;
|
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]
|
#[inline]
|
||||||
|
|
|
@ -49,10 +49,6 @@ pub struct Atom(*mut WeakAtom);
|
||||||
/// where `'a` is the lifetime of something that holds a strong reference to that atom.
|
/// where `'a` is the lifetime of something that holds a strong reference to that atom.
|
||||||
pub struct WeakAtom(nsAtom);
|
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 {
|
impl Deref for Atom {
|
||||||
type Target = WeakAtom;
|
type Target = WeakAtom;
|
||||||
|
|
||||||
|
@ -267,7 +263,7 @@ impl fmt::Display for WeakAtom {
|
||||||
|
|
||||||
impl Atom {
|
impl Atom {
|
||||||
/// Execute a callback with the atom represented by `ptr`.
|
/// 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
|
where
|
||||||
F: FnOnce(&Atom) -> R,
|
F: FnOnce(&Atom) -> R,
|
||||||
{
|
{
|
||||||
|
|
|
@ -24,12 +24,6 @@ use selectors::matching::matches_selector;
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
use stylesheets::origin::{Origin, OriginSet};
|
use stylesheets::origin::{Origin, OriginSet};
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
|
||||||
enum VisitedDependent {
|
|
||||||
Yes,
|
|
||||||
No,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The collector implementation.
|
/// The collector implementation.
|
||||||
struct Collector<'a, 'b: 'a, 'selectors: 'a, E>
|
struct Collector<'a, 'b: 'a, 'selectors: 'a, E>
|
||||||
where
|
where
|
||||||
|
@ -167,8 +161,13 @@ where
|
||||||
// do for this case.
|
// do for this case.
|
||||||
if state_changes.intersects(ElementState::IN_VISITED_OR_UNVISITED_STATE) {
|
if state_changes.intersects(ElementState::IN_VISITED_OR_UNVISITED_STATE) {
|
||||||
trace!(" > visitedness change, force subtree restyle");
|
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
|
// 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());
|
self.data.hint.insert(RestyleHint::restyle_subtree());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -347,7 +346,7 @@ where
|
||||||
if let Some(ref id) = removed_id {
|
if let Some(ref id) = removed_id {
|
||||||
if let Some(deps) = map.id_to_selector.get(id, quirks_mode) {
|
if let Some(deps) = map.id_to_selector.get(id, quirks_mode) {
|
||||||
for dep in deps {
|
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(ref id) = added_id {
|
||||||
if let Some(deps) = map.id_to_selector.get(id, quirks_mode) {
|
if let Some(deps) = map.id_to_selector.get(id, quirks_mode) {
|
||||||
for dep in deps {
|
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()) {
|
for class in self.classes_added.iter().chain(self.classes_removed.iter()) {
|
||||||
if let Some(deps) = map.class_to_selector.get(class, quirks_mode) {
|
if let Some(deps) = map.class_to_selector.get(class, quirks_mode) {
|
||||||
for dep in deps {
|
for dep in deps {
|
||||||
self.scan_dependency(dep, VisitedDependent::No);
|
self.scan_dependency(dep);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -390,7 +389,7 @@ where
|
||||||
self.removed_id,
|
self.removed_id,
|
||||||
self.classes_removed,
|
self.classes_removed,
|
||||||
|dependency| {
|
|dependency| {
|
||||||
self.scan_dependency(dependency, VisitedDependent::No);
|
self.scan_dependency(dependency);
|
||||||
true
|
true
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
@ -410,98 +409,53 @@ where
|
||||||
if !dependency.state.intersects(state_changes) {
|
if !dependency.state.intersects(state_changes) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
let visited_dependent = if dependency
|
self.scan_dependency(&dependency.dep);
|
||||||
.state
|
|
||||||
.intersects(ElementState::IN_VISITED_OR_UNVISITED_STATE)
|
|
||||||
{
|
|
||||||
VisitedDependent::Yes
|
|
||||||
} else {
|
|
||||||
VisitedDependent::No
|
|
||||||
};
|
|
||||||
self.scan_dependency(&dependency.dep, visited_dependent);
|
|
||||||
true
|
true
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check whether a dependency should be taken into account, using a given
|
/// Check whether a dependency should be taken into account.
|
||||||
/// visited handling mode.
|
|
||||||
fn check_dependency(
|
fn check_dependency(
|
||||||
&mut self,
|
&mut self,
|
||||||
visited_handling_mode: VisitedHandlingMode,
|
|
||||||
dependency: &Dependency,
|
dependency: &Dependency,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
let element = &self.element;
|
let element = &self.element;
|
||||||
let wrapper = &self.wrapper;
|
let wrapper = &self.wrapper;
|
||||||
self.matching_context
|
let matches_now = matches_selector(
|
||||||
.with_visited_handling_mode(visited_handling_mode, |mut context| {
|
&dependency.selector,
|
||||||
let matches_now = matches_selector(
|
dependency.selector_offset,
|
||||||
&dependency.selector,
|
None,
|
||||||
dependency.selector_offset,
|
element,
|
||||||
None,
|
&mut self.matching_context,
|
||||||
element,
|
&mut |_, _| {},
|
||||||
&mut context,
|
);
|
||||||
&mut |_, _| {},
|
|
||||||
);
|
|
||||||
|
|
||||||
let matched_then = matches_selector(
|
let matched_then = matches_selector(
|
||||||
&dependency.selector,
|
&dependency.selector,
|
||||||
dependency.selector_offset,
|
dependency.selector_offset,
|
||||||
None,
|
None,
|
||||||
wrapper,
|
wrapper,
|
||||||
&mut context,
|
&mut self.matching_context,
|
||||||
&mut |_, _| {},
|
&mut |_, _| {},
|
||||||
);
|
);
|
||||||
|
|
||||||
matched_then != matches_now
|
matched_then != matches_now
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn scan_dependency(
|
fn scan_dependency(&mut self, dependency: &'selectors Dependency) {
|
||||||
&mut self,
|
|
||||||
dependency: &'selectors Dependency,
|
|
||||||
is_visited_dependent: VisitedDependent,
|
|
||||||
) {
|
|
||||||
debug!(
|
debug!(
|
||||||
"TreeStyleInvalidator::scan_dependency({:?}, {:?}, {:?})",
|
"TreeStyleInvalidator::scan_dependency({:?}, {:?})",
|
||||||
self.element, dependency, is_visited_dependent,
|
self.element, dependency
|
||||||
);
|
);
|
||||||
|
|
||||||
if !self.dependency_may_be_relevant(dependency) {
|
if !self.dependency_may_be_relevant(dependency) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let should_account_for_dependency =
|
if self.check_dependency(dependency) {
|
||||||
self.check_dependency(VisitedHandlingMode::AllLinksVisitedAndUnvisited, dependency);
|
|
||||||
|
|
||||||
if should_account_for_dependency {
|
|
||||||
return self.note_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) {
|
fn note_dependency(&mut self, dependency: &'selectors Dependency) {
|
||||||
|
|
|
@ -67,6 +67,8 @@ extern crate matches;
|
||||||
pub extern crate nsstring;
|
pub extern crate nsstring;
|
||||||
#[cfg(feature = "gecko")]
|
#[cfg(feature = "gecko")]
|
||||||
extern crate num_cpus;
|
extern crate num_cpus;
|
||||||
|
#[macro_use]
|
||||||
|
extern crate num_derive;
|
||||||
extern crate num_integer;
|
extern crate num_integer;
|
||||||
extern crate num_traits;
|
extern crate num_traits;
|
||||||
extern crate ordered_float;
|
extern crate ordered_float;
|
||||||
|
@ -128,15 +130,13 @@ pub mod font_face;
|
||||||
pub mod font_metrics;
|
pub mod font_metrics;
|
||||||
#[cfg(feature = "gecko")]
|
#[cfg(feature = "gecko")]
|
||||||
#[allow(unsafe_code)]
|
#[allow(unsafe_code)]
|
||||||
pub mod gecko;
|
|
||||||
#[cfg(feature = "gecko")]
|
|
||||||
#[allow(unsafe_code)]
|
|
||||||
pub mod gecko_bindings;
|
pub mod gecko_bindings;
|
||||||
pub mod hash;
|
pub mod hash;
|
||||||
pub mod invalidation;
|
pub mod invalidation;
|
||||||
#[allow(missing_docs)] // TODO.
|
#[allow(missing_docs)] // TODO.
|
||||||
pub mod logical_geometry;
|
pub mod logical_geometry;
|
||||||
pub mod matching;
|
pub mod matching;
|
||||||
|
#[macro_use]
|
||||||
pub mod media_queries;
|
pub mod media_queries;
|
||||||
pub mod parallel;
|
pub mod parallel;
|
||||||
pub mod parser;
|
pub mod parser;
|
||||||
|
@ -190,11 +190,16 @@ pub mod properties {
|
||||||
include!(concat!(env!("OUT_DIR"), "/properties.rs"));
|
include!(concat!(env!("OUT_DIR"), "/properties.rs"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "gecko")]
|
||||||
|
#[allow(unsafe_code)]
|
||||||
|
pub mod gecko;
|
||||||
|
|
||||||
// uses a macro from properties
|
// uses a macro from properties
|
||||||
#[cfg(feature = "servo")]
|
#[cfg(feature = "servo")]
|
||||||
#[allow(unsafe_code)]
|
#[allow(unsafe_code)]
|
||||||
pub mod servo;
|
pub mod servo;
|
||||||
|
|
||||||
|
|
||||||
#[cfg(feature = "gecko")]
|
#[cfg(feature = "gecko")]
|
||||||
#[allow(unsafe_code, missing_docs)]
|
#[allow(unsafe_code, missing_docs)]
|
||||||
pub mod gecko_properties {
|
pub mod gecko_properties {
|
||||||
|
|
|
@ -13,7 +13,6 @@ use std::fmt::{self, Write};
|
||||||
use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss};
|
use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss};
|
||||||
use super::{Device, MediaFeatureExpression};
|
use super::{Device, MediaFeatureExpression};
|
||||||
|
|
||||||
|
|
||||||
/// A binary `and` or `or` operator.
|
/// A binary `and` or `or` operator.
|
||||||
#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, Parse, PartialEq, ToCss)]
|
#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, Parse, PartialEq, ToCss)]
|
||||||
#[allow(missing_docs)]
|
#[allow(missing_docs)]
|
||||||
|
|
183
components/style/media_queries/media_feature.rs
Normal file
183
components/style/media_queries/media_feature.rs
Normal file
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
580
components/style/media_queries/media_feature_expression.rs
Normal file
580
components/style/media_queries/media_feature_expression.rs
Normal file
|
@ -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()))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -9,12 +9,16 @@
|
||||||
mod media_condition;
|
mod media_condition;
|
||||||
mod media_list;
|
mod media_list;
|
||||||
mod media_query;
|
mod media_query;
|
||||||
|
#[macro_use]
|
||||||
|
pub mod media_feature;
|
||||||
|
pub mod media_feature_expression;
|
||||||
|
|
||||||
pub use self::media_condition::MediaCondition;
|
pub use self::media_condition::MediaCondition;
|
||||||
pub use self::media_list::MediaList;
|
pub use self::media_list::MediaList;
|
||||||
pub use self::media_query::{MediaQuery, MediaQueryType, MediaType, Qualifier};
|
pub use self::media_query::{MediaQuery, MediaQueryType, MediaType, Qualifier};
|
||||||
|
pub use self::media_feature_expression::MediaFeatureExpression;
|
||||||
|
|
||||||
#[cfg(feature = "servo")]
|
#[cfg(feature = "servo")]
|
||||||
pub use servo::media_queries::{Device, MediaFeatureExpression};
|
pub use servo::media_queries::Device;
|
||||||
#[cfg(feature = "gecko")]
|
#[cfg(feature = "gecko")]
|
||||||
pub use gecko::media_queries::{Device, MediaFeatureExpression};
|
pub use gecko::media_queries::Device;
|
||||||
|
|
|
@ -290,14 +290,25 @@ class Longhand(object):
|
||||||
"AlignContent",
|
"AlignContent",
|
||||||
"AlignItems",
|
"AlignItems",
|
||||||
"AlignSelf",
|
"AlignSelf",
|
||||||
|
"Appearance",
|
||||||
"BackgroundRepeat",
|
"BackgroundRepeat",
|
||||||
"BorderImageRepeat",
|
"BorderImageRepeat",
|
||||||
"BorderStyle",
|
"BorderStyle",
|
||||||
|
"Clear",
|
||||||
"ColumnCount",
|
"ColumnCount",
|
||||||
"Contain",
|
"Contain",
|
||||||
|
"Display",
|
||||||
|
"Float",
|
||||||
|
"FontSizeAdjust",
|
||||||
|
"FontStretch",
|
||||||
|
"FontStyle",
|
||||||
"FontStyleAdjust",
|
"FontStyleAdjust",
|
||||||
"FontSynthesis",
|
"FontSynthesis",
|
||||||
|
"FontVariantEastAsian",
|
||||||
|
"FontVariantLigatures",
|
||||||
|
"FontVariantNumeric",
|
||||||
"FontWeight",
|
"FontWeight",
|
||||||
|
"GreaterThanOrEqualToOneNumber",
|
||||||
"GridAutoFlow",
|
"GridAutoFlow",
|
||||||
"InitialLetter",
|
"InitialLetter",
|
||||||
"Integer",
|
"Integer",
|
||||||
|
@ -311,17 +322,24 @@ class Longhand(object):
|
||||||
"NonNegativeNumber",
|
"NonNegativeNumber",
|
||||||
"Opacity",
|
"Opacity",
|
||||||
"OutlineStyle",
|
"OutlineStyle",
|
||||||
|
"OverflowClipBox",
|
||||||
"OverscrollBehavior",
|
"OverscrollBehavior",
|
||||||
"Percentage",
|
"Percentage",
|
||||||
|
"Resize",
|
||||||
|
"SVGOpacity",
|
||||||
"SVGPaintOrder",
|
"SVGPaintOrder",
|
||||||
"ScrollSnapType",
|
"ScrollSnapType",
|
||||||
|
"TextAlign",
|
||||||
"TextDecorationLine",
|
"TextDecorationLine",
|
||||||
|
"TextEmphasisPosition",
|
||||||
"TouchAction",
|
"TouchAction",
|
||||||
"TransformStyle",
|
"TransformStyle",
|
||||||
"XSpan",
|
"XSpan",
|
||||||
"XTextZoom",
|
"XTextZoom",
|
||||||
"ZIndex",
|
"ZIndex",
|
||||||
}
|
}
|
||||||
|
if self.name == "overflow-y":
|
||||||
|
return True
|
||||||
return bool(self.keyword)
|
return bool(self.keyword)
|
||||||
|
|
||||||
def animated_type(self):
|
def animated_type(self):
|
||||||
|
|
|
@ -2124,7 +2124,7 @@ fn static_assert() {
|
||||||
};
|
};
|
||||||
|
|
||||||
for (servo, gecko) in v.0.areas.into_iter().zip(refptr.mNamedAreas.iter_mut()) {
|
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.mColumnStart = servo.columns.start;
|
||||||
gecko.mColumnEnd = servo.columns.end;
|
gecko.mColumnEnd = servo.columns.end;
|
||||||
gecko.mRowStart = servo.rows.start;
|
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()) {
|
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())
|
self.gecko.mGridTemplateAreas.set_move(refptr.get())
|
||||||
|
@ -3106,6 +3106,9 @@ fn static_assert() {
|
||||||
) %>
|
) %>
|
||||||
${impl_keyword('clear', 'mBreakType', clear_keyword)}
|
${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"] %>
|
<% overflow_x = data.longhands_by_name["overflow-x"] %>
|
||||||
pub fn set_overflow_y(&mut self, v: longhands::overflow_y::computed_value::T) {
|
pub fn set_overflow_y(&mut self, v: longhands::overflow_y::computed_value::T) {
|
||||||
use properties::longhands::overflow_x::computed_value::T as BaseType;
|
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()) {
|
for (servo, gecko) in other.0.into_iter().zip(refptr.mQuotePairs.iter_mut()) {
|
||||||
gecko.first.assign_utf8(&servo.0);
|
gecko.first.assign_str(&servo.0);
|
||||||
gecko.second.assign_utf8(&servo.1);
|
gecko.second.assign_str(&servo.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.gecko.mQuotes.set_move(refptr.get())
|
self.gecko.mQuotes.set_move(refptr.get())
|
||||||
|
@ -4725,7 +4728,7 @@ fn static_assert() {
|
||||||
(structs::NS_STYLE_TEXT_EMPHASIS_STYLE_STRING, &**s)
|
(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;
|
self.gecko.mTextEmphasisStyle = te as u8;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4826,7 +4829,7 @@ fn static_assert() {
|
||||||
TextOverflowSide::Clip => structs::NS_STYLE_TEXT_OVERFLOW_CLIP,
|
TextOverflowSide::Clip => structs::NS_STYLE_TEXT_OVERFLOW_CLIP,
|
||||||
TextOverflowSide::Ellipsis => structs::NS_STYLE_TEXT_OVERFLOW_ELLIPSIS,
|
TextOverflowSide::Ellipsis => structs::NS_STYLE_TEXT_OVERFLOW_ELLIPSIS,
|
||||||
TextOverflowSide::String(ref s) => {
|
TextOverflowSide::String(ref s) => {
|
||||||
side.mString.assign_utf8(s);
|
side.mString.assign_str(s);
|
||||||
structs::NS_STYLE_TEXT_OVERFLOW_STRING
|
structs::NS_STYLE_TEXT_OVERFLOW_STRING
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -4960,7 +4963,7 @@ fn static_assert() {
|
||||||
ShapeSource::ImageOrUrl(image) => {
|
ShapeSource::ImageOrUrl(image) => {
|
||||||
unsafe {
|
unsafe {
|
||||||
bindings::Gecko_NewShapeImage(${ident});
|
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);
|
style_image.set(image);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4980,7 +4983,7 @@ fn static_assert() {
|
||||||
// Create StyleBasicShape in StyleShapeSource. mReferenceBox and mType
|
// Create StyleBasicShape in StyleShapeSource. mReferenceBox and mType
|
||||||
// will be set manually later.
|
// will be set manually later.
|
||||||
Gecko_NewBasicShape(${ident}, basic_shape_type);
|
Gecko_NewBasicShape(${ident}, basic_shape_type);
|
||||||
&mut *${ident}.mBasicShape.mPtr
|
&mut *${ident}.__bindgen_anon_1.mBasicShape.as_mut().mPtr
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
match servo_shape {
|
match servo_shape {
|
||||||
|
@ -5458,7 +5461,7 @@ clip-path
|
||||||
};
|
};
|
||||||
counter_func.mIdent.assign(name.0.as_slice());
|
counter_func.mIdent.assign(name.0.as_slice());
|
||||||
if content_type == StyleContentType::Counters {
|
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);
|
style.to_gecko_value(&mut counter_func.mCounterStyle, device);
|
||||||
}
|
}
|
||||||
|
|
|
@ -422,17 +422,21 @@ ${helpers.single_keyword("page-break-inside",
|
||||||
|
|
||||||
// CSS Basic User Interface Module Level 3
|
// CSS Basic User Interface Module Level 3
|
||||||
// http://dev.w3.org/csswg/css-ui
|
// 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
|
// This is APPLIES_TO_PLACEHOLDER so we can override, in the UA sheet, the
|
||||||
// 'resize' property we'd inherit from textarea otherwise. Basically, just
|
// 'resize' property we'd inherit from textarea otherwise. Basically, just
|
||||||
// makes the UA rules easier to write.
|
// makes the UA rules easier to write.
|
||||||
${helpers.single_keyword("resize",
|
${helpers.predefined_type(
|
||||||
"none both horizontal vertical",
|
"resize",
|
||||||
products="gecko",
|
"Resize",
|
||||||
spec="https://drafts.csswg.org/css-ui/#propdef-resize",
|
"computed::Resize::None",
|
||||||
flags="APPLIES_TO_PLACEHOLDER",
|
products="gecko",
|
||||||
animation_value_type="discrete")}
|
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(
|
${helpers.predefined_type(
|
||||||
"perspective",
|
"perspective",
|
||||||
|
@ -448,7 +452,7 @@ ${helpers.predefined_type(
|
||||||
|
|
||||||
${helpers.predefined_type(
|
${helpers.predefined_type(
|
||||||
"perspective-origin",
|
"perspective-origin",
|
||||||
"position::Position",
|
"Position",
|
||||||
"computed::position::Position::center()",
|
"computed::position::Position::center()",
|
||||||
boxed=True,
|
boxed=True,
|
||||||
extra_prefixes=transform_extra_prefixes,
|
extra_prefixes=transform_extra_prefixes,
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
<%namespace name="helpers" file="/helpers.mako.rs" />
|
<%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",
|
${helpers.predefined_type("cursor",
|
||||||
"Cursor",
|
"Cursor",
|
||||||
|
|
|
@ -16,6 +16,17 @@ ${helpers.single_keyword("ime-mode", "auto normal active disabled inactive",
|
||||||
animation_value_type="discrete",
|
animation_value_type="discrete",
|
||||||
spec="https://drafts.csswg.org/css-ui/#input-method-editor")}
|
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" +
|
${helpers.single_keyword("-moz-user-select", "auto text none all element elements" +
|
||||||
" toggle tri-state -moz-all -moz-text",
|
" toggle tri-state -moz-all -moz-text",
|
||||||
products="gecko",
|
products="gecko",
|
||||||
|
|
|
@ -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 {
|
match *self {
|
||||||
${" |\n".join("{}(..)".format(v["name"]) for v in copy)} => {
|
${" |\n".join("{}(..)".format(v["name"]) for v in copy)} => {
|
||||||
unsafe { debug_unreachable!() }
|
unsafe { debug_unreachable!() }
|
||||||
|
@ -588,16 +603,17 @@ impl NonCustomPropertyId {
|
||||||
|
|
||||||
/// See PropertyId::collect_property_completion_keywords.
|
/// See PropertyId::collect_property_completion_keywords.
|
||||||
fn collect_property_completion_keywords(&self, f: KeywordsCollectFn) {
|
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)}] = [
|
${len(data.longhands) + len(data.shorthands)}] = [
|
||||||
% for prop in data.longhands:
|
% for prop in data.longhands:
|
||||||
&<${prop.specified_type()} as SpecifiedValueInfo>::collect_completion_keywords,
|
<${prop.specified_type()} as SpecifiedValueInfo>::collect_completion_keywords,
|
||||||
% endfor
|
% endfor
|
||||||
% for prop in data.shorthands:
|
% for prop in data.shorthands:
|
||||||
% if prop.name == "all":
|
% 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:
|
% else:
|
||||||
&<shorthands::${prop.ident}::Longhands as SpecifiedValueInfo>::
|
<shorthands::${prop.ident}::Longhands as SpecifiedValueInfo>::
|
||||||
collect_completion_keywords,
|
collect_completion_keywords,
|
||||||
% endif
|
% endif
|
||||||
% endfor
|
% endfor
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
<%helpers:shorthand
|
<%helpers:shorthand
|
||||||
name="overflow"
|
name="overflow"
|
||||||
flags="SHORTHAND_IN_GETCS"
|
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"
|
spec="https://drafts.csswg.org/css-overflow/#propdef-overflow"
|
||||||
>
|
>
|
||||||
use properties::longhands::overflow_x::parse as parse_overflow;
|
use properties::longhands::overflow_x::parse as parse_overflow;
|
||||||
|
@ -20,35 +20,41 @@
|
||||||
input: &mut Parser<'i, 't>,
|
input: &mut Parser<'i, 't>,
|
||||||
) -> Result<Longhands, ParseError<'i>> {
|
) -> Result<Longhands, ParseError<'i>> {
|
||||||
% if product == "gecko":
|
% if product == "gecko":
|
||||||
let moz_kw_found = input.try(|input| {
|
use gecko_bindings::structs;
|
||||||
try_match_ident_ignore_ascii_case! { input,
|
let moz_kw_enabled = unsafe {
|
||||||
"-moz-scrollbars-horizontal" => {
|
structs::StaticPrefs_sVarCache_layout_css_overflow_moz_scrollbars_enabled
|
||||||
Ok(expanded! {
|
};
|
||||||
overflow_x: SpecifiedValue::Scroll,
|
if moz_kw_enabled {
|
||||||
overflow_y: SpecifiedValue::Hidden,
|
let moz_kw_found = input.try(|input| {
|
||||||
})
|
try_match_ident_ignore_ascii_case! { input,
|
||||||
}
|
"-moz-scrollbars-horizontal" => {
|
||||||
"-moz-scrollbars-vertical" => {
|
Ok(expanded! {
|
||||||
Ok(expanded! {
|
overflow_x: SpecifiedValue::Scroll,
|
||||||
overflow_x: SpecifiedValue::Hidden,
|
overflow_y: SpecifiedValue::Hidden,
|
||||||
overflow_y: SpecifiedValue::Scroll,
|
})
|
||||||
})
|
}
|
||||||
}
|
"-moz-scrollbars-vertical" => {
|
||||||
"-moz-scrollbars-none" => {
|
Ok(expanded! {
|
||||||
Ok(expanded! {
|
overflow_x: SpecifiedValue::Hidden,
|
||||||
overflow_x: SpecifiedValue::Hidden,
|
overflow_y: SpecifiedValue::Scroll,
|
||||||
overflow_y: SpecifiedValue::Hidden,
|
})
|
||||||
})
|
}
|
||||||
|
"-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
|
% endif
|
||||||
let overflow_x = parse_overflow(context, input)?;
|
let overflow_y = parse_overflow(context, input)?;
|
||||||
let overflow_y =
|
let overflow_x =
|
||||||
input.try(|i| parse_overflow(context, i)).unwrap_or(overflow_x);
|
input.try(|i| parse_overflow(context, i)).unwrap_or(overflow_y);
|
||||||
Ok(expanded! {
|
Ok(expanded! {
|
||||||
overflow_x: overflow_x,
|
overflow_x: overflow_x,
|
||||||
overflow_y: overflow_y,
|
overflow_y: overflow_y,
|
||||||
|
@ -57,10 +63,10 @@
|
||||||
|
|
||||||
impl<'a> ToCss for LonghandsToSerialize<'a> {
|
impl<'a> ToCss for LonghandsToSerialize<'a> {
|
||||||
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: fmt::Write {
|
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 {
|
if self.overflow_x != self.overflow_y {
|
||||||
dest.write_char(' ')?;
|
dest.write_char(' ')?;
|
||||||
self.overflow_y.to_css(dest)?;
|
self.overflow_x.to_css(dest)?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,19 +5,18 @@
|
||||||
//! Servo's media-query device and expression representation.
|
//! Servo's media-query device and expression representation.
|
||||||
|
|
||||||
use app_units::Au;
|
use app_units::Au;
|
||||||
use context::QuirksMode;
|
use cssparser::RGBA;
|
||||||
use cssparser::{Parser, RGBA};
|
|
||||||
use euclid::{Size2D, TypedScale, TypedSize2D};
|
use euclid::{Size2D, TypedScale, TypedSize2D};
|
||||||
use media_queries::MediaType;
|
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 properties::ComputedValues;
|
||||||
use selectors::parser::SelectorParseErrorKind;
|
|
||||||
use std::fmt::{self, Write};
|
|
||||||
use std::sync::atomic::{AtomicBool, AtomicIsize, Ordering};
|
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 style_traits::viewport::ViewportConstraints;
|
||||||
use values::{specified, KeyframesName};
|
use values::KeyframesName;
|
||||||
use values::computed::{self, ToComputedValue};
|
use values::computed::CSSPixelLength;
|
||||||
use values::computed::font::FontSize;
|
use values::computed::font::FontSize;
|
||||||
|
|
||||||
/// A device is a structure that represents the current media a given document
|
/// 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.
|
/// https://drafts.csswg.org/mediaqueries-4/#width
|
||||||
///
|
fn eval_width(
|
||||||
/// Only `pub` for unit testing, please don't use it directly!
|
device: &Device,
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
value: Option<CSSPixelLength>,
|
||||||
#[cfg_attr(feature = "servo", derive(MallocSizeOf))]
|
range_or_operator: Option<RangeOrOperator>,
|
||||||
pub enum ExpressionKind {
|
) -> bool {
|
||||||
/// <http://dev.w3.org/csswg/mediaqueries-3/#width>
|
RangeOrOperator::evaluate(
|
||||||
Width(Range<specified::Length>),
|
range_or_operator,
|
||||||
|
value.map(Au::from),
|
||||||
|
device.au_viewport_size().width,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A single expression a per:
|
#[derive(Clone, Copy, Debug, FromPrimitive, Parse, ToCss)]
|
||||||
///
|
#[repr(u8)]
|
||||||
/// <http://dev.w3.org/csswg/mediaqueries-3/#media1>
|
enum Scan {
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
Progressive,
|
||||||
#[cfg_attr(feature = "servo", derive(MallocSizeOf))]
|
Interlace,
|
||||||
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,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ToCss for MediaFeatureExpression {
|
/// https://drafts.csswg.org/mediaqueries-4/#scan
|
||||||
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
|
fn eval_scan(_: &Device, _: Option<Scan>) -> bool {
|
||||||
where
|
// Since we doesn't support the 'tv' media type, the 'scan' feature never
|
||||||
W: Write,
|
// matches.
|
||||||
{
|
false
|
||||||
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(')')
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An enumeration that represents a ranged value.
|
lazy_static! {
|
||||||
///
|
/// A list with all the media features that Servo supports.
|
||||||
/// Only public for testing, implementation details of `MediaFeatureExpression`
|
pub static ref MEDIA_FEATURES: [MediaFeatureDescription; 2] = [
|
||||||
/// may change for Stylo.
|
feature!(
|
||||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
atom!("width"),
|
||||||
#[cfg_attr(feature = "servo", derive(MallocSizeOf))]
|
AllowsRanges::Yes,
|
||||||
pub enum Range<T> {
|
Evaluator::Length(eval_width),
|
||||||
/// At least the inner value.
|
ParsingRequirements::empty(),
|
||||||
Min(T),
|
),
|
||||||
/// At most the inner value.
|
feature!(
|
||||||
Max(T),
|
atom!("scan"),
|
||||||
/// Exactly the inner value.
|
AllowsRanges::No,
|
||||||
Eq(T),
|
keyword_evaluator!(eval_scan, Scan),
|
||||||
}
|
ParsingRequirements::empty(),
|
||||||
|
),
|
||||||
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))),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1131,11 +1131,6 @@ impl Stylist {
|
||||||
|
|
||||||
let rule_hash_target = element.rule_hash_target();
|
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_user_rules = rule_hash_target.matches_user_and_author_rules();
|
||||||
let matches_author_rules =
|
let matches_author_rules =
|
||||||
matches_user_rules && self.author_styles_enabled == AuthorStylesEnabled::Yes;
|
matches_user_rules && self.author_styles_enabled == AuthorStylesEnabled::Yes;
|
||||||
|
|
|
@ -9,6 +9,7 @@ use values::computed::length::{LengthOrPercentage, NonNegativeLength};
|
||||||
use values::generics::box_::AnimationIterationCount as GenericAnimationIterationCount;
|
use values::generics::box_::AnimationIterationCount as GenericAnimationIterationCount;
|
||||||
use values::generics::box_::Perspective as GenericPerspective;
|
use values::generics::box_::Perspective as GenericPerspective;
|
||||||
use values::generics::box_::VerticalAlign as GenericVerticalAlign;
|
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_::{AnimationName, Appearance, Contain, Display, OverflowClipBox};
|
||||||
pub use values::specified::box_::{Clear as SpecifiedClear, Float as SpecifiedFloat};
|
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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -44,7 +44,7 @@ pub use self::font::{FontFeatureSettings, FontVariantLigatures, FontVariantNumer
|
||||||
pub use self::font::{MozScriptLevel, MozScriptMinSize, MozScriptSizeMultiplier, XLang, XTextZoom};
|
pub use self::font::{MozScriptLevel, MozScriptMinSize, MozScriptSizeMultiplier, XLang, XTextZoom};
|
||||||
pub use self::box_::{AnimationIterationCount, AnimationName, Contain, Display, TransitionProperty};
|
pub use self::box_::{AnimationIterationCount, AnimationName, Contain, Display, TransitionProperty};
|
||||||
pub use self::box_::{Appearance, Clear, Float};
|
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::box_::{ScrollSnapType, TouchAction, VerticalAlign, WillChange};
|
||||||
pub use self::color::{Color, ColorPropertyValue, RGBAColor};
|
pub use self::color::{Color, ColorPropertyValue, RGBAColor};
|
||||||
pub use self::column::ColumnCount;
|
pub use self::column::ColumnCount;
|
||||||
|
|
|
@ -21,6 +21,12 @@ impl Resolution {
|
||||||
pub fn dppx(&self) -> CSSFloat {
|
pub fn dppx(&self) -> CSSFloat {
|
||||||
self.0
|
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 {
|
impl ToComputedValue for specified::Resolution {
|
||||||
|
|
|
@ -884,6 +884,21 @@ pub enum Clear {
|
||||||
InlineEnd
|
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.
|
/// The value for the `appearance` property.
|
||||||
///
|
///
|
||||||
/// https://developer.mozilla.org/en-US/docs/Web/CSS/-moz-appearance
|
/// https://developer.mozilla.org/en-US/docs/Web/CSS/-moz-appearance
|
||||||
|
|
|
@ -1359,7 +1359,7 @@ impl VariantEastAsian {
|
||||||
impl_gecko_keyword_conversions!(VariantEastAsian, u16);
|
impl_gecko_keyword_conversions!(VariantEastAsian, u16);
|
||||||
|
|
||||||
#[cfg_attr(feature = "gecko", derive(MallocSizeOf))]
|
#[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.
|
/// Allows control of glyph substitution and sizing in East Asian text.
|
||||||
pub enum FontVariantEastAsian {
|
pub enum FontVariantEastAsian {
|
||||||
/// Value variant with `variant-east-asian`
|
/// Value variant with `variant-east-asian`
|
||||||
|
@ -1570,7 +1570,7 @@ impl VariantLigatures {
|
||||||
impl_gecko_keyword_conversions!(VariantLigatures, u16);
|
impl_gecko_keyword_conversions!(VariantLigatures, u16);
|
||||||
|
|
||||||
#[cfg_attr(feature = "gecko", derive(MallocSizeOf))]
|
#[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
|
/// Ligatures and contextual forms are ways of combining glyphs
|
||||||
/// to produce more harmonized forms
|
/// to produce more harmonized forms
|
||||||
pub enum FontVariantLigatures {
|
pub enum FontVariantLigatures {
|
||||||
|
@ -1786,7 +1786,7 @@ impl VariantNumeric {
|
||||||
impl_gecko_keyword_conversions!(VariantNumeric, u8);
|
impl_gecko_keyword_conversions!(VariantNumeric, u8);
|
||||||
|
|
||||||
#[cfg_attr(feature = "gecko", derive(MallocSizeOf))]
|
#[cfg_attr(feature = "gecko", derive(MallocSizeOf))]
|
||||||
#[derive(Clone, Debug, PartialEq, SpecifiedValueInfo, ToCss)]
|
#[derive(Clone, Copy, Debug, PartialEq, SpecifiedValueInfo, ToCss)]
|
||||||
/// Specifies control over numerical forms.
|
/// Specifies control over numerical forms.
|
||||||
pub enum FontVariantNumeric {
|
pub enum FontVariantNumeric {
|
||||||
/// Value variant with `variant-numeric`
|
/// Value variant with `variant-numeric`
|
||||||
|
|
|
@ -421,46 +421,34 @@ impl NoCalcLength {
|
||||||
value: CSSFloat,
|
value: CSSFloat,
|
||||||
unit: &str,
|
unit: &str,
|
||||||
) -> Result<Self, ()> {
|
) -> Result<Self, ()> {
|
||||||
match_ignore_ascii_case! { unit,
|
Ok(match_ignore_ascii_case! { unit,
|
||||||
"px" => Ok(NoCalcLength::Absolute(AbsoluteLength::Px(value))),
|
"px" => NoCalcLength::Absolute(AbsoluteLength::Px(value)),
|
||||||
"in" => Ok(NoCalcLength::Absolute(AbsoluteLength::In(value))),
|
"in" => NoCalcLength::Absolute(AbsoluteLength::In(value)),
|
||||||
"cm" => Ok(NoCalcLength::Absolute(AbsoluteLength::Cm(value))),
|
"cm" => NoCalcLength::Absolute(AbsoluteLength::Cm(value)),
|
||||||
"mm" => Ok(NoCalcLength::Absolute(AbsoluteLength::Mm(value))),
|
"mm" => NoCalcLength::Absolute(AbsoluteLength::Mm(value)),
|
||||||
"q" => Ok(NoCalcLength::Absolute(AbsoluteLength::Q(value))),
|
"q" => NoCalcLength::Absolute(AbsoluteLength::Q(value)),
|
||||||
"pt" => Ok(NoCalcLength::Absolute(AbsoluteLength::Pt(value))),
|
"pt" => NoCalcLength::Absolute(AbsoluteLength::Pt(value)),
|
||||||
"pc" => Ok(NoCalcLength::Absolute(AbsoluteLength::Pc(value))),
|
"pc" => NoCalcLength::Absolute(AbsoluteLength::Pc(value)),
|
||||||
// font-relative
|
// font-relative
|
||||||
"em" => Ok(NoCalcLength::FontRelative(FontRelativeLength::Em(value))),
|
"em" => NoCalcLength::FontRelative(FontRelativeLength::Em(value)),
|
||||||
"ex" => Ok(NoCalcLength::FontRelative(FontRelativeLength::Ex(value))),
|
"ex" => NoCalcLength::FontRelative(FontRelativeLength::Ex(value)),
|
||||||
"ch" => Ok(NoCalcLength::FontRelative(FontRelativeLength::Ch(value))),
|
"ch" => NoCalcLength::FontRelative(FontRelativeLength::Ch(value)),
|
||||||
"rem" => Ok(NoCalcLength::FontRelative(FontRelativeLength::Rem(value))),
|
"rem" => NoCalcLength::FontRelative(FontRelativeLength::Rem(value)),
|
||||||
// viewport percentages
|
// viewport percentages
|
||||||
"vw" => {
|
"vw" if !context.in_page_rule() => {
|
||||||
if context.in_page_rule() {
|
NoCalcLength::ViewportPercentage(ViewportPercentageLength::Vw(value))
|
||||||
return Err(())
|
}
|
||||||
}
|
"vh" if !context.in_page_rule() => {
|
||||||
Ok(NoCalcLength::ViewportPercentage(ViewportPercentageLength::Vw(value)))
|
NoCalcLength::ViewportPercentage(ViewportPercentageLength::Vh(value))
|
||||||
},
|
}
|
||||||
"vh" => {
|
"vmin" if !context.in_page_rule() => {
|
||||||
if context.in_page_rule() {
|
NoCalcLength::ViewportPercentage(ViewportPercentageLength::Vmin(value))
|
||||||
return Err(())
|
}
|
||||||
}
|
"vmax" if !context.in_page_rule() => {
|
||||||
Ok(NoCalcLength::ViewportPercentage(ViewportPercentageLength::Vh(value)))
|
NoCalcLength::ViewportPercentage(ViewportPercentageLength::Vmax(value))
|
||||||
},
|
}
|
||||||
"vmin" => {
|
_ => return Err(())
|
||||||
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(())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
|
|
@ -39,7 +39,7 @@ pub use self::font::{FontFeatureSettings, FontVariantLigatures, FontVariantNumer
|
||||||
pub use self::font::{MozScriptLevel, MozScriptMinSize, MozScriptSizeMultiplier, XLang, XTextZoom};
|
pub use self::font::{MozScriptLevel, MozScriptMinSize, MozScriptSizeMultiplier, XLang, XTextZoom};
|
||||||
pub use self::box_::{AnimationIterationCount, AnimationName, Contain, Display};
|
pub use self::box_::{AnimationIterationCount, AnimationName, Contain, Display};
|
||||||
pub use self::box_::{Appearance, Clear, Float};
|
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::box_::{ScrollSnapType, TouchAction, TransitionProperty, VerticalAlign, WillChange};
|
||||||
pub use self::color::{Color, ColorPropertyValue, RGBAColor};
|
pub use self::color::{Color, ColorPropertyValue, RGBAColor};
|
||||||
pub use self::counters::{Content, ContentItem, CounterIncrement, CounterReset};
|
pub use self::counters::{Content, ContentItem, CounterIncrement, CounterReset};
|
||||||
|
|
|
@ -744,7 +744,7 @@ pub enum TextEmphasisVerticalWritingModeValue {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Specified value of `text-emphasis-position` property.
|
/// Specified value of `text-emphasis-position` property.
|
||||||
#[derive(Clone, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo,
|
#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo,
|
||||||
ToComputedValue, ToCss)]
|
ToComputedValue, ToCss)]
|
||||||
pub struct TextEmphasisPosition(
|
pub struct TextEmphasisPosition(
|
||||||
pub TextEmphasisHorizontalWritingModeValue,
|
pub TextEmphasisHorizontalWritingModeValue,
|
||||||
|
|
|
@ -1,4 +0,0 @@
|
||||||
[overflow-serialization.html]
|
|
||||||
[CSSOM - Overflow shorthand serialization]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
|
@ -29,6 +29,3 @@
|
||||||
[The serialization of list-style-type: circle; list-style-position: inside; list-style-image: initial; should be canonical.]
|
[The serialization of list-style-type: circle; list-style-position: inside; list-style-image: initial; should be canonical.]
|
||||||
expected: FAIL
|
expected: FAIL
|
||||||
|
|
||||||
[The serialization of overflow-x: scroll; overflow-y: hidden; should be canonical.]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue