mirror of
https://github.com/servo/servo.git
synced 2025-06-06 16:45:39 +00: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
|
@ -66,6 +66,7 @@ reftest-wait
|
|||
reset
|
||||
right
|
||||
sans-serif
|
||||
scan
|
||||
screen
|
||||
scroll-position
|
||||
search
|
||||
|
@ -86,3 +87,4 @@ url
|
|||
waiting
|
||||
webglcontextcreationerror
|
||||
week
|
||||
width
|
||||
|
|
|
@ -24,7 +24,6 @@ bitflags = "1.0"
|
|||
matches = "0.1"
|
||||
cssparser = "0.24.0"
|
||||
log = "0.4"
|
||||
fnv = "1.0"
|
||||
fxhash = "0.2"
|
||||
phf = "0.7.18"
|
||||
precomputed-hash = "0.1"
|
||||
|
|
|
@ -5,9 +5,7 @@
|
|||
//! Counting and non-counting Bloom filters tuned for use as ancestor filters
|
||||
//! for selector matching.
|
||||
|
||||
use fnv::FnvHasher;
|
||||
use std::fmt::{self, Debug};
|
||||
use std::hash::{Hash, Hasher};
|
||||
|
||||
// The top 8 bits of the 32-bit hash value are not used by the bloom filter.
|
||||
// Consumers may rely on this to pack hashes more efficiently.
|
||||
|
@ -108,43 +106,27 @@ where
|
|||
unreachable!()
|
||||
}
|
||||
|
||||
/// Inserts an item with a particular hash into the bloom filter.
|
||||
#[inline]
|
||||
pub fn insert_hash(&mut self, hash: u32) {
|
||||
self.storage.adjust_first_slot(hash, true);
|
||||
self.storage.adjust_second_slot(hash, true);
|
||||
}
|
||||
|
||||
/// Inserts an item into the bloom filter.
|
||||
#[inline]
|
||||
pub fn insert<T: Hash>(&mut self, elem: &T) {
|
||||
self.insert_hash(hash(elem))
|
||||
}
|
||||
|
||||
/// Removes an item with a particular hash from the bloom filter.
|
||||
#[inline]
|
||||
pub fn remove_hash(&mut self, hash: u32) {
|
||||
self.storage.adjust_first_slot(hash, false);
|
||||
self.storage.adjust_second_slot(hash, false);
|
||||
}
|
||||
|
||||
/// Removes an item from the bloom filter.
|
||||
#[inline]
|
||||
pub fn remove<T: Hash>(&mut self, elem: &T) {
|
||||
self.remove_hash(hash(elem))
|
||||
}
|
||||
|
||||
/// Check whether the filter might contain an item with the given hash.
|
||||
/// This can sometimes return true even if the item is not in the filter,
|
||||
/// but will never return false for items that are actually in the filter.
|
||||
#[inline]
|
||||
pub fn might_contain_hash(&self, hash: u32) -> bool {
|
||||
!self.storage.first_slot_is_empty(hash) && !self.storage.second_slot_is_empty(hash)
|
||||
}
|
||||
|
||||
/// Check whether the filter might contain an item. This can
|
||||
/// sometimes return true even if the item is not in the filter,
|
||||
/// but will never return false for items that are actually in the
|
||||
/// filter.
|
||||
#[inline]
|
||||
pub fn might_contain<T: Hash>(&self, elem: &T) -> bool {
|
||||
self.might_contain_hash(hash(elem))
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> Debug for CountingBloomFilter<S>
|
||||
|
@ -296,16 +278,6 @@ impl Clone for BloomStorageBool {
|
|||
}
|
||||
}
|
||||
|
||||
fn hash<T: Hash>(elem: &T) -> u32 {
|
||||
// We generally use FxHasher in Stylo because it's faster than FnvHasher,
|
||||
// but the increased collision rate has outsized effect on the bloom
|
||||
// filter, so we use FnvHasher instead here.
|
||||
let mut hasher = FnvHasher::default();
|
||||
elem.hash(&mut hasher);
|
||||
let hash: u64 = hasher.finish();
|
||||
(hash >> 32) as u32 ^ (hash as u32)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn hash1(hash: u32) -> u32 {
|
||||
hash & KEY_MASK
|
||||
|
@ -318,8 +290,18 @@ fn hash2(hash: u32) -> u32 {
|
|||
|
||||
#[test]
|
||||
fn create_and_insert_some_stuff() {
|
||||
use fxhash::FxHasher;
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::mem::transmute;
|
||||
|
||||
fn hash_as_str(i: usize) -> u32 {
|
||||
let mut hasher = FxHasher::default();
|
||||
let s = i.to_string();
|
||||
s.hash(&mut hasher);
|
||||
let hash: u64 = hasher.finish();
|
||||
(hash >> 32) as u32 ^ (hash as u32)
|
||||
}
|
||||
|
||||
let mut bf = BloomFilter::new();
|
||||
|
||||
// Statically assert that ARRAY_SIZE is a multiple of 8, which
|
||||
|
@ -329,33 +311,34 @@ fn create_and_insert_some_stuff() {
|
|||
}
|
||||
|
||||
for i in 0_usize..1000 {
|
||||
bf.insert(&i);
|
||||
bf.insert_hash(hash_as_str(i));
|
||||
}
|
||||
|
||||
for i in 0_usize..1000 {
|
||||
assert!(bf.might_contain(&i));
|
||||
assert!(bf.might_contain_hash(hash_as_str(i)));
|
||||
}
|
||||
|
||||
let false_positives = (1001_usize..2000).filter(|i| bf.might_contain(i)).count();
|
||||
let false_positives =
|
||||
(1001_usize..2000).filter(|i| bf.might_contain_hash(hash_as_str(*i))).count();
|
||||
|
||||
assert!(false_positives < 160, "{} is not < 160", false_positives); // 16%.
|
||||
assert!(false_positives < 190, "{} is not < 190", false_positives); // 19%.
|
||||
|
||||
for i in 0_usize..100 {
|
||||
bf.remove(&i);
|
||||
bf.remove_hash(hash_as_str(i));
|
||||
}
|
||||
|
||||
for i in 100_usize..1000 {
|
||||
assert!(bf.might_contain(&i));
|
||||
assert!(bf.might_contain_hash(hash_as_str(i)));
|
||||
}
|
||||
|
||||
let false_positives = (0_usize..100).filter(|i| bf.might_contain(i)).count();
|
||||
let false_positives = (0_usize..100).filter(|i| bf.might_contain_hash(hash_as_str(*i))).count();
|
||||
|
||||
assert!(false_positives < 20, "{} is not < 20", false_positives); // 20%.
|
||||
|
||||
bf.clear();
|
||||
|
||||
for i in 0_usize..2000 {
|
||||
assert!(!bf.might_contain(&i));
|
||||
assert!(!bf.might_contain_hash(hash_as_str(i)));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -9,7 +9,6 @@
|
|||
extern crate bitflags;
|
||||
#[macro_use]
|
||||
extern crate cssparser;
|
||||
extern crate fnv;
|
||||
extern crate fxhash;
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
|
|
|
@ -47,8 +47,9 @@ malloc_size_of = { path = "../malloc_size_of" }
|
|||
malloc_size_of_derive = { path = "../malloc_size_of_derive" }
|
||||
matches = "0.1"
|
||||
num_cpus = {version = "1.1.0", optional = true}
|
||||
num-integer = "0.1.32"
|
||||
num-integer = "0.1"
|
||||
num-traits = "0.2"
|
||||
num-derive = "0.2"
|
||||
new-ordered-float = "1.0"
|
||||
owning_ref = "0.3.3"
|
||||
parking_lot = "0.6"
|
||||
|
@ -72,9 +73,6 @@ unicode-bidi = "0.3"
|
|||
unicode-segmentation = "1.0"
|
||||
void = "1.0.2"
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
kernel32-sys = "0.2"
|
||||
|
||||
[build-dependencies]
|
||||
lazy_static = "1"
|
||||
log = "0.4"
|
||||
|
|
|
@ -22,5 +22,5 @@ derive_helper_methods = true
|
|||
|
||||
[export]
|
||||
prefix = "Style"
|
||||
include = ["StyleDisplay", "StyleAppearance"]
|
||||
include = ["StyleDisplay", "StyleAppearance", "StyleDisplayMode"]
|
||||
item_types = ["enums"]
|
||||
|
|
|
@ -659,7 +659,7 @@ pub mod basic_shape {
|
|||
StyleShapeSourceType::None => Some(ShapeSource::None),
|
||||
StyleShapeSourceType::Box => Some(ShapeSource::Box(self.mReferenceBox.into())),
|
||||
StyleShapeSourceType::Shape => {
|
||||
let other_shape = unsafe { &*self.mBasicShape.mPtr };
|
||||
let other_shape = unsafe { &*self.__bindgen_anon_1.mBasicShape.as_ref().mPtr };
|
||||
let shape = other_shape.into();
|
||||
let reference_box = if self.mReferenceBox == StyleGeometryBox::NoBox {
|
||||
None
|
||||
|
@ -677,7 +677,7 @@ pub mod basic_shape {
|
|||
fn from(other: &'a StyleShapeSource) -> Self {
|
||||
match other.mType {
|
||||
StyleShapeSourceType::URL => unsafe {
|
||||
let shape_image = &*other.mShapeImage.mPtr;
|
||||
let shape_image = &*other.__bindgen_anon_1.mShapeImage.as_ref().mPtr;
|
||||
let other_url = RefPtr::new(*shape_image.__bindgen_anon_1.mURLValue.as_ref());
|
||||
let url = ComputedUrl::from_url_value(other_url);
|
||||
ShapeSource::ImageOrUrl(url)
|
||||
|
@ -699,7 +699,7 @@ pub mod basic_shape {
|
|||
unreachable!("FloatAreaShape doesn't support URL!");
|
||||
},
|
||||
StyleShapeSourceType::Image => unsafe {
|
||||
let shape_image = &*other.mShapeImage.mPtr;
|
||||
let shape_image = &*other.__bindgen_anon_1.mShapeImage.as_ref().mPtr;
|
||||
let image = shape_image.into_image().expect("Cannot convert to Image");
|
||||
ShapeSource::ImageOrUrl(image)
|
||||
},
|
||||
|
|
|
@ -8,7 +8,7 @@ use atomic_refcell::{AtomicRef, AtomicRefCell, AtomicRefMut};
|
|||
use context::QuirksMode;
|
||||
use dom::TElement;
|
||||
use gecko_bindings::bindings::{self, RawServoStyleSet};
|
||||
use gecko_bindings::structs::{self, RawGeckoPresContextOwned, ServoStyleSetSizes, StyleSheet as DomStyleSheet};
|
||||
use gecko_bindings::structs::{RawGeckoPresContextOwned, ServoStyleSetSizes, StyleSheet as DomStyleSheet};
|
||||
use gecko_bindings::structs::{StyleSheetInfo, nsIDocument};
|
||||
use gecko_bindings::sugar::ownership::{HasArcFFI, HasBoxFFI, HasFFI, HasSimpleFFI};
|
||||
use invalidation::media_queries::{MediaListKey, ToMediaListKey};
|
||||
|
@ -185,52 +185,20 @@ impl PerDocumentStyleDataImpl {
|
|||
.flush(&StylesheetGuards::same(guard), document_element, snapshots)
|
||||
}
|
||||
|
||||
/// Returns whether private browsing is enabled.
|
||||
fn is_private_browsing_enabled(&self) -> bool {
|
||||
let doc = self.stylist
|
||||
.device()
|
||||
.pres_context()
|
||||
.mDocument
|
||||
.raw::<nsIDocument>();
|
||||
unsafe { bindings::Gecko_IsPrivateBrowsingEnabled(doc) }
|
||||
}
|
||||
|
||||
/// Returns whether the document is being used as an image.
|
||||
fn is_being_used_as_an_image(&self) -> bool {
|
||||
let doc = self.stylist
|
||||
.device()
|
||||
.pres_context()
|
||||
.mDocument
|
||||
.raw::<nsIDocument>();
|
||||
|
||||
unsafe { (*doc).mIsBeingUsedAsImage() }
|
||||
}
|
||||
|
||||
/// Get the default computed values for this document.
|
||||
pub fn default_computed_values(&self) -> &Arc<ComputedValues> {
|
||||
self.stylist.device().default_computed_values_arc()
|
||||
}
|
||||
|
||||
/// Returns whether visited links are enabled.
|
||||
fn visited_links_enabled(&self) -> bool {
|
||||
unsafe { structs::StaticPrefs_sVarCache_layout_css_visited_links_enabled }
|
||||
}
|
||||
|
||||
/// Returns whether visited styles are enabled.
|
||||
#[inline]
|
||||
pub fn visited_styles_enabled(&self) -> bool {
|
||||
if !self.visited_links_enabled() {
|
||||
return false;
|
||||
}
|
||||
|
||||
if self.is_private_browsing_enabled() {
|
||||
return false;
|
||||
}
|
||||
|
||||
if self.is_being_used_as_an_image() {
|
||||
return false;
|
||||
}
|
||||
|
||||
true
|
||||
let doc = self.stylist
|
||||
.device()
|
||||
.pres_context()
|
||||
.mDocument
|
||||
.raw::<nsIDocument>();
|
||||
unsafe { bindings::Gecko_VisitedStylesEnabled(doc) }
|
||||
}
|
||||
|
||||
/// Measure heap usage.
|
||||
|
|
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;
|
||||
use context::QuirksMode;
|
||||
use cssparser::{Parser, RGBA, Token};
|
||||
use cssparser::RGBA;
|
||||
use euclid::Size2D;
|
||||
use euclid::TypedScale;
|
||||
use gecko::values::{convert_nscolor_to_rgba, convert_rgba_to_nscolor};
|
||||
use gecko_bindings::bindings;
|
||||
use gecko_bindings::structs;
|
||||
use gecko_bindings::structs::{nsCSSKTableEntry, nsCSSKeyword, nsCSSUnit, nsCSSValue};
|
||||
use gecko_bindings::structs::{nsMediaFeature, nsMediaFeature_RangeType};
|
||||
use gecko_bindings::structs::{nsMediaFeature_ValueType, nsPresContext};
|
||||
use gecko_bindings::structs::RawGeckoPresContextOwned;
|
||||
use gecko_bindings::structs::nsCSSKeywordAndBoolTableEntry;
|
||||
use gecko_bindings::structs::{nsPresContext, RawGeckoPresContextOwned};
|
||||
use media_queries::MediaType;
|
||||
use parser::{Parse, ParserContext};
|
||||
use properties::ComputedValues;
|
||||
use servo_arc::Arc;
|
||||
use std::fmt::{self, Write};
|
||||
use std::fmt;
|
||||
use std::sync::atomic::{AtomicBool, AtomicIsize, AtomicUsize, Ordering};
|
||||
use str::{starts_with_ignore_ascii_case, string_as_ascii_lowercase};
|
||||
use string_cache::Atom;
|
||||
use style_traits::{CSSPixel, CssWriter, DevicePixel};
|
||||
use style_traits::{ParseError, StyleParseErrorKind, ToCss};
|
||||
use style_traits::{CSSPixel, DevicePixel};
|
||||
use style_traits::viewport::ViewportConstraints;
|
||||
use stylesheets::Origin;
|
||||
use values::{serialize_atom_identifier, CSSFloat, CustomIdent, KeyframesName};
|
||||
use values::computed::{self, ToComputedValue};
|
||||
use values::{CustomIdent, KeyframesName};
|
||||
use values::computed::font::FontSize;
|
||||
use values::specified::{Integer, Length, Number, Resolution};
|
||||
|
||||
/// The `Device` in Gecko wraps a pres context, has a default values computed,
|
||||
/// and contains all the viewport rule state.
|
||||
|
@ -71,11 +60,8 @@ impl fmt::Debug for Device {
|
|||
|
||||
let mut doc_uri = nsCString::new();
|
||||
unsafe {
|
||||
let doc =
|
||||
&*self.pres_context().mDocument.raw::<structs::nsIDocument>();
|
||||
|
||||
bindings::Gecko_nsIURI_Debug(
|
||||
doc.mDocumentURI.raw::<structs::nsIURI>(),
|
||||
(*self.document()).mDocumentURI.raw::<structs::nsIURI>(),
|
||||
&mut doc_uri,
|
||||
)
|
||||
};
|
||||
|
@ -157,10 +143,17 @@ impl Device {
|
|||
}
|
||||
|
||||
/// Gets the pres context associated with this document.
|
||||
#[inline]
|
||||
pub fn pres_context(&self) -> &nsPresContext {
|
||||
unsafe { &*self.pres_context }
|
||||
}
|
||||
|
||||
/// Gets the document pointer.
|
||||
#[inline]
|
||||
pub fn document(&self) -> *mut structs::nsIDocument {
|
||||
self.pres_context().mDocument.raw::<structs::nsIDocument>()
|
||||
}
|
||||
|
||||
/// Recreates the default computed values.
|
||||
pub fn reset_computed_values(&mut self) {
|
||||
self.default_values = ComputedValues::default_values(self.pres_context());
|
||||
|
@ -248,758 +241,3 @@ impl Device {
|
|||
size.scale_by(1. / self.pres_context().mEffectiveTextZoom)
|
||||
}
|
||||
}
|
||||
|
||||
/// The kind of matching that should be performed on a media feature value.
|
||||
#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq)]
|
||||
pub enum Range {
|
||||
/// At least the specified value.
|
||||
Min,
|
||||
/// At most the specified value.
|
||||
Max,
|
||||
}
|
||||
|
||||
/// The operator that was specified in this media feature.
|
||||
#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq)]
|
||||
enum Operator {
|
||||
Equal,
|
||||
GreaterThan,
|
||||
GreaterThanEqual,
|
||||
LessThan,
|
||||
LessThanEqual,
|
||||
}
|
||||
|
||||
impl ToCss for Operator {
|
||||
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
|
||||
where
|
||||
W: fmt::Write,
|
||||
{
|
||||
dest.write_str(match *self {
|
||||
Operator::Equal => "=",
|
||||
Operator::LessThan => "<",
|
||||
Operator::LessThanEqual => "<=",
|
||||
Operator::GreaterThan => ">",
|
||||
Operator::GreaterThanEqual => ">=",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Either a `Range` or an `Operator`.
|
||||
///
|
||||
/// Ranged media features are not allowed with operations (that'd make no
|
||||
/// sense).
|
||||
#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq)]
|
||||
enum RangeOrOperator {
|
||||
Range(Range),
|
||||
Operator(Operator),
|
||||
}
|
||||
|
||||
/// A feature expression for gecko contains a reference to the media feature,
|
||||
/// the value the media query contained, and the range to evaluate.
|
||||
#[derive(Clone, Debug, MallocSizeOf)]
|
||||
pub struct MediaFeatureExpression {
|
||||
feature: &'static nsMediaFeature,
|
||||
value: Option<MediaExpressionValue>,
|
||||
range_or_operator: Option<RangeOrOperator>,
|
||||
}
|
||||
|
||||
impl ToCss for MediaFeatureExpression {
|
||||
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
|
||||
where
|
||||
W: fmt::Write,
|
||||
{
|
||||
dest.write_str("(")?;
|
||||
|
||||
if (self.feature.mReqFlags & structs::nsMediaFeature_RequirementFlags_eHasWebkitPrefix) != 0
|
||||
{
|
||||
dest.write_str("-webkit-")?;
|
||||
}
|
||||
|
||||
if let Some(RangeOrOperator::Range(range)) = self.range_or_operator {
|
||||
match range {
|
||||
Range::Min => dest.write_str("min-")?,
|
||||
Range::Max => dest.write_str("max-")?,
|
||||
}
|
||||
}
|
||||
|
||||
// NB: CssStringWriter not needed, feature names are under control.
|
||||
write!(dest, "{}", unsafe {
|
||||
Atom::from_static(*self.feature.mName)
|
||||
})?;
|
||||
|
||||
if let Some(RangeOrOperator::Operator(op)) = self.range_or_operator {
|
||||
dest.write_char(' ')?;
|
||||
op.to_css(dest)?;
|
||||
dest.write_char(' ')?;
|
||||
} else if self.value.is_some() {
|
||||
dest.write_str(": ")?;
|
||||
}
|
||||
|
||||
if let Some(ref val) = self.value {
|
||||
val.to_css(dest, self)?;
|
||||
}
|
||||
|
||||
dest.write_str(")")
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for MediaFeatureExpression {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.feature.mName == other.feature.mName && self.value == other.value &&
|
||||
self.range_or_operator == other.range_or_operator
|
||||
}
|
||||
}
|
||||
|
||||
/// A value found or expected in a media expression.
|
||||
///
|
||||
/// FIXME(emilio): How should calc() serialize in the Number / Integer /
|
||||
/// BoolInteger / IntRatio case, as computed or as specified value?
|
||||
///
|
||||
/// If the first, this would need to store the relevant values.
|
||||
///
|
||||
/// See: https://github.com/w3c/csswg-drafts/issues/1968
|
||||
#[derive(Clone, Debug, MallocSizeOf, PartialEq)]
|
||||
pub enum MediaExpressionValue {
|
||||
/// A length.
|
||||
Length(Length),
|
||||
/// A (non-negative) integer.
|
||||
Integer(u32),
|
||||
/// A floating point value.
|
||||
Float(CSSFloat),
|
||||
/// A boolean value, specified as an integer (i.e., either 0 or 1).
|
||||
BoolInteger(bool),
|
||||
/// Two integers separated by '/', with optional whitespace on either side
|
||||
/// of the '/'.
|
||||
IntRatio(u32, u32),
|
||||
/// A resolution.
|
||||
Resolution(Resolution),
|
||||
/// An enumerated value, defined by the variant keyword table in the
|
||||
/// feature's `mData` member.
|
||||
Enumerated(i16),
|
||||
/// Similar to the above Enumerated but the variant keyword table has an
|
||||
/// additional field what the keyword value means in the Boolean Context.
|
||||
BoolEnumerated(i16),
|
||||
/// An identifier.
|
||||
Ident(Atom),
|
||||
}
|
||||
|
||||
impl MediaExpressionValue {
|
||||
fn from_css_value(
|
||||
for_expr: &MediaFeatureExpression,
|
||||
css_value: &nsCSSValue,
|
||||
) -> Option<Self> {
|
||||
// NB: If there's a null value, that means that we don't support the
|
||||
// feature.
|
||||
if css_value.mUnit == nsCSSUnit::eCSSUnit_Null {
|
||||
return None;
|
||||
}
|
||||
|
||||
match for_expr.feature.mValueType {
|
||||
nsMediaFeature_ValueType::eLength => {
|
||||
debug_assert_eq!(css_value.mUnit, nsCSSUnit::eCSSUnit_Pixel);
|
||||
let pixels = css_value.float_unchecked();
|
||||
Some(MediaExpressionValue::Length(Length::from_px(pixels)))
|
||||
},
|
||||
nsMediaFeature_ValueType::eInteger => {
|
||||
let i = css_value.integer_unchecked();
|
||||
debug_assert!(i >= 0);
|
||||
Some(MediaExpressionValue::Integer(i as u32))
|
||||
},
|
||||
nsMediaFeature_ValueType::eFloat => {
|
||||
debug_assert_eq!(css_value.mUnit, nsCSSUnit::eCSSUnit_Number);
|
||||
Some(MediaExpressionValue::Float(css_value.float_unchecked()))
|
||||
},
|
||||
nsMediaFeature_ValueType::eBoolInteger => {
|
||||
debug_assert_eq!(css_value.mUnit, nsCSSUnit::eCSSUnit_Integer);
|
||||
let i = css_value.integer_unchecked();
|
||||
debug_assert!(i == 0 || i == 1);
|
||||
Some(MediaExpressionValue::BoolInteger(i == 1))
|
||||
},
|
||||
nsMediaFeature_ValueType::eResolution => {
|
||||
debug_assert_eq!(css_value.mUnit, nsCSSUnit::eCSSUnit_Pixel);
|
||||
Some(MediaExpressionValue::Resolution(Resolution::Dppx(
|
||||
css_value.float_unchecked(),
|
||||
)))
|
||||
},
|
||||
nsMediaFeature_ValueType::eEnumerated => {
|
||||
let value = css_value.integer_unchecked() as i16;
|
||||
Some(MediaExpressionValue::Enumerated(value))
|
||||
},
|
||||
nsMediaFeature_ValueType::eBoolEnumerated => {
|
||||
let value = css_value.integer_unchecked() as i16;
|
||||
Some(MediaExpressionValue::BoolEnumerated(value))
|
||||
},
|
||||
nsMediaFeature_ValueType::eIdent => {
|
||||
debug_assert_eq!(css_value.mUnit, nsCSSUnit::eCSSUnit_AtomIdent);
|
||||
Some(MediaExpressionValue::Ident(unsafe {
|
||||
Atom::from_raw(*css_value.mValue.mAtom.as_ref())
|
||||
}))
|
||||
},
|
||||
nsMediaFeature_ValueType::eIntRatio => {
|
||||
let array = unsafe { css_value.array_unchecked() };
|
||||
debug_assert_eq!(array.len(), 2);
|
||||
let first = array[0].integer_unchecked();
|
||||
let second = array[1].integer_unchecked();
|
||||
|
||||
debug_assert!(first >= 0 && second >= 0);
|
||||
Some(MediaExpressionValue::IntRatio(first as u32, second as u32))
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MediaExpressionValue {
|
||||
fn to_css<W>(&self, dest: &mut CssWriter<W>, for_expr: &MediaFeatureExpression) -> fmt::Result
|
||||
where
|
||||
W: fmt::Write,
|
||||
{
|
||||
match *self {
|
||||
MediaExpressionValue::Length(ref l) => l.to_css(dest),
|
||||
MediaExpressionValue::Integer(v) => v.to_css(dest),
|
||||
MediaExpressionValue::Float(v) => v.to_css(dest),
|
||||
MediaExpressionValue::BoolInteger(v) => dest.write_str(if v { "1" } else { "0" }),
|
||||
MediaExpressionValue::IntRatio(a, b) => {
|
||||
a.to_css(dest)?;
|
||||
dest.write_char('/')?;
|
||||
b.to_css(dest)
|
||||
},
|
||||
MediaExpressionValue::Resolution(ref r) => r.to_css(dest),
|
||||
MediaExpressionValue::Ident(ref ident) => serialize_atom_identifier(ident, dest),
|
||||
MediaExpressionValue::Enumerated(value) => unsafe {
|
||||
let keyword = find_in_table(
|
||||
*for_expr.feature.mData.mKeywordTable.as_ref(),
|
||||
|_kw, val| val == value,
|
||||
|e| e.keyword(),
|
||||
).expect("Value not found in the keyword table?");
|
||||
|
||||
MediaExpressionValue::keyword_to_css(keyword, dest)
|
||||
},
|
||||
MediaExpressionValue::BoolEnumerated(value) => unsafe {
|
||||
let keyword = find_in_table(
|
||||
*for_expr.feature.mData.mKeywordAndBoolTable.as_ref(),
|
||||
|_kw, val| val == value,
|
||||
|e| e.keyword(),
|
||||
).expect("Value not found in the keyword table?");
|
||||
|
||||
MediaExpressionValue::keyword_to_css(keyword, dest)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn keyword_to_css<W>(keyword: nsCSSKeyword, dest: &mut CssWriter<W>) -> fmt::Result
|
||||
where
|
||||
W: fmt::Write,
|
||||
{
|
||||
use std::{slice, str};
|
||||
use std::os::raw::c_char;
|
||||
|
||||
// NB: All the keywords on nsMediaFeatures are static,
|
||||
// well-formed utf-8.
|
||||
let mut length = 0;
|
||||
unsafe {
|
||||
let buffer: *const c_char = bindings::Gecko_CSSKeywordString(keyword, &mut length);
|
||||
let buffer = slice::from_raw_parts(buffer as *const u8, length as usize);
|
||||
|
||||
let string = str::from_utf8_unchecked(buffer);
|
||||
|
||||
dest.write_str(string)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn find_feature<F>(mut f: F) -> Option<&'static nsMediaFeature>
|
||||
where
|
||||
F: FnMut(&'static nsMediaFeature) -> bool,
|
||||
{
|
||||
unsafe {
|
||||
let mut features = structs::nsMediaFeatures_features.as_ptr();
|
||||
while !(*features).mName.is_null() {
|
||||
if f(&*features) {
|
||||
return Some(&*features);
|
||||
}
|
||||
features = features.offset(1);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
trait TableEntry {
|
||||
fn value(&self) -> i16;
|
||||
fn keyword(&self) -> nsCSSKeyword;
|
||||
}
|
||||
|
||||
impl TableEntry for nsCSSKTableEntry {
|
||||
fn value(&self) -> i16 {
|
||||
self.mValue
|
||||
}
|
||||
fn keyword(&self) -> nsCSSKeyword {
|
||||
self.mKeyword
|
||||
}
|
||||
}
|
||||
|
||||
impl TableEntry for nsCSSKeywordAndBoolTableEntry {
|
||||
fn value(&self) -> i16 {
|
||||
self._base.mValue
|
||||
}
|
||||
fn keyword(&self) -> nsCSSKeyword {
|
||||
self._base.mKeyword
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn find_in_table<T, R, FindFunc, ResultFunc>(
|
||||
current_entry: *const T,
|
||||
find: FindFunc,
|
||||
result_func: ResultFunc,
|
||||
) -> Option<R>
|
||||
where
|
||||
T: TableEntry,
|
||||
FindFunc: Fn(nsCSSKeyword, i16) -> bool,
|
||||
ResultFunc: Fn(&T) -> R,
|
||||
{
|
||||
let mut current_entry = current_entry;
|
||||
|
||||
loop {
|
||||
let value = (*current_entry).value();
|
||||
let keyword = (*current_entry).keyword();
|
||||
|
||||
if value == -1 {
|
||||
return None; // End of the table.
|
||||
}
|
||||
|
||||
if find(keyword, value) {
|
||||
return Some(result_func(&*current_entry));
|
||||
}
|
||||
|
||||
current_entry = current_entry.offset(1);
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_feature_value<'i, 't>(
|
||||
feature: &nsMediaFeature,
|
||||
feature_value_type: nsMediaFeature_ValueType,
|
||||
context: &ParserContext,
|
||||
input: &mut Parser<'i, 't>,
|
||||
) -> Result<MediaExpressionValue, ParseError<'i>> {
|
||||
let value = match feature_value_type {
|
||||
nsMediaFeature_ValueType::eLength => {
|
||||
let length = Length::parse_non_negative(context, input)?;
|
||||
MediaExpressionValue::Length(length)
|
||||
},
|
||||
nsMediaFeature_ValueType::eInteger => {
|
||||
let integer = Integer::parse_non_negative(context, input)?;
|
||||
MediaExpressionValue::Integer(integer.value() as u32)
|
||||
},
|
||||
nsMediaFeature_ValueType::eBoolInteger => {
|
||||
let integer = Integer::parse_non_negative(context, input)?;
|
||||
let value = integer.value();
|
||||
if value > 1 {
|
||||
return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
|
||||
}
|
||||
MediaExpressionValue::BoolInteger(value == 1)
|
||||
},
|
||||
nsMediaFeature_ValueType::eFloat => {
|
||||
let number = Number::parse(context, input)?;
|
||||
MediaExpressionValue::Float(number.get())
|
||||
},
|
||||
nsMediaFeature_ValueType::eIntRatio => {
|
||||
let a = Integer::parse_positive(context, input)?;
|
||||
input.expect_delim('/')?;
|
||||
let b = Integer::parse_positive(context, input)?;
|
||||
MediaExpressionValue::IntRatio(a.value() as u32, b.value() as u32)
|
||||
},
|
||||
nsMediaFeature_ValueType::eResolution => {
|
||||
MediaExpressionValue::Resolution(Resolution::parse(context, input)?)
|
||||
},
|
||||
nsMediaFeature_ValueType::eEnumerated => {
|
||||
let first_table_entry: *const nsCSSKTableEntry =
|
||||
unsafe { *feature.mData.mKeywordTable.as_ref() };
|
||||
|
||||
let value = parse_keyword(input, first_table_entry)?;
|
||||
|
||||
MediaExpressionValue::Enumerated(value)
|
||||
},
|
||||
nsMediaFeature_ValueType::eBoolEnumerated => {
|
||||
let first_table_entry: *const nsCSSKeywordAndBoolTableEntry =
|
||||
unsafe { *feature.mData.mKeywordAndBoolTable.as_ref() };
|
||||
|
||||
let value = parse_keyword(input, first_table_entry)?;
|
||||
|
||||
MediaExpressionValue::BoolEnumerated(value)
|
||||
},
|
||||
nsMediaFeature_ValueType::eIdent => {
|
||||
MediaExpressionValue::Ident(Atom::from(input.expect_ident()?.as_ref()))
|
||||
},
|
||||
};
|
||||
|
||||
Ok(value)
|
||||
}
|
||||
|
||||
/// Parse a keyword and returns the corresponding i16 value.
|
||||
fn parse_keyword<'i, 't, T>(
|
||||
input: &mut Parser<'i, 't>,
|
||||
first_table_entry: *const T,
|
||||
) -> Result<i16, ParseError<'i>>
|
||||
where
|
||||
T: TableEntry,
|
||||
{
|
||||
let location = input.current_source_location();
|
||||
let keyword = input.expect_ident()?;
|
||||
let keyword = unsafe {
|
||||
bindings::Gecko_LookupCSSKeyword(keyword.as_bytes().as_ptr(), keyword.len() as u32)
|
||||
};
|
||||
|
||||
let value = unsafe {
|
||||
find_in_table(first_table_entry, |kw, _| kw == keyword, |e| e.value())
|
||||
};
|
||||
|
||||
match value {
|
||||
Some(value) => Ok(value),
|
||||
None => Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Consumes an operation or a colon, or returns an error.
|
||||
fn consume_operation_or_colon(
|
||||
input: &mut Parser,
|
||||
) -> Result<Option<Operator>, ()> {
|
||||
let first_delim = {
|
||||
let next_token = match input.next() {
|
||||
Ok(t) => t,
|
||||
Err(..) => return Err(()),
|
||||
};
|
||||
|
||||
match *next_token {
|
||||
Token::Colon => return Ok(None),
|
||||
Token::Delim(oper) => oper,
|
||||
_ => return Err(()),
|
||||
}
|
||||
};
|
||||
Ok(Some(match first_delim {
|
||||
'=' => Operator::Equal,
|
||||
'>' => {
|
||||
if input.try(|i| i.expect_delim('=')).is_ok() {
|
||||
Operator::GreaterThanEqual
|
||||
} else {
|
||||
Operator::GreaterThan
|
||||
}
|
||||
}
|
||||
'<' => {
|
||||
if input.try(|i| i.expect_delim('=')).is_ok() {
|
||||
Operator::LessThanEqual
|
||||
} else {
|
||||
Operator::LessThan
|
||||
}
|
||||
}
|
||||
_ => return Err(()),
|
||||
}))
|
||||
}
|
||||
|
||||
impl MediaFeatureExpression {
|
||||
/// Trivially construct a new expression.
|
||||
fn new(
|
||||
feature: &'static nsMediaFeature,
|
||||
value: Option<MediaExpressionValue>,
|
||||
range_or_operator: Option<RangeOrOperator>,
|
||||
) -> Self {
|
||||
Self {
|
||||
feature,
|
||||
value,
|
||||
range_or_operator,
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse a media expression of the form:
|
||||
///
|
||||
/// ```
|
||||
/// (media-feature: media-value)
|
||||
/// ```
|
||||
pub fn parse<'i, 't>(
|
||||
context: &ParserContext,
|
||||
input: &mut Parser<'i, 't>,
|
||||
) -> Result<Self, ParseError<'i>> {
|
||||
input.expect_parenthesis_block()?;
|
||||
input.parse_nested_block(|input| {
|
||||
Self::parse_in_parenthesis_block(context, input)
|
||||
})
|
||||
}
|
||||
|
||||
/// Parse a media feature expression where we've already consumed the
|
||||
/// parenthesis.
|
||||
pub fn parse_in_parenthesis_block<'i, 't>(
|
||||
context: &ParserContext,
|
||||
input: &mut Parser<'i, 't>,
|
||||
) -> Result<Self, ParseError<'i>> {
|
||||
// FIXME: remove extra indented block when lifetimes are non-lexical
|
||||
let feature;
|
||||
let range;
|
||||
{
|
||||
let location = input.current_source_location();
|
||||
let ident = input.expect_ident()?;
|
||||
|
||||
let mut flags = 0;
|
||||
|
||||
if context.chrome_rules_enabled() || context.stylesheet_origin == Origin::UserAgent
|
||||
{
|
||||
flags |= structs::nsMediaFeature_RequirementFlags_eUserAgentAndChromeOnly;
|
||||
}
|
||||
|
||||
let result = {
|
||||
let mut feature_name = &**ident;
|
||||
|
||||
if unsafe { structs::StaticPrefs_sVarCache_layout_css_prefixes_webkit } &&
|
||||
starts_with_ignore_ascii_case(feature_name, "-webkit-")
|
||||
{
|
||||
feature_name = &feature_name[8..];
|
||||
flags |= structs::nsMediaFeature_RequirementFlags_eHasWebkitPrefix;
|
||||
if unsafe {
|
||||
structs::StaticPrefs_sVarCache_layout_css_prefixes_device_pixel_ratio_webkit
|
||||
} {
|
||||
flags |= structs::nsMediaFeature_RequirementFlags_eWebkitDevicePixelRatioPrefEnabled;
|
||||
}
|
||||
}
|
||||
|
||||
let range = if starts_with_ignore_ascii_case(feature_name, "min-") {
|
||||
feature_name = &feature_name[4..];
|
||||
Some(Range::Min)
|
||||
} else if starts_with_ignore_ascii_case(feature_name, "max-") {
|
||||
feature_name = &feature_name[4..];
|
||||
Some(Range::Max)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let atom = Atom::from(string_as_ascii_lowercase(feature_name));
|
||||
match find_feature(|f| atom.as_ptr() == unsafe { *f.mName as *mut _ }) {
|
||||
Some(f) => Ok((f, range)),
|
||||
None => Err(()),
|
||||
}
|
||||
};
|
||||
|
||||
match result {
|
||||
Ok((f, r)) => {
|
||||
feature = f;
|
||||
range = r;
|
||||
},
|
||||
Err(()) => {
|
||||
return Err(location.new_custom_error(
|
||||
StyleParseErrorKind::MediaQueryExpectedFeatureName(ident.clone()),
|
||||
))
|
||||
},
|
||||
}
|
||||
|
||||
if (feature.mReqFlags & !flags) != 0 {
|
||||
return Err(location.new_custom_error(
|
||||
StyleParseErrorKind::MediaQueryExpectedFeatureName(ident.clone()),
|
||||
));
|
||||
}
|
||||
|
||||
if range.is_some() &&
|
||||
feature.mRangeType != nsMediaFeature_RangeType::eMinMaxAllowed
|
||||
{
|
||||
return Err(location.new_custom_error(
|
||||
StyleParseErrorKind::MediaQueryExpectedFeatureName(ident.clone()),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
let feature_allows_ranges =
|
||||
feature.mRangeType == nsMediaFeature_RangeType::eMinMaxAllowed;
|
||||
|
||||
let operator = input.try(consume_operation_or_colon);
|
||||
let operator = match operator {
|
||||
Err(..) => {
|
||||
// If there's no colon, this is a media query of the
|
||||
// form '(<feature>)', that is, there's no value
|
||||
// specified.
|
||||
//
|
||||
// Gecko doesn't allow ranged expressions without a
|
||||
// value, so just reject them here too.
|
||||
if range.is_some() {
|
||||
return Err(input.new_custom_error(
|
||||
StyleParseErrorKind::RangedExpressionWithNoValue
|
||||
));
|
||||
}
|
||||
|
||||
return Ok(Self::new(feature, None, None));
|
||||
}
|
||||
Ok(operator) => operator,
|
||||
};
|
||||
|
||||
let range_or_operator = match range {
|
||||
Some(range) => {
|
||||
if operator.is_some() {
|
||||
return Err(input.new_custom_error(
|
||||
StyleParseErrorKind::MediaQueryUnexpectedOperator
|
||||
));
|
||||
}
|
||||
Some(RangeOrOperator::Range(range))
|
||||
}
|
||||
None => {
|
||||
match operator {
|
||||
Some(operator) => {
|
||||
if !feature_allows_ranges {
|
||||
return Err(input.new_custom_error(
|
||||
StyleParseErrorKind::MediaQueryUnexpectedOperator
|
||||
));
|
||||
}
|
||||
Some(RangeOrOperator::Operator(operator))
|
||||
}
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let value =
|
||||
parse_feature_value(feature, feature.mValueType, context, input).map_err(|err| {
|
||||
err.location
|
||||
.new_custom_error(StyleParseErrorKind::MediaQueryExpectedFeatureValue)
|
||||
})?;
|
||||
|
||||
Ok(Self::new(feature, Some(value), range_or_operator))
|
||||
}
|
||||
|
||||
/// Returns whether this media query evaluates to true for the given device.
|
||||
pub fn matches(&self, device: &Device, quirks_mode: QuirksMode) -> bool {
|
||||
let mut css_value = nsCSSValue::null();
|
||||
unsafe {
|
||||
(self.feature.mGetter.unwrap())(
|
||||
device
|
||||
.pres_context()
|
||||
.mDocument
|
||||
.raw::<structs::nsIDocument>(),
|
||||
self.feature,
|
||||
&mut css_value,
|
||||
)
|
||||
};
|
||||
|
||||
let value = match MediaExpressionValue::from_css_value(self, &css_value) {
|
||||
Some(v) => v,
|
||||
None => return false,
|
||||
};
|
||||
|
||||
self.evaluate_against(device, &value, quirks_mode)
|
||||
}
|
||||
|
||||
fn evaluate_against(
|
||||
&self,
|
||||
device: &Device,
|
||||
actual_value: &MediaExpressionValue,
|
||||
quirks_mode: QuirksMode,
|
||||
) -> bool {
|
||||
use self::MediaExpressionValue::*;
|
||||
use std::cmp::Ordering;
|
||||
|
||||
debug_assert!(
|
||||
self.feature.mRangeType == nsMediaFeature_RangeType::eMinMaxAllowed ||
|
||||
self.range_or_operator.is_none(),
|
||||
"Whoops, wrong range"
|
||||
);
|
||||
|
||||
// http://dev.w3.org/csswg/mediaqueries3/#units
|
||||
// em units are relative to the initial font-size.
|
||||
let required_value = match self.value {
|
||||
Some(ref v) => v,
|
||||
None => {
|
||||
// If there's no value, always match unless it's a zero length
|
||||
// or a zero integer or boolean.
|
||||
return match *actual_value {
|
||||
BoolInteger(v) => v,
|
||||
Integer(v) => v != 0,
|
||||
Length(ref l) => computed::Context::for_media_query_evaluation(
|
||||
device,
|
||||
quirks_mode,
|
||||
|context| l.to_computed_value(&context).px() != 0.,
|
||||
),
|
||||
BoolEnumerated(value) => {
|
||||
let value = unsafe {
|
||||
find_in_table(
|
||||
*self.feature.mData.mKeywordAndBoolTable.as_ref(),
|
||||
|_kw, val| val == value,
|
||||
|e| e.mValueInBooleanContext,
|
||||
)
|
||||
};
|
||||
value.expect("Value not found in the keyword table?")
|
||||
},
|
||||
_ => true,
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
// FIXME(emilio): Handle the possible floating point errors?
|
||||
let cmp = match (actual_value, required_value) {
|
||||
(&Length(ref one), &Length(ref other)) => {
|
||||
computed::Context::for_media_query_evaluation(device, quirks_mode, |context| {
|
||||
one.to_computed_value(&context)
|
||||
.to_i32_au()
|
||||
.cmp(&other.to_computed_value(&context).to_i32_au())
|
||||
})
|
||||
},
|
||||
(&Integer(one), &Integer(ref other)) => one.cmp(other),
|
||||
(&BoolInteger(one), &BoolInteger(ref other)) => one.cmp(other),
|
||||
(&Float(one), &Float(ref other)) => one.partial_cmp(other).unwrap(),
|
||||
(&IntRatio(one_num, one_den), &IntRatio(other_num, other_den)) => {
|
||||
// Extend to avoid overflow.
|
||||
(one_num as u64 * other_den as u64).cmp(&(other_num as u64 * one_den as u64))
|
||||
},
|
||||
(&Resolution(ref one), &Resolution(ref other)) => {
|
||||
let actual_dpi = unsafe {
|
||||
if (*device.pres_context).mOverrideDPPX > 0.0 {
|
||||
self::Resolution::Dppx((*device.pres_context).mOverrideDPPX).to_dpi()
|
||||
} else {
|
||||
one.to_dpi()
|
||||
}
|
||||
};
|
||||
|
||||
actual_dpi.partial_cmp(&other.to_dpi()).unwrap()
|
||||
},
|
||||
(&Ident(ref one), &Ident(ref other)) => {
|
||||
debug_assert_ne!(
|
||||
self.feature.mRangeType,
|
||||
nsMediaFeature_RangeType::eMinMaxAllowed
|
||||
);
|
||||
return one == other;
|
||||
},
|
||||
(&Enumerated(one), &Enumerated(other)) => {
|
||||
debug_assert_ne!(
|
||||
self.feature.mRangeType,
|
||||
nsMediaFeature_RangeType::eMinMaxAllowed
|
||||
);
|
||||
return one == other;
|
||||
},
|
||||
(&BoolEnumerated(one), &BoolEnumerated(other)) => {
|
||||
debug_assert_ne!(
|
||||
self.feature.mRangeType,
|
||||
nsMediaFeature_RangeType::eMinMaxAllowed
|
||||
);
|
||||
return one == other;
|
||||
},
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
let range_or_op = match self.range_or_operator {
|
||||
Some(r) => r,
|
||||
None => return cmp == Ordering::Equal,
|
||||
};
|
||||
|
||||
match range_or_op {
|
||||
RangeOrOperator::Range(range) => {
|
||||
cmp == Ordering::Equal || match range {
|
||||
Range::Min => cmp == Ordering::Greater,
|
||||
Range::Max => cmp == Ordering::Less,
|
||||
}
|
||||
}
|
||||
RangeOrOperator::Operator(op) => {
|
||||
match op {
|
||||
Operator::Equal => cmp == Ordering::Equal,
|
||||
Operator::GreaterThan => cmp == Ordering::Greater,
|
||||
Operator::GreaterThanEqual => {
|
||||
cmp == Ordering::Equal || cmp == Ordering::Greater
|
||||
}
|
||||
Operator::LessThan => cmp == Ordering::Less,
|
||||
Operator::LessThanEqual => {
|
||||
cmp == Ordering::Equal || cmp == Ordering::Less
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ pub mod arc_types;
|
|||
pub mod conversions;
|
||||
pub mod data;
|
||||
pub mod global_style_data;
|
||||
pub mod media_features;
|
||||
pub mod media_queries;
|
||||
pub mod pseudo_element;
|
||||
pub mod restyle_damage;
|
||||
|
|
|
@ -8,9 +8,9 @@ pub enum PseudoElement {
|
|||
% for pseudo in PSEUDOS:
|
||||
/// ${pseudo.value}
|
||||
% if pseudo.is_tree_pseudo_element():
|
||||
${pseudo.capitalized()}(ThinBoxedSlice<Atom>),
|
||||
${pseudo.capitalized_pseudo()}(ThinBoxedSlice<Atom>),
|
||||
% else:
|
||||
${pseudo.capitalized()},
|
||||
${pseudo.capitalized_pseudo()},
|
||||
% endif
|
||||
% endfor
|
||||
}
|
||||
|
@ -41,7 +41,7 @@ pub const EAGER_PSEUDOS: [PseudoElement; EAGER_PSEUDO_COUNT] = [
|
|||
];
|
||||
|
||||
<%def name="pseudo_element_variant(pseudo, tree_arg='..')">\
|
||||
PseudoElement::${pseudo.capitalized()}${"({})".format(tree_arg) if pseudo.is_tree_pseudo_element() else ""}\
|
||||
PseudoElement::${pseudo.capitalized_pseudo()}${"({})".format(tree_arg) if pseudo.is_tree_pseudo_element() else ""}\
|
||||
</%def>
|
||||
|
||||
impl PseudoElement {
|
||||
|
@ -120,7 +120,7 @@ impl PseudoElement {
|
|||
% elif pseudo.is_anon_box():
|
||||
structs::CSS_PSEUDO_ELEMENT_ENABLED_IN_UA_SHEETS,
|
||||
% else:
|
||||
structs::SERVO_CSS_PSEUDO_ELEMENT_FLAGS_${pseudo.original_ident},
|
||||
structs::SERVO_CSS_PSEUDO_ELEMENT_FLAGS_${pseudo.pseudo_ident},
|
||||
% endif
|
||||
% endfor
|
||||
}
|
||||
|
@ -132,7 +132,7 @@ impl PseudoElement {
|
|||
match type_ {
|
||||
% for pseudo in PSEUDOS:
|
||||
% if not pseudo.is_anon_box():
|
||||
CSSPseudoElementType::${pseudo.original_ident} => {
|
||||
CSSPseudoElementType::${pseudo.pseudo_ident} => {
|
||||
Some(${pseudo_element_variant(pseudo)})
|
||||
},
|
||||
% endif
|
||||
|
@ -149,13 +149,13 @@ impl PseudoElement {
|
|||
match *self {
|
||||
% for pseudo in PSEUDOS:
|
||||
% if not pseudo.is_anon_box():
|
||||
PseudoElement::${pseudo.capitalized()} => CSSPseudoElementType::${pseudo.original_ident},
|
||||
PseudoElement::${pseudo.capitalized_pseudo()} => CSSPseudoElementType::${pseudo.pseudo_ident},
|
||||
% elif pseudo.is_tree_pseudo_element():
|
||||
PseudoElement::${pseudo.capitalized()}(..) => CSSPseudoElementType::XULTree,
|
||||
PseudoElement::${pseudo.capitalized_pseudo()}(..) => CSSPseudoElementType::XULTree,
|
||||
% elif pseudo.is_inheriting_anon_box():
|
||||
PseudoElement::${pseudo.capitalized()} => CSSPseudoElementType_InheritingAnonBox,
|
||||
PseudoElement::${pseudo.capitalized_pseudo()} => CSSPseudoElementType_InheritingAnonBox,
|
||||
% else:
|
||||
PseudoElement::${pseudo.capitalized()} => CSSPseudoElementType::NonInheritingAnonBox,
|
||||
PseudoElement::${pseudo.capitalized_pseudo()} => CSSPseudoElementType::NonInheritingAnonBox,
|
||||
% endif
|
||||
% endfor
|
||||
}
|
||||
|
@ -171,7 +171,7 @@ impl PseudoElement {
|
|||
pub fn tree_pseudo_args(&self) -> Option<<&[Atom]> {
|
||||
match *self {
|
||||
% for pseudo in TREE_PSEUDOS:
|
||||
PseudoElement::${pseudo.capitalized()}(ref args) => Some(args),
|
||||
PseudoElement::${pseudo.capitalized_pseudo()}(ref args) => Some(args),
|
||||
% endfor
|
||||
_ => None,
|
||||
}
|
||||
|
@ -213,7 +213,7 @@ impl PseudoElement {
|
|||
% for pseudo in PSEUDOS:
|
||||
% if pseudo.is_tree_pseudo_element():
|
||||
if atom == &atom!("${pseudo.value}") {
|
||||
return Some(PseudoElement::${pseudo.capitalized()}(args.into()));
|
||||
return Some(PseudoElement::${pseudo.capitalized_pseudo()}(args.into()));
|
||||
}
|
||||
% endif
|
||||
% endfor
|
||||
|
|
|
@ -20,55 +20,29 @@ PRELUDE = """
|
|||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
/* Autogenerated file created by components/style/gecko/binding_tools/regen_atoms.py, DO NOT EDIT DIRECTLY */
|
||||
/* Autogenerated file created by components/style/gecko/regen_atoms.py, DO NOT EDIT DIRECTLY */
|
||||
"""[1:] # NOQA: E501
|
||||
|
||||
|
||||
def gnu_symbolify(source, ident):
|
||||
return "_ZN{}{}{}{}E".format(len(source.CLASS), source.CLASS, len(ident), ident)
|
||||
# Matches lines like `GK_ATOM(foo, "foo", 0x12345678, nsStaticAtom, PseudoElementAtom)`.
|
||||
PATTERN = re.compile('^GK_ATOM\(([^,]*),[^"]*"([^"]*)",\s*(0x[0-9a-f]+),\s*([^,]*),\s*([^)]*)\)',
|
||||
re.MULTILINE)
|
||||
FILE = "include/nsGkAtomList.h"
|
||||
CLASS = "nsGkAtoms"
|
||||
|
||||
|
||||
def msvc64_symbolify(source, ident):
|
||||
return "?{}@{}@@2PEAV{}@@EA".format(ident, source.CLASS, source.TYPE)
|
||||
def gnu_symbolify(ident):
|
||||
return "_ZN{}{}{}{}E".format(len(CLASS), CLASS, len(ident), ident)
|
||||
|
||||
|
||||
def msvc32_symbolify(source, ident):
|
||||
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 "_".
|
||||
# See https://github.com/rust-lang/rust/issues/36097
|
||||
return "\\x01?{}@{}@@2PAV{}@@A".format(ident, source.CLASS, source.TYPE)
|
||||
|
||||
|
||||
class GkAtomSource:
|
||||
PATTERN = re.compile('^(GK_ATOM)\(([^,]*),[^"]*"([^"]*)"\)',
|
||||
re.MULTILINE)
|
||||
FILE = "include/nsGkAtomList.h"
|
||||
CLASS = "nsGkAtoms"
|
||||
TYPE = "nsStaticAtom"
|
||||
|
||||
|
||||
class CSSPseudoElementsAtomSource:
|
||||
PATTERN = re.compile('^(CSS_PSEUDO_ELEMENT)\(([^,]*),[^"]*"([^"]*)",',
|
||||
re.MULTILINE)
|
||||
FILE = "include/nsCSSPseudoElementList.h"
|
||||
CLASS = "nsCSSPseudoElements"
|
||||
# NB: nsICSSPseudoElement is effectively the same as a nsStaticAtom, but we need
|
||||
# this for MSVC name mangling.
|
||||
TYPE = "nsICSSPseudoElement"
|
||||
|
||||
|
||||
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,
|
||||
]
|
||||
return "\\x01?{}@{}@@2PAV{}@@A".format(ident, CLASS, ty)
|
||||
|
||||
|
||||
def map_atom(ident):
|
||||
|
@ -79,42 +53,47 @@ def map_atom(ident):
|
|||
|
||||
|
||||
class Atom:
|
||||
def __init__(self, source, macro_name, ident, value):
|
||||
self.ident = "{}_{}".format(source.CLASS, ident)
|
||||
def __init__(self, ident, value, hash, ty, atom_type):
|
||||
self.ident = "{}_{}".format(CLASS, ident)
|
||||
self.original_ident = ident
|
||||
self.value = value
|
||||
self.source = source
|
||||
self.macro = macro_name
|
||||
self.hash = hash
|
||||
# The Gecko type: "nsStaticAtom", "nsICSSPseudoElement", or "nsIAnonBoxPseudo"
|
||||
self.ty = ty
|
||||
# The type of atom: "Atom", "PseudoElement", "NonInheritingAnonBox",
|
||||
# or "InheritingAnonBox"
|
||||
self.atom_type = atom_type
|
||||
if self.is_pseudo() or self.is_anon_box():
|
||||
self.pseudo_ident = (ident.split("_", 1))[1]
|
||||
if self.is_anon_box():
|
||||
assert self.is_inheriting_anon_box() or self.is_non_inheriting_anon_box()
|
||||
|
||||
def cpp_class(self):
|
||||
return self.source.CLASS
|
||||
|
||||
def gnu_symbol(self):
|
||||
return gnu_symbolify(self.source, self.original_ident)
|
||||
return gnu_symbolify(self.original_ident)
|
||||
|
||||
def msvc32_symbol(self):
|
||||
return msvc32_symbolify(self.source, self.original_ident)
|
||||
return msvc32_symbolify(self.original_ident, self.ty)
|
||||
|
||||
def msvc64_symbol(self):
|
||||
return msvc64_symbolify(self.source, self.original_ident)
|
||||
return msvc64_symbolify(self.original_ident, self.ty)
|
||||
|
||||
def type(self):
|
||||
return self.source.TYPE
|
||||
return self.ty
|
||||
|
||||
def capitalized(self):
|
||||
return self.original_ident[0].upper() + self.original_ident[1:]
|
||||
def capitalized_pseudo(self):
|
||||
return self.pseudo_ident[0].upper() + self.pseudo_ident[1:]
|
||||
|
||||
def is_pseudo(self):
|
||||
return self.atom_type == "PseudoElementAtom"
|
||||
|
||||
def is_anon_box(self):
|
||||
return self.type() == "nsICSSAnonBoxPseudo"
|
||||
return self.is_non_inheriting_anon_box() or self.is_inheriting_anon_box()
|
||||
|
||||
def is_non_inheriting_anon_box(self):
|
||||
return self.macro == "CSS_NON_INHERITING_ANON_BOX"
|
||||
return self.atom_type == "NonInheritingAnonBoxAtom"
|
||||
|
||||
def is_inheriting_anon_box(self):
|
||||
return (self.macro == "CSS_ANON_BOX" or
|
||||
self.macro == "CSS_WRAPPER_ANON_BOX")
|
||||
return self.atom_type == "InheritingAnonBoxAtom"
|
||||
|
||||
def is_tree_pseudo_element(self):
|
||||
return self.value.startswith(":-moz-tree-")
|
||||
|
@ -122,13 +101,13 @@ class Atom:
|
|||
|
||||
def collect_atoms(objdir):
|
||||
atoms = []
|
||||
for source in SOURCES:
|
||||
path = os.path.abspath(os.path.join(objdir, source.FILE))
|
||||
print("cargo:rerun-if-changed={}".format(path))
|
||||
with open(path) as f:
|
||||
content = f.read()
|
||||
for result in source.PATTERN.finditer(content):
|
||||
atoms.append(Atom(source, result.group(1), result.group(2), result.group(3)))
|
||||
path = os.path.abspath(os.path.join(objdir, FILE))
|
||||
print("cargo:rerun-if-changed={}".format(path))
|
||||
with open(path) as f:
|
||||
content = f.read()
|
||||
for result in PATTERN.finditer(content):
|
||||
atoms.append(Atom(result.group(1), result.group(2), result.group(3),
|
||||
result.group(4), result.group(5)))
|
||||
return atoms
|
||||
|
||||
|
||||
|
@ -219,9 +198,9 @@ def write_atom_macro(atoms, file_name):
|
|||
f.write(PRELUDE)
|
||||
f.write(IMPORTS)
|
||||
|
||||
for source in SOURCES:
|
||||
if source.TYPE != "nsStaticAtom":
|
||||
f.write("pub enum {} {{}}\n\n".format(source.TYPE))
|
||||
for ty in sorted(set([atom.type() for atom in atoms])):
|
||||
if ty != "nsStaticAtom":
|
||||
f.write("pub enum {} {{}}\n\n".format(ty))
|
||||
|
||||
f.write(UNSAFE_STATIC)
|
||||
|
||||
|
|
|
@ -51,10 +51,6 @@ impl GeckoElementSnapshot {
|
|||
(self.mContains as u8 & flags as u8) != 0
|
||||
}
|
||||
|
||||
fn as_ptr(&self) -> *const Self {
|
||||
self
|
||||
}
|
||||
|
||||
/// Returns true if the snapshot has stored state for pseudo-classes
|
||||
/// that depend on things other than `ElementState`.
|
||||
#[inline]
|
||||
|
@ -184,14 +180,7 @@ impl ElementSnapshot for GeckoElementSnapshot {
|
|||
return None;
|
||||
}
|
||||
|
||||
let ptr = unsafe { bindings::Gecko_SnapshotAtomAttrValue(self, atom!("id").as_ptr()) };
|
||||
|
||||
// FIXME(emilio): This should assert, since this flag is exact.
|
||||
if ptr.is_null() {
|
||||
None
|
||||
} else {
|
||||
Some(unsafe { WeakAtom::new(ptr) })
|
||||
}
|
||||
snapshot_helpers::get_id(&*self.mAttrs)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
@ -200,12 +189,7 @@ impl ElementSnapshot for GeckoElementSnapshot {
|
|||
return false;
|
||||
}
|
||||
|
||||
snapshot_helpers::has_class(
|
||||
self.as_ptr(),
|
||||
name,
|
||||
case_sensitivity,
|
||||
bindings::Gecko_SnapshotHasClass,
|
||||
)
|
||||
snapshot_helpers::has_class(name, case_sensitivity, &self.mClass)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
@ -217,11 +201,7 @@ impl ElementSnapshot for GeckoElementSnapshot {
|
|||
return;
|
||||
}
|
||||
|
||||
snapshot_helpers::each_class(
|
||||
self.as_ptr(),
|
||||
callback,
|
||||
bindings::Gecko_SnapshotClassOrClassList,
|
||||
)
|
||||
snapshot_helpers::each_class(&self.mClass, callback)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
|
|
@ -4,58 +4,114 @@
|
|||
|
||||
//! Element an snapshot common logic.
|
||||
|
||||
use gecko_bindings::structs::nsAtom;
|
||||
use CaseSensitivityExt;
|
||||
use gecko_bindings::bindings;
|
||||
use gecko_bindings::structs::{self, nsAtom};
|
||||
use selectors::attr::CaseSensitivity;
|
||||
use std::{ptr, slice};
|
||||
use string_cache::Atom;
|
||||
use string_cache::{Atom, WeakAtom};
|
||||
|
||||
/// A function that, given an element of type `T`, allows you to get a single
|
||||
/// class or a class list.
|
||||
pub type ClassOrClassList<T> =
|
||||
unsafe extern "C" fn(T, *mut *mut nsAtom, *mut *mut *mut nsAtom) -> u32;
|
||||
enum Class<'a> {
|
||||
None,
|
||||
One(*const nsAtom),
|
||||
More(&'a [structs::RefPtr<nsAtom>]),
|
||||
}
|
||||
|
||||
/// 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)]
|
||||
pub fn has_class<T>(
|
||||
item: T,
|
||||
fn base_type(attr: &structs::nsAttrValue) -> structs::nsAttrValue_ValueBaseType {
|
||||
(attr.mBits & structs::NS_ATTRVALUE_BASETYPE_MASK) as structs::nsAttrValue_ValueBaseType
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
unsafe fn ptr<T>(attr: &structs::nsAttrValue) -> *const T {
|
||||
(attr.mBits & !structs::NS_ATTRVALUE_BASETYPE_MASK) as *const T
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
unsafe fn get_class_from_attr(attr: &structs::nsAttrValue) -> Class {
|
||||
debug_assert!(bindings::Gecko_AssertClassAttrValueIsSane(attr));
|
||||
let base_type = base_type(attr);
|
||||
if base_type == structs::nsAttrValue_ValueBaseType_eStringBase {
|
||||
return Class::None;
|
||||
}
|
||||
if base_type == structs::nsAttrValue_ValueBaseType_eAtomBase {
|
||||
return Class::One(ptr::<nsAtom>(attr));
|
||||
}
|
||||
debug_assert_eq!(base_type, structs::nsAttrValue_ValueBaseType_eOtherBase);
|
||||
|
||||
let container = ptr::<structs::MiscContainer>(attr);
|
||||
debug_assert_eq!((*container).mType, structs::nsAttrValue_ValueType_eAtomArray);
|
||||
let array =
|
||||
(*container).__bindgen_anon_1.mValue.as_ref().__bindgen_anon_1.mAtomArray.as_ref();
|
||||
Class::More(&***array)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
unsafe fn get_id_from_attr(attr: &structs::nsAttrValue) -> &WeakAtom {
|
||||
debug_assert_eq!(base_type(attr), structs::nsAttrValue_ValueBaseType_eAtomBase);
|
||||
WeakAtom::new(ptr::<nsAtom>(attr))
|
||||
}
|
||||
|
||||
/// 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,
|
||||
case_sensitivity: CaseSensitivity,
|
||||
getter: HasClass<T>,
|
||||
attr: &structs::nsAttrValue,
|
||||
) -> bool {
|
||||
let ignore_case = match case_sensitivity {
|
||||
CaseSensitivity::CaseSensitive => false,
|
||||
CaseSensitivity::AsciiCaseInsensitive => true,
|
||||
};
|
||||
|
||||
unsafe { getter(item, name.as_ptr(), ignore_case) }
|
||||
match unsafe { get_class_from_attr(attr) } {
|
||||
Class::None => false,
|
||||
Class::One(atom) => unsafe {
|
||||
case_sensitivity.eq_atom(name, WeakAtom::new(atom))
|
||||
},
|
||||
Class::More(atoms) => {
|
||||
match case_sensitivity {
|
||||
CaseSensitivity::CaseSensitive => {
|
||||
atoms.iter().any(|atom| atom.mRawPtr == name.as_ptr())
|
||||
}
|
||||
CaseSensitivity::AsciiCaseInsensitive => unsafe {
|
||||
atoms.iter().any(|atom| WeakAtom::new(atom.mRawPtr).eq_ignore_ascii_case(name))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Given an item, a callback, and a getter, execute `callback` for each class
|
||||
/// this `item` has.
|
||||
pub fn each_class<F, T>(item: T, mut callback: F, getter: ClassOrClassList<T>)
|
||||
#[inline(always)]
|
||||
pub fn each_class<F>(attr: &structs::nsAttrValue, mut callback: F)
|
||||
where
|
||||
F: FnMut(&Atom),
|
||||
{
|
||||
unsafe {
|
||||
let mut class: *mut nsAtom = ptr::null_mut();
|
||||
let mut list: *mut *mut nsAtom = ptr::null_mut();
|
||||
let length = getter(item, &mut class, &mut list);
|
||||
match length {
|
||||
0 => {},
|
||||
1 => Atom::with(class, callback),
|
||||
n => {
|
||||
let classes = slice::from_raw_parts(list, n as usize);
|
||||
for c in classes {
|
||||
Atom::with(*c, &mut callback)
|
||||
match get_class_from_attr(attr) {
|
||||
Class::None => {},
|
||||
Class::One(atom) => Atom::with(atom, callback),
|
||||
Class::More(atoms) => {
|
||||
for atom in atoms {
|
||||
Atom::with(atom.mRawPtr, &mut callback)
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,7 +32,6 @@ use gecko_bindings::bindings;
|
|||
use gecko_bindings::bindings::{Gecko_ElementState, Gecko_GetDocumentLWTheme};
|
||||
use gecko_bindings::bindings::{Gecko_GetLastChild, Gecko_GetPreviousSibling, Gecko_GetNextStyleChild};
|
||||
use gecko_bindings::bindings::{Gecko_SetNodeFlags, Gecko_UnsetNodeFlags};
|
||||
use gecko_bindings::bindings::Gecko_ClassOrClassList;
|
||||
use gecko_bindings::bindings::Gecko_ElementHasAnimations;
|
||||
use gecko_bindings::bindings::Gecko_ElementHasCSSAnimations;
|
||||
use gecko_bindings::bindings::Gecko_ElementHasCSSTransitions;
|
||||
|
@ -581,6 +580,34 @@ impl<'le> fmt::Debug for GeckoElement<'le> {
|
|||
}
|
||||
|
||||
impl<'le> GeckoElement<'le> {
|
||||
#[inline(always)]
|
||||
fn attrs(&self) -> &[structs::AttrArray_InternalAttr] {
|
||||
unsafe {
|
||||
let attrs = match self.0._base.mAttrs.mImpl.mPtr.as_ref() {
|
||||
Some(attrs) => attrs,
|
||||
None => return &[],
|
||||
};
|
||||
|
||||
attrs.mBuffer.as_slice(attrs.mAttrCount as usize)
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn get_class_attr(&self) -> Option<&structs::nsAttrValue> {
|
||||
if !self.may_have_class() {
|
||||
return None;
|
||||
}
|
||||
|
||||
if self.is_svg_element() {
|
||||
let svg_class = unsafe { bindings::Gecko_GetSVGAnimatedClass(self.0).as_ref() };
|
||||
if let Some(c) = svg_class {
|
||||
return Some(c)
|
||||
}
|
||||
}
|
||||
|
||||
snapshot_helpers::find_attr(self.attrs(), &atom!("class"))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn closest_anon_subtree_root_parent(&self) -> Option<Self> {
|
||||
debug_assert!(self.is_in_native_anonymous_subtree());
|
||||
|
@ -1281,26 +1308,19 @@ impl<'le> TElement for GeckoElement<'le> {
|
|||
return None;
|
||||
}
|
||||
|
||||
let ptr = unsafe { bindings::Gecko_AtomAttrValue(self.0, atom!("id").as_ptr()) };
|
||||
|
||||
// FIXME(emilio): Pretty sure the has_id flag is exact and we could
|
||||
// assert here.
|
||||
if ptr.is_null() {
|
||||
None
|
||||
} else {
|
||||
Some(unsafe { WeakAtom::new(ptr) })
|
||||
}
|
||||
snapshot_helpers::get_id(self.attrs())
|
||||
}
|
||||
|
||||
fn each_class<F>(&self, callback: F)
|
||||
where
|
||||
F: FnMut(&Atom),
|
||||
{
|
||||
if !self.may_have_class() {
|
||||
return;
|
||||
}
|
||||
let attr = match self.get_class_attr() {
|
||||
Some(c) => c,
|
||||
None => return,
|
||||
};
|
||||
|
||||
snapshot_helpers::each_class(self.0, callback, Gecko_ClassOrClassList)
|
||||
snapshot_helpers::each_class(attr, callback)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
@ -2265,24 +2285,22 @@ impl<'le> ::selectors::Element for GeckoElement<'le> {
|
|||
return false;
|
||||
}
|
||||
|
||||
unsafe {
|
||||
let ptr = bindings::Gecko_AtomAttrValue(self.0, atom!("id").as_ptr());
|
||||
let element_id = match snapshot_helpers::get_id(self.attrs()) {
|
||||
Some(id) => id,
|
||||
None => return false,
|
||||
};
|
||||
|
||||
if ptr.is_null() {
|
||||
false
|
||||
} else {
|
||||
case_sensitivity.eq_atom(WeakAtom::new(ptr), id)
|
||||
}
|
||||
}
|
||||
case_sensitivity.eq_atom(element_id, id)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn has_class(&self, name: &Atom, case_sensitivity: CaseSensitivity) -> bool {
|
||||
if !self.may_have_class() {
|
||||
return false;
|
||||
}
|
||||
let attr = match self.get_class_attr() {
|
||||
Some(c) => c,
|
||||
None => return false,
|
||||
};
|
||||
|
||||
snapshot_helpers::has_class(self.0, name, case_sensitivity, bindings::Gecko_HasClass)
|
||||
snapshot_helpers::has_class(name, case_sensitivity, attr)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
|
|
@ -49,10 +49,6 @@ pub struct Atom(*mut WeakAtom);
|
|||
/// where `'a` is the lifetime of something that holds a strong reference to that atom.
|
||||
pub struct WeakAtom(nsAtom);
|
||||
|
||||
/// A BorrowedAtom for Gecko is just a weak reference to a `nsAtom`, that
|
||||
/// hasn't been bumped.
|
||||
pub type BorrowedAtom<'a> = &'a WeakAtom;
|
||||
|
||||
impl Deref for Atom {
|
||||
type Target = WeakAtom;
|
||||
|
||||
|
@ -267,7 +263,7 @@ impl fmt::Display for WeakAtom {
|
|||
|
||||
impl Atom {
|
||||
/// Execute a callback with the atom represented by `ptr`.
|
||||
pub unsafe fn with<F, R>(ptr: *mut nsAtom, callback: F) -> R
|
||||
pub unsafe fn with<F, R>(ptr: *const nsAtom, callback: F) -> R
|
||||
where
|
||||
F: FnOnce(&Atom) -> R,
|
||||
{
|
||||
|
|
|
@ -24,12 +24,6 @@ use selectors::matching::matches_selector;
|
|||
use smallvec::SmallVec;
|
||||
use stylesheets::origin::{Origin, OriginSet};
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
enum VisitedDependent {
|
||||
Yes,
|
||||
No,
|
||||
}
|
||||
|
||||
/// The collector implementation.
|
||||
struct Collector<'a, 'b: 'a, 'selectors: 'a, E>
|
||||
where
|
||||
|
@ -167,8 +161,13 @@ where
|
|||
// do for this case.
|
||||
if state_changes.intersects(ElementState::IN_VISITED_OR_UNVISITED_STATE) {
|
||||
trace!(" > visitedness change, force subtree restyle");
|
||||
// If we get here with visited links disabled, we should probably
|
||||
// just avoid the restyle and remove the state change here, not only
|
||||
// as an optimization, but also because it kind of would kill the
|
||||
// point of disabling visited links.
|
||||
debug_assert!(self.shared_context.visited_styles_enabled);
|
||||
// We can't just return here because there may also be attribute
|
||||
// changes as well that imply additional hints.
|
||||
// changes as well that imply additional hints for siblings.
|
||||
self.data.hint.insert(RestyleHint::restyle_subtree());
|
||||
}
|
||||
|
||||
|
@ -347,7 +346,7 @@ where
|
|||
if let Some(ref id) = removed_id {
|
||||
if let Some(deps) = map.id_to_selector.get(id, quirks_mode) {
|
||||
for dep in deps {
|
||||
self.scan_dependency(dep, VisitedDependent::No);
|
||||
self.scan_dependency(dep);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -356,7 +355,7 @@ where
|
|||
if let Some(ref id) = added_id {
|
||||
if let Some(deps) = map.id_to_selector.get(id, quirks_mode) {
|
||||
for dep in deps {
|
||||
self.scan_dependency(dep, VisitedDependent::No);
|
||||
self.scan_dependency(dep);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -364,7 +363,7 @@ where
|
|||
for class in self.classes_added.iter().chain(self.classes_removed.iter()) {
|
||||
if let Some(deps) = map.class_to_selector.get(class, quirks_mode) {
|
||||
for dep in deps {
|
||||
self.scan_dependency(dep, VisitedDependent::No);
|
||||
self.scan_dependency(dep);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -390,7 +389,7 @@ where
|
|||
self.removed_id,
|
||||
self.classes_removed,
|
||||
|dependency| {
|
||||
self.scan_dependency(dependency, VisitedDependent::No);
|
||||
self.scan_dependency(dependency);
|
||||
true
|
||||
},
|
||||
);
|
||||
|
@ -410,98 +409,53 @@ where
|
|||
if !dependency.state.intersects(state_changes) {
|
||||
return true;
|
||||
}
|
||||
let visited_dependent = if dependency
|
||||
.state
|
||||
.intersects(ElementState::IN_VISITED_OR_UNVISITED_STATE)
|
||||
{
|
||||
VisitedDependent::Yes
|
||||
} else {
|
||||
VisitedDependent::No
|
||||
};
|
||||
self.scan_dependency(&dependency.dep, visited_dependent);
|
||||
self.scan_dependency(&dependency.dep);
|
||||
true
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// Check whether a dependency should be taken into account, using a given
|
||||
/// visited handling mode.
|
||||
/// Check whether a dependency should be taken into account.
|
||||
fn check_dependency(
|
||||
&mut self,
|
||||
visited_handling_mode: VisitedHandlingMode,
|
||||
dependency: &Dependency,
|
||||
) -> bool {
|
||||
let element = &self.element;
|
||||
let wrapper = &self.wrapper;
|
||||
self.matching_context
|
||||
.with_visited_handling_mode(visited_handling_mode, |mut context| {
|
||||
let matches_now = matches_selector(
|
||||
&dependency.selector,
|
||||
dependency.selector_offset,
|
||||
None,
|
||||
element,
|
||||
&mut context,
|
||||
&mut |_, _| {},
|
||||
);
|
||||
let matches_now = matches_selector(
|
||||
&dependency.selector,
|
||||
dependency.selector_offset,
|
||||
None,
|
||||
element,
|
||||
&mut self.matching_context,
|
||||
&mut |_, _| {},
|
||||
);
|
||||
|
||||
let matched_then = matches_selector(
|
||||
&dependency.selector,
|
||||
dependency.selector_offset,
|
||||
None,
|
||||
wrapper,
|
||||
&mut context,
|
||||
&mut |_, _| {},
|
||||
);
|
||||
let matched_then = matches_selector(
|
||||
&dependency.selector,
|
||||
dependency.selector_offset,
|
||||
None,
|
||||
wrapper,
|
||||
&mut self.matching_context,
|
||||
&mut |_, _| {},
|
||||
);
|
||||
|
||||
matched_then != matches_now
|
||||
})
|
||||
matched_then != matches_now
|
||||
}
|
||||
|
||||
fn scan_dependency(
|
||||
&mut self,
|
||||
dependency: &'selectors Dependency,
|
||||
is_visited_dependent: VisitedDependent,
|
||||
) {
|
||||
fn scan_dependency(&mut self, dependency: &'selectors Dependency) {
|
||||
debug!(
|
||||
"TreeStyleInvalidator::scan_dependency({:?}, {:?}, {:?})",
|
||||
self.element, dependency, is_visited_dependent,
|
||||
"TreeStyleInvalidator::scan_dependency({:?}, {:?})",
|
||||
self.element, dependency
|
||||
);
|
||||
|
||||
if !self.dependency_may_be_relevant(dependency) {
|
||||
return;
|
||||
}
|
||||
|
||||
let should_account_for_dependency =
|
||||
self.check_dependency(VisitedHandlingMode::AllLinksVisitedAndUnvisited, dependency);
|
||||
|
||||
if should_account_for_dependency {
|
||||
if self.check_dependency(dependency) {
|
||||
return self.note_dependency(dependency);
|
||||
}
|
||||
|
||||
// If there is a relevant link, then we also matched in visited
|
||||
// mode.
|
||||
//
|
||||
// Match again in this mode to ensure this also matches.
|
||||
//
|
||||
// Note that we never actually match directly against the element's true
|
||||
// visited state at all, since that would expose us to timing attacks.
|
||||
//
|
||||
// The matching process only considers the relevant link state and
|
||||
// visited handling mode when deciding if visited matches. Instead, we
|
||||
// are rematching here in case there is some :visited selector whose
|
||||
// matching result changed for some other state or attribute change of
|
||||
// this element (for example, for things like [foo]:visited).
|
||||
//
|
||||
// NOTE: This thing is actually untested because testing it is flaky,
|
||||
// see the tests that were added and then backed out in bug 1328509.
|
||||
if is_visited_dependent == VisitedDependent::Yes && self.element.is_link() {
|
||||
let should_account_for_dependency =
|
||||
self.check_dependency(VisitedHandlingMode::RelevantLinkVisited, dependency);
|
||||
|
||||
if should_account_for_dependency {
|
||||
return self.note_dependency(dependency);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn note_dependency(&mut self, dependency: &'selectors Dependency) {
|
||||
|
|
|
@ -67,6 +67,8 @@ extern crate matches;
|
|||
pub extern crate nsstring;
|
||||
#[cfg(feature = "gecko")]
|
||||
extern crate num_cpus;
|
||||
#[macro_use]
|
||||
extern crate num_derive;
|
||||
extern crate num_integer;
|
||||
extern crate num_traits;
|
||||
extern crate ordered_float;
|
||||
|
@ -128,15 +130,13 @@ pub mod font_face;
|
|||
pub mod font_metrics;
|
||||
#[cfg(feature = "gecko")]
|
||||
#[allow(unsafe_code)]
|
||||
pub mod gecko;
|
||||
#[cfg(feature = "gecko")]
|
||||
#[allow(unsafe_code)]
|
||||
pub mod gecko_bindings;
|
||||
pub mod hash;
|
||||
pub mod invalidation;
|
||||
#[allow(missing_docs)] // TODO.
|
||||
pub mod logical_geometry;
|
||||
pub mod matching;
|
||||
#[macro_use]
|
||||
pub mod media_queries;
|
||||
pub mod parallel;
|
||||
pub mod parser;
|
||||
|
@ -190,11 +190,16 @@ pub mod properties {
|
|||
include!(concat!(env!("OUT_DIR"), "/properties.rs"));
|
||||
}
|
||||
|
||||
#[cfg(feature = "gecko")]
|
||||
#[allow(unsafe_code)]
|
||||
pub mod gecko;
|
||||
|
||||
// uses a macro from properties
|
||||
#[cfg(feature = "servo")]
|
||||
#[allow(unsafe_code)]
|
||||
pub mod servo;
|
||||
|
||||
|
||||
#[cfg(feature = "gecko")]
|
||||
#[allow(unsafe_code, missing_docs)]
|
||||
pub mod gecko_properties {
|
||||
|
|
|
@ -13,7 +13,6 @@ use std::fmt::{self, Write};
|
|||
use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss};
|
||||
use super::{Device, MediaFeatureExpression};
|
||||
|
||||
|
||||
/// A binary `and` or `or` operator.
|
||||
#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, Parse, PartialEq, ToCss)]
|
||||
#[allow(missing_docs)]
|
||||
|
|
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_list;
|
||||
mod media_query;
|
||||
#[macro_use]
|
||||
pub mod media_feature;
|
||||
pub mod media_feature_expression;
|
||||
|
||||
pub use self::media_condition::MediaCondition;
|
||||
pub use self::media_list::MediaList;
|
||||
pub use self::media_query::{MediaQuery, MediaQueryType, MediaType, Qualifier};
|
||||
pub use self::media_feature_expression::MediaFeatureExpression;
|
||||
|
||||
#[cfg(feature = "servo")]
|
||||
pub use servo::media_queries::{Device, MediaFeatureExpression};
|
||||
pub use servo::media_queries::Device;
|
||||
#[cfg(feature = "gecko")]
|
||||
pub use gecko::media_queries::{Device, MediaFeatureExpression};
|
||||
pub use gecko::media_queries::Device;
|
||||
|
|
|
@ -290,14 +290,25 @@ class Longhand(object):
|
|||
"AlignContent",
|
||||
"AlignItems",
|
||||
"AlignSelf",
|
||||
"Appearance",
|
||||
"BackgroundRepeat",
|
||||
"BorderImageRepeat",
|
||||
"BorderStyle",
|
||||
"Clear",
|
||||
"ColumnCount",
|
||||
"Contain",
|
||||
"Display",
|
||||
"Float",
|
||||
"FontSizeAdjust",
|
||||
"FontStretch",
|
||||
"FontStyle",
|
||||
"FontStyleAdjust",
|
||||
"FontSynthesis",
|
||||
"FontVariantEastAsian",
|
||||
"FontVariantLigatures",
|
||||
"FontVariantNumeric",
|
||||
"FontWeight",
|
||||
"GreaterThanOrEqualToOneNumber",
|
||||
"GridAutoFlow",
|
||||
"InitialLetter",
|
||||
"Integer",
|
||||
|
@ -311,17 +322,24 @@ class Longhand(object):
|
|||
"NonNegativeNumber",
|
||||
"Opacity",
|
||||
"OutlineStyle",
|
||||
"OverflowClipBox",
|
||||
"OverscrollBehavior",
|
||||
"Percentage",
|
||||
"Resize",
|
||||
"SVGOpacity",
|
||||
"SVGPaintOrder",
|
||||
"ScrollSnapType",
|
||||
"TextAlign",
|
||||
"TextDecorationLine",
|
||||
"TextEmphasisPosition",
|
||||
"TouchAction",
|
||||
"TransformStyle",
|
||||
"XSpan",
|
||||
"XTextZoom",
|
||||
"ZIndex",
|
||||
}
|
||||
if self.name == "overflow-y":
|
||||
return True
|
||||
return bool(self.keyword)
|
||||
|
||||
def animated_type(self):
|
||||
|
|
|
@ -2124,7 +2124,7 @@ fn static_assert() {
|
|||
};
|
||||
|
||||
for (servo, gecko) in v.0.areas.into_iter().zip(refptr.mNamedAreas.iter_mut()) {
|
||||
gecko.mName.assign_utf8(&*servo.name);
|
||||
gecko.mName.assign_str(&*servo.name);
|
||||
gecko.mColumnStart = servo.columns.start;
|
||||
gecko.mColumnEnd = servo.columns.end;
|
||||
gecko.mRowStart = servo.rows.start;
|
||||
|
@ -2132,7 +2132,7 @@ fn static_assert() {
|
|||
}
|
||||
|
||||
for (servo, gecko) in v.0.strings.into_iter().zip(refptr.mTemplates.iter_mut()) {
|
||||
gecko.assign_utf8(&*servo);
|
||||
gecko.assign_str(&*servo);
|
||||
}
|
||||
|
||||
self.gecko.mGridTemplateAreas.set_move(refptr.get())
|
||||
|
@ -3106,6 +3106,9 @@ fn static_assert() {
|
|||
) %>
|
||||
${impl_keyword('clear', 'mBreakType', clear_keyword)}
|
||||
|
||||
<% resize_keyword = Keyword("resize", "None Both Horizontal Vertical") %>
|
||||
${impl_keyword('resize', 'mResize', resize_keyword)}
|
||||
|
||||
<% overflow_x = data.longhands_by_name["overflow-x"] %>
|
||||
pub fn set_overflow_y(&mut self, v: longhands::overflow_y::computed_value::T) {
|
||||
use properties::longhands::overflow_x::computed_value::T as BaseType;
|
||||
|
@ -4186,8 +4189,8 @@ fn static_assert() {
|
|||
};
|
||||
|
||||
for (servo, gecko) in other.0.into_iter().zip(refptr.mQuotePairs.iter_mut()) {
|
||||
gecko.first.assign_utf8(&servo.0);
|
||||
gecko.second.assign_utf8(&servo.1);
|
||||
gecko.first.assign_str(&servo.0);
|
||||
gecko.second.assign_str(&servo.1);
|
||||
}
|
||||
|
||||
self.gecko.mQuotes.set_move(refptr.get())
|
||||
|
@ -4725,7 +4728,7 @@ fn static_assert() {
|
|||
(structs::NS_STYLE_TEXT_EMPHASIS_STYLE_STRING, &**s)
|
||||
},
|
||||
};
|
||||
self.gecko.mTextEmphasisStyleString.assign_utf8(s);
|
||||
self.gecko.mTextEmphasisStyleString.assign_str(s);
|
||||
self.gecko.mTextEmphasisStyle = te as u8;
|
||||
}
|
||||
|
||||
|
@ -4826,7 +4829,7 @@ fn static_assert() {
|
|||
TextOverflowSide::Clip => structs::NS_STYLE_TEXT_OVERFLOW_CLIP,
|
||||
TextOverflowSide::Ellipsis => structs::NS_STYLE_TEXT_OVERFLOW_ELLIPSIS,
|
||||
TextOverflowSide::String(ref s) => {
|
||||
side.mString.assign_utf8(s);
|
||||
side.mString.assign_str(s);
|
||||
structs::NS_STYLE_TEXT_OVERFLOW_STRING
|
||||
}
|
||||
};
|
||||
|
@ -4960,7 +4963,7 @@ fn static_assert() {
|
|||
ShapeSource::ImageOrUrl(image) => {
|
||||
unsafe {
|
||||
bindings::Gecko_NewShapeImage(${ident});
|
||||
let style_image = &mut *${ident}.mShapeImage.mPtr;
|
||||
let style_image = &mut *${ident}.__bindgen_anon_1.mShapeImage.as_mut().mPtr;
|
||||
style_image.set(image);
|
||||
}
|
||||
}
|
||||
|
@ -4980,7 +4983,7 @@ fn static_assert() {
|
|||
// Create StyleBasicShape in StyleShapeSource. mReferenceBox and mType
|
||||
// will be set manually later.
|
||||
Gecko_NewBasicShape(${ident}, basic_shape_type);
|
||||
&mut *${ident}.mBasicShape.mPtr
|
||||
&mut *${ident}.__bindgen_anon_1.mBasicShape.as_mut().mPtr
|
||||
}
|
||||
}
|
||||
match servo_shape {
|
||||
|
@ -5458,7 +5461,7 @@ clip-path
|
|||
};
|
||||
counter_func.mIdent.assign(name.0.as_slice());
|
||||
if content_type == StyleContentType::Counters {
|
||||
counter_func.mSeparator.assign_utf8(sep);
|
||||
counter_func.mSeparator.assign_str(sep);
|
||||
}
|
||||
style.to_gecko_value(&mut counter_func.mCounterStyle, device);
|
||||
}
|
||||
|
|
|
@ -422,17 +422,21 @@ ${helpers.single_keyword("page-break-inside",
|
|||
|
||||
// CSS Basic User Interface Module Level 3
|
||||
// http://dev.w3.org/csswg/css-ui
|
||||
// FIXME support logical values `block` and `inline` (https://drafts.csswg.org/css-logical-props/#resize)
|
||||
//
|
||||
// This is APPLIES_TO_PLACEHOLDER so we can override, in the UA sheet, the
|
||||
// 'resize' property we'd inherit from textarea otherwise. Basically, just
|
||||
// makes the UA rules easier to write.
|
||||
${helpers.single_keyword("resize",
|
||||
"none both horizontal vertical",
|
||||
products="gecko",
|
||||
spec="https://drafts.csswg.org/css-ui/#propdef-resize",
|
||||
flags="APPLIES_TO_PLACEHOLDER",
|
||||
animation_value_type="discrete")}
|
||||
${helpers.predefined_type(
|
||||
"resize",
|
||||
"Resize",
|
||||
"computed::Resize::None",
|
||||
products="gecko",
|
||||
animation_value_type="discrete",
|
||||
needs_context=False,
|
||||
gecko_ffi_name="mResize",
|
||||
flags="APPLIES_TO_PLACEHOLDER",
|
||||
spec="https://drafts.csswg.org/css-ui/#propdef-resize",
|
||||
)}
|
||||
|
||||
${helpers.predefined_type(
|
||||
"perspective",
|
||||
|
@ -448,7 +452,7 @@ ${helpers.predefined_type(
|
|||
|
||||
${helpers.predefined_type(
|
||||
"perspective-origin",
|
||||
"position::Position",
|
||||
"Position",
|
||||
"computed::position::Position::center()",
|
||||
boxed=True,
|
||||
extra_prefixes=transform_extra_prefixes,
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
<%namespace name="helpers" file="/helpers.mako.rs" />
|
||||
|
||||
<% data.new_style_struct("InheritedUI", inherited=True, gecko_name="UserInterface") %>
|
||||
<% data.new_style_struct("InheritedUI", inherited=True, gecko_name="UI") %>
|
||||
|
||||
${helpers.predefined_type("cursor",
|
||||
"Cursor",
|
||||
|
|
|
@ -16,6 +16,17 @@ ${helpers.single_keyword("ime-mode", "auto normal active disabled inactive",
|
|||
animation_value_type="discrete",
|
||||
spec="https://drafts.csswg.org/css-ui/#input-method-editor")}
|
||||
|
||||
${helpers.single_keyword(
|
||||
"scrollbar-width",
|
||||
"auto thin none",
|
||||
products="gecko",
|
||||
gecko_enum_prefix="StyleScrollbarWidth",
|
||||
animation_value_type="discrete",
|
||||
gecko_pref="layout.css.scrollbar-width.enabled",
|
||||
enabled_in="chrome",
|
||||
spec="https://drafts.csswg.org/css-scrollbars-1/#scrollbar-width"
|
||||
)}
|
||||
|
||||
${helpers.single_keyword("-moz-user-select", "auto text none all element elements" +
|
||||
" toggle tri-state -moz-all -moz-text",
|
||||
products="gecko",
|
||||
|
|
|
@ -305,6 +305,21 @@ impl Clone for PropertyDeclaration {
|
|||
}
|
||||
}
|
||||
|
||||
// This function ensures that all properties not handled above
|
||||
// do not have a specified value implements Copy. If you hit
|
||||
// compile error here, you may want to add the type name into
|
||||
// Longhand.specified_is_copy in data.py.
|
||||
fn _static_assert_others_are_not_copy() {
|
||||
struct Helper<T>(T);
|
||||
trait AssertCopy { fn assert() {} }
|
||||
trait AssertNotCopy { fn assert() {} }
|
||||
impl<T: Copy> AssertCopy for Helper<T> {}
|
||||
% for ty in set(x["type"] for x in others):
|
||||
impl AssertNotCopy for Helper<${ty}> {}
|
||||
Helper::<${ty}>::assert();
|
||||
% endfor
|
||||
}
|
||||
|
||||
match *self {
|
||||
${" |\n".join("{}(..)".format(v["name"]) for v in copy)} => {
|
||||
unsafe { debug_unreachable!() }
|
||||
|
@ -588,16 +603,17 @@ impl NonCustomPropertyId {
|
|||
|
||||
/// See PropertyId::collect_property_completion_keywords.
|
||||
fn collect_property_completion_keywords(&self, f: KeywordsCollectFn) {
|
||||
const COLLECT_FUNCTIONS: [&Fn(KeywordsCollectFn);
|
||||
fn do_nothing(_: KeywordsCollectFn) {}
|
||||
const COLLECT_FUNCTIONS: [fn(KeywordsCollectFn);
|
||||
${len(data.longhands) + len(data.shorthands)}] = [
|
||||
% for prop in data.longhands:
|
||||
&<${prop.specified_type()} as SpecifiedValueInfo>::collect_completion_keywords,
|
||||
<${prop.specified_type()} as SpecifiedValueInfo>::collect_completion_keywords,
|
||||
% endfor
|
||||
% for prop in data.shorthands:
|
||||
% if prop.name == "all":
|
||||
&|_f| {}, // 'all' accepts no value other than CSS-wide keywords
|
||||
do_nothing, // 'all' accepts no value other than CSS-wide keywords
|
||||
% else:
|
||||
&<shorthands::${prop.ident}::Longhands as SpecifiedValueInfo>::
|
||||
<shorthands::${prop.ident}::Longhands as SpecifiedValueInfo>::
|
||||
collect_completion_keywords,
|
||||
% endif
|
||||
% endfor
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
<%helpers:shorthand
|
||||
name="overflow"
|
||||
flags="SHORTHAND_IN_GETCS"
|
||||
sub_properties="overflow-x overflow-y"
|
||||
sub_properties="overflow-y overflow-x"
|
||||
spec="https://drafts.csswg.org/css-overflow/#propdef-overflow"
|
||||
>
|
||||
use properties::longhands::overflow_x::parse as parse_overflow;
|
||||
|
@ -20,35 +20,41 @@
|
|||
input: &mut Parser<'i, 't>,
|
||||
) -> Result<Longhands, ParseError<'i>> {
|
||||
% if product == "gecko":
|
||||
let moz_kw_found = input.try(|input| {
|
||||
try_match_ident_ignore_ascii_case! { input,
|
||||
"-moz-scrollbars-horizontal" => {
|
||||
Ok(expanded! {
|
||||
overflow_x: SpecifiedValue::Scroll,
|
||||
overflow_y: SpecifiedValue::Hidden,
|
||||
})
|
||||
}
|
||||
"-moz-scrollbars-vertical" => {
|
||||
Ok(expanded! {
|
||||
overflow_x: SpecifiedValue::Hidden,
|
||||
overflow_y: SpecifiedValue::Scroll,
|
||||
})
|
||||
}
|
||||
"-moz-scrollbars-none" => {
|
||||
Ok(expanded! {
|
||||
overflow_x: SpecifiedValue::Hidden,
|
||||
overflow_y: SpecifiedValue::Hidden,
|
||||
})
|
||||
use gecko_bindings::structs;
|
||||
let moz_kw_enabled = unsafe {
|
||||
structs::StaticPrefs_sVarCache_layout_css_overflow_moz_scrollbars_enabled
|
||||
};
|
||||
if moz_kw_enabled {
|
||||
let moz_kw_found = input.try(|input| {
|
||||
try_match_ident_ignore_ascii_case! { input,
|
||||
"-moz-scrollbars-horizontal" => {
|
||||
Ok(expanded! {
|
||||
overflow_x: SpecifiedValue::Scroll,
|
||||
overflow_y: SpecifiedValue::Hidden,
|
||||
})
|
||||
}
|
||||
"-moz-scrollbars-vertical" => {
|
||||
Ok(expanded! {
|
||||
overflow_x: SpecifiedValue::Hidden,
|
||||
overflow_y: SpecifiedValue::Scroll,
|
||||
})
|
||||
}
|
||||
"-moz-scrollbars-none" => {
|
||||
Ok(expanded! {
|
||||
overflow_x: SpecifiedValue::Hidden,
|
||||
overflow_y: SpecifiedValue::Hidden,
|
||||
})
|
||||
}
|
||||
}
|
||||
});
|
||||
if moz_kw_found.is_ok() {
|
||||
return moz_kw_found
|
||||
}
|
||||
});
|
||||
if moz_kw_found.is_ok() {
|
||||
return moz_kw_found
|
||||
}
|
||||
% endif
|
||||
let overflow_x = parse_overflow(context, input)?;
|
||||
let overflow_y =
|
||||
input.try(|i| parse_overflow(context, i)).unwrap_or(overflow_x);
|
||||
let overflow_y = parse_overflow(context, input)?;
|
||||
let overflow_x =
|
||||
input.try(|i| parse_overflow(context, i)).unwrap_or(overflow_y);
|
||||
Ok(expanded! {
|
||||
overflow_x: overflow_x,
|
||||
overflow_y: overflow_y,
|
||||
|
@ -57,10 +63,10 @@
|
|||
|
||||
impl<'a> ToCss for LonghandsToSerialize<'a> {
|
||||
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: fmt::Write {
|
||||
self.overflow_x.to_css(dest)?;
|
||||
self.overflow_y.to_css(dest)?;
|
||||
if self.overflow_x != self.overflow_y {
|
||||
dest.write_char(' ')?;
|
||||
self.overflow_y.to_css(dest)?;
|
||||
self.overflow_x.to_css(dest)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -5,19 +5,18 @@
|
|||
//! Servo's media-query device and expression representation.
|
||||
|
||||
use app_units::Au;
|
||||
use context::QuirksMode;
|
||||
use cssparser::{Parser, RGBA};
|
||||
use cssparser::RGBA;
|
||||
use euclid::{Size2D, TypedScale, TypedSize2D};
|
||||
use media_queries::MediaType;
|
||||
use parser::ParserContext;
|
||||
use media_queries::media_feature::{AllowsRanges, ParsingRequirements};
|
||||
use media_queries::media_feature::{MediaFeatureDescription, Evaluator};
|
||||
use media_queries::media_feature_expression::RangeOrOperator;
|
||||
use properties::ComputedValues;
|
||||
use selectors::parser::SelectorParseErrorKind;
|
||||
use std::fmt::{self, Write};
|
||||
use std::sync::atomic::{AtomicBool, AtomicIsize, Ordering};
|
||||
use style_traits::{CSSPixel, CssWriter, DevicePixel, ParseError, ToCss};
|
||||
use style_traits::{CSSPixel, DevicePixel};
|
||||
use style_traits::viewport::ViewportConstraints;
|
||||
use values::{specified, KeyframesName};
|
||||
use values::computed::{self, ToComputedValue};
|
||||
use values::KeyframesName;
|
||||
use values::computed::CSSPixelLength;
|
||||
use values::computed::font::FontSize;
|
||||
|
||||
/// A device is a structure that represents the current media a given document
|
||||
|
@ -155,125 +154,47 @@ impl Device {
|
|||
}
|
||||
}
|
||||
|
||||
/// A expression kind servo understands and parses.
|
||||
///
|
||||
/// Only `pub` for unit testing, please don't use it directly!
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
#[cfg_attr(feature = "servo", derive(MallocSizeOf))]
|
||||
pub enum ExpressionKind {
|
||||
/// <http://dev.w3.org/csswg/mediaqueries-3/#width>
|
||||
Width(Range<specified::Length>),
|
||||
/// https://drafts.csswg.org/mediaqueries-4/#width
|
||||
fn eval_width(
|
||||
device: &Device,
|
||||
value: Option<CSSPixelLength>,
|
||||
range_or_operator: Option<RangeOrOperator>,
|
||||
) -> bool {
|
||||
RangeOrOperator::evaluate(
|
||||
range_or_operator,
|
||||
value.map(Au::from),
|
||||
device.au_viewport_size().width,
|
||||
)
|
||||
}
|
||||
|
||||
/// A single expression a per:
|
||||
///
|
||||
/// <http://dev.w3.org/csswg/mediaqueries-3/#media1>
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
#[cfg_attr(feature = "servo", derive(MallocSizeOf))]
|
||||
pub struct MediaFeatureExpression(pub ExpressionKind);
|
||||
|
||||
impl MediaFeatureExpression {
|
||||
/// The kind of expression we're, just for unit testing.
|
||||
///
|
||||
/// Eventually this will become servo-only.
|
||||
pub fn kind_for_testing(&self) -> &ExpressionKind {
|
||||
&self.0
|
||||
}
|
||||
|
||||
/// Parse a media expression of the form:
|
||||
///
|
||||
/// ```
|
||||
/// media-feature: media-value
|
||||
/// ```
|
||||
///
|
||||
/// Only supports width ranges for now.
|
||||
pub fn parse<'i, 't>(
|
||||
context: &ParserContext,
|
||||
input: &mut Parser<'i, 't>,
|
||||
) -> Result<Self, ParseError<'i>> {
|
||||
input.expect_parenthesis_block()?;
|
||||
input.parse_nested_block(|input| {
|
||||
Self::parse_in_parenthesis_block(context, input)
|
||||
})
|
||||
}
|
||||
|
||||
/// Parse a media range expression where we've already consumed the
|
||||
/// parenthesis.
|
||||
pub fn parse_in_parenthesis_block<'i, 't>(
|
||||
context: &ParserContext,
|
||||
input: &mut Parser<'i, 't>,
|
||||
) -> Result<Self, ParseError<'i>> {
|
||||
let name = input.expect_ident_cloned()?;
|
||||
input.expect_colon()?;
|
||||
// TODO: Handle other media features
|
||||
Ok(MediaFeatureExpression(match_ignore_ascii_case! { &name,
|
||||
"min-width" => {
|
||||
ExpressionKind::Width(Range::Min(specified::Length::parse_non_negative(context, input)?))
|
||||
},
|
||||
"max-width" => {
|
||||
ExpressionKind::Width(Range::Max(specified::Length::parse_non_negative(context, input)?))
|
||||
},
|
||||
"width" => {
|
||||
ExpressionKind::Width(Range::Eq(specified::Length::parse_non_negative(context, input)?))
|
||||
},
|
||||
_ => return Err(input.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name.clone())))
|
||||
}))
|
||||
}
|
||||
|
||||
/// Evaluate this expression and return whether it matches the current
|
||||
/// device.
|
||||
pub fn matches(&self, device: &Device, quirks_mode: QuirksMode) -> bool {
|
||||
let viewport_size = device.au_viewport_size();
|
||||
let value = viewport_size.width;
|
||||
match self.0 {
|
||||
ExpressionKind::Width(ref range) => {
|
||||
match range.to_computed_range(device, quirks_mode) {
|
||||
Range::Min(ref width) => value >= *width,
|
||||
Range::Max(ref width) => value <= *width,
|
||||
Range::Eq(ref width) => value == *width,
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
#[derive(Clone, Copy, Debug, FromPrimitive, Parse, ToCss)]
|
||||
#[repr(u8)]
|
||||
enum Scan {
|
||||
Progressive,
|
||||
Interlace,
|
||||
}
|
||||
|
||||
impl ToCss for MediaFeatureExpression {
|
||||
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
|
||||
where
|
||||
W: Write,
|
||||
{
|
||||
let (s, l) = match self.0 {
|
||||
ExpressionKind::Width(Range::Min(ref l)) => ("(min-width: ", l),
|
||||
ExpressionKind::Width(Range::Max(ref l)) => ("(max-width: ", l),
|
||||
ExpressionKind::Width(Range::Eq(ref l)) => ("(width: ", l),
|
||||
};
|
||||
dest.write_str(s)?;
|
||||
l.to_css(dest)?;
|
||||
dest.write_char(')')
|
||||
}
|
||||
/// https://drafts.csswg.org/mediaqueries-4/#scan
|
||||
fn eval_scan(_: &Device, _: Option<Scan>) -> bool {
|
||||
// Since we doesn't support the 'tv' media type, the 'scan' feature never
|
||||
// matches.
|
||||
false
|
||||
}
|
||||
|
||||
/// An enumeration that represents a ranged value.
|
||||
///
|
||||
/// Only public for testing, implementation details of `MediaFeatureExpression`
|
||||
/// may change for Stylo.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
#[cfg_attr(feature = "servo", derive(MallocSizeOf))]
|
||||
pub enum Range<T> {
|
||||
/// At least the inner value.
|
||||
Min(T),
|
||||
/// At most the inner value.
|
||||
Max(T),
|
||||
/// Exactly the inner value.
|
||||
Eq(T),
|
||||
}
|
||||
|
||||
impl Range<specified::Length> {
|
||||
fn to_computed_range(&self, device: &Device, quirks_mode: QuirksMode) -> Range<Au> {
|
||||
computed::Context::for_media_query_evaluation(device, quirks_mode, |context| match *self {
|
||||
Range::Min(ref width) => Range::Min(Au::from(width.to_computed_value(&context))),
|
||||
Range::Max(ref width) => Range::Max(Au::from(width.to_computed_value(&context))),
|
||||
Range::Eq(ref width) => Range::Eq(Au::from(width.to_computed_value(&context))),
|
||||
})
|
||||
}
|
||||
lazy_static! {
|
||||
/// A list with all the media features that Servo supports.
|
||||
pub static ref MEDIA_FEATURES: [MediaFeatureDescription; 2] = [
|
||||
feature!(
|
||||
atom!("width"),
|
||||
AllowsRanges::Yes,
|
||||
Evaluator::Length(eval_width),
|
||||
ParsingRequirements::empty(),
|
||||
),
|
||||
feature!(
|
||||
atom!("scan"),
|
||||
AllowsRanges::No,
|
||||
keyword_evaluator!(eval_scan, Scan),
|
||||
ParsingRequirements::empty(),
|
||||
),
|
||||
];
|
||||
}
|
||||
|
|
|
@ -1131,11 +1131,6 @@ impl Stylist {
|
|||
|
||||
let rule_hash_target = element.rule_hash_target();
|
||||
|
||||
debug!(
|
||||
"Determining if style is shareable: pseudo: {}",
|
||||
pseudo_element.is_some()
|
||||
);
|
||||
|
||||
let matches_user_rules = rule_hash_target.matches_user_and_author_rules();
|
||||
let matches_author_rules =
|
||||
matches_user_rules && self.author_styles_enabled == AuthorStylesEnabled::Yes;
|
||||
|
|
|
@ -9,6 +9,7 @@ use values::computed::length::{LengthOrPercentage, NonNegativeLength};
|
|||
use values::generics::box_::AnimationIterationCount as GenericAnimationIterationCount;
|
||||
use values::generics::box_::Perspective as GenericPerspective;
|
||||
use values::generics::box_::VerticalAlign as GenericVerticalAlign;
|
||||
use values::specified::box_ as specified;
|
||||
|
||||
pub use values::specified::box_::{AnimationName, Appearance, Contain, Display, OverflowClipBox};
|
||||
pub use values::specified::box_::{Clear as SpecifiedClear, Float as SpecifiedFloat};
|
||||
|
@ -139,3 +140,57 @@ impl ToComputedValue for SpecifiedClear {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A computed value for the `resize` property.
|
||||
#[allow(missing_docs)]
|
||||
#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, MallocSizeOf, Parse, PartialEq, ToCss)]
|
||||
pub enum Resize {
|
||||
None,
|
||||
Both,
|
||||
Horizontal,
|
||||
Vertical,
|
||||
}
|
||||
|
||||
impl ToComputedValue for specified::Resize {
|
||||
type ComputedValue = Resize;
|
||||
|
||||
#[inline]
|
||||
fn to_computed_value(&self, context: &Context) -> Resize {
|
||||
let is_vertical = context.style().writing_mode.is_vertical();
|
||||
match self {
|
||||
specified::Resize::Inline => {
|
||||
context.rule_cache_conditions.borrow_mut()
|
||||
.set_writing_mode_dependency(context.builder.writing_mode);
|
||||
if is_vertical {
|
||||
Resize::Vertical
|
||||
} else {
|
||||
Resize::Horizontal
|
||||
}
|
||||
}
|
||||
specified::Resize::Block => {
|
||||
context.rule_cache_conditions.borrow_mut()
|
||||
.set_writing_mode_dependency(context.builder.writing_mode);
|
||||
if is_vertical {
|
||||
Resize::Horizontal
|
||||
} else {
|
||||
Resize::Vertical
|
||||
}
|
||||
}
|
||||
specified::Resize::None => Resize::None,
|
||||
specified::Resize::Both => Resize::Both,
|
||||
specified::Resize::Horizontal => Resize::Horizontal,
|
||||
specified::Resize::Vertical => Resize::Vertical,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn from_computed_value(computed: &Resize) -> specified::Resize {
|
||||
match computed {
|
||||
Resize::None => specified::Resize::None,
|
||||
Resize::Both => specified::Resize::Both,
|
||||
Resize::Horizontal => specified::Resize::Horizontal,
|
||||
Resize::Vertical => specified::Resize::Vertical,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -44,7 +44,7 @@ pub use self::font::{FontFeatureSettings, FontVariantLigatures, FontVariantNumer
|
|||
pub use self::font::{MozScriptLevel, MozScriptMinSize, MozScriptSizeMultiplier, XLang, XTextZoom};
|
||||
pub use self::box_::{AnimationIterationCount, AnimationName, Contain, Display, TransitionProperty};
|
||||
pub use self::box_::{Appearance, Clear, Float};
|
||||
pub use self::box_::{OverflowClipBox, OverscrollBehavior, Perspective};
|
||||
pub use self::box_::{OverflowClipBox, OverscrollBehavior, Perspective, Resize};
|
||||
pub use self::box_::{ScrollSnapType, TouchAction, VerticalAlign, WillChange};
|
||||
pub use self::color::{Color, ColorPropertyValue, RGBAColor};
|
||||
pub use self::column::ColumnCount;
|
||||
|
|
|
@ -21,6 +21,12 @@ impl Resolution {
|
|||
pub fn dppx(&self) -> CSSFloat {
|
||||
self.0
|
||||
}
|
||||
|
||||
/// Return a computed `resolution` value from a dppx float value.
|
||||
#[inline]
|
||||
pub fn from_dppx(dppx: CSSFloat) -> Self {
|
||||
Resolution(dppx)
|
||||
}
|
||||
}
|
||||
|
||||
impl ToComputedValue for specified::Resolution {
|
||||
|
|
|
@ -884,6 +884,21 @@ pub enum Clear {
|
|||
InlineEnd
|
||||
}
|
||||
|
||||
/// https://drafts.csswg.org/css-ui/#propdef-resize
|
||||
#[allow(missing_docs)]
|
||||
#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, MallocSizeOf, Parse, PartialEq,
|
||||
SpecifiedValueInfo, ToCss)]
|
||||
pub enum Resize {
|
||||
None,
|
||||
Both,
|
||||
Horizontal,
|
||||
Vertical,
|
||||
// https://drafts.csswg.org/css-logical-1/#resize
|
||||
Inline,
|
||||
Block,
|
||||
}
|
||||
|
||||
/// The value for the `appearance` property.
|
||||
///
|
||||
/// https://developer.mozilla.org/en-US/docs/Web/CSS/-moz-appearance
|
||||
|
|
|
@ -1359,7 +1359,7 @@ impl VariantEastAsian {
|
|||
impl_gecko_keyword_conversions!(VariantEastAsian, u16);
|
||||
|
||||
#[cfg_attr(feature = "gecko", derive(MallocSizeOf))]
|
||||
#[derive(Clone, Debug, PartialEq, SpecifiedValueInfo, ToCss)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, SpecifiedValueInfo, ToCss)]
|
||||
/// Allows control of glyph substitution and sizing in East Asian text.
|
||||
pub enum FontVariantEastAsian {
|
||||
/// Value variant with `variant-east-asian`
|
||||
|
@ -1570,7 +1570,7 @@ impl VariantLigatures {
|
|||
impl_gecko_keyword_conversions!(VariantLigatures, u16);
|
||||
|
||||
#[cfg_attr(feature = "gecko", derive(MallocSizeOf))]
|
||||
#[derive(Clone, Debug, PartialEq, SpecifiedValueInfo, ToCss)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, SpecifiedValueInfo, ToCss)]
|
||||
/// Ligatures and contextual forms are ways of combining glyphs
|
||||
/// to produce more harmonized forms
|
||||
pub enum FontVariantLigatures {
|
||||
|
@ -1786,7 +1786,7 @@ impl VariantNumeric {
|
|||
impl_gecko_keyword_conversions!(VariantNumeric, u8);
|
||||
|
||||
#[cfg_attr(feature = "gecko", derive(MallocSizeOf))]
|
||||
#[derive(Clone, Debug, PartialEq, SpecifiedValueInfo, ToCss)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, SpecifiedValueInfo, ToCss)]
|
||||
/// Specifies control over numerical forms.
|
||||
pub enum FontVariantNumeric {
|
||||
/// Value variant with `variant-numeric`
|
||||
|
|
|
@ -421,46 +421,34 @@ impl NoCalcLength {
|
|||
value: CSSFloat,
|
||||
unit: &str,
|
||||
) -> Result<Self, ()> {
|
||||
match_ignore_ascii_case! { unit,
|
||||
"px" => Ok(NoCalcLength::Absolute(AbsoluteLength::Px(value))),
|
||||
"in" => Ok(NoCalcLength::Absolute(AbsoluteLength::In(value))),
|
||||
"cm" => Ok(NoCalcLength::Absolute(AbsoluteLength::Cm(value))),
|
||||
"mm" => Ok(NoCalcLength::Absolute(AbsoluteLength::Mm(value))),
|
||||
"q" => Ok(NoCalcLength::Absolute(AbsoluteLength::Q(value))),
|
||||
"pt" => Ok(NoCalcLength::Absolute(AbsoluteLength::Pt(value))),
|
||||
"pc" => Ok(NoCalcLength::Absolute(AbsoluteLength::Pc(value))),
|
||||
Ok(match_ignore_ascii_case! { unit,
|
||||
"px" => NoCalcLength::Absolute(AbsoluteLength::Px(value)),
|
||||
"in" => NoCalcLength::Absolute(AbsoluteLength::In(value)),
|
||||
"cm" => NoCalcLength::Absolute(AbsoluteLength::Cm(value)),
|
||||
"mm" => NoCalcLength::Absolute(AbsoluteLength::Mm(value)),
|
||||
"q" => NoCalcLength::Absolute(AbsoluteLength::Q(value)),
|
||||
"pt" => NoCalcLength::Absolute(AbsoluteLength::Pt(value)),
|
||||
"pc" => NoCalcLength::Absolute(AbsoluteLength::Pc(value)),
|
||||
// font-relative
|
||||
"em" => Ok(NoCalcLength::FontRelative(FontRelativeLength::Em(value))),
|
||||
"ex" => Ok(NoCalcLength::FontRelative(FontRelativeLength::Ex(value))),
|
||||
"ch" => Ok(NoCalcLength::FontRelative(FontRelativeLength::Ch(value))),
|
||||
"rem" => Ok(NoCalcLength::FontRelative(FontRelativeLength::Rem(value))),
|
||||
"em" => NoCalcLength::FontRelative(FontRelativeLength::Em(value)),
|
||||
"ex" => NoCalcLength::FontRelative(FontRelativeLength::Ex(value)),
|
||||
"ch" => NoCalcLength::FontRelative(FontRelativeLength::Ch(value)),
|
||||
"rem" => NoCalcLength::FontRelative(FontRelativeLength::Rem(value)),
|
||||
// viewport percentages
|
||||
"vw" => {
|
||||
if context.in_page_rule() {
|
||||
return Err(())
|
||||
}
|
||||
Ok(NoCalcLength::ViewportPercentage(ViewportPercentageLength::Vw(value)))
|
||||
},
|
||||
"vh" => {
|
||||
if context.in_page_rule() {
|
||||
return Err(())
|
||||
}
|
||||
Ok(NoCalcLength::ViewportPercentage(ViewportPercentageLength::Vh(value)))
|
||||
},
|
||||
"vmin" => {
|
||||
if context.in_page_rule() {
|
||||
return Err(())
|
||||
}
|
||||
Ok(NoCalcLength::ViewportPercentage(ViewportPercentageLength::Vmin(value)))
|
||||
},
|
||||
"vmax" => {
|
||||
if context.in_page_rule() {
|
||||
return Err(())
|
||||
}
|
||||
Ok(NoCalcLength::ViewportPercentage(ViewportPercentageLength::Vmax(value)))
|
||||
},
|
||||
_ => Err(())
|
||||
}
|
||||
"vw" if !context.in_page_rule() => {
|
||||
NoCalcLength::ViewportPercentage(ViewportPercentageLength::Vw(value))
|
||||
}
|
||||
"vh" if !context.in_page_rule() => {
|
||||
NoCalcLength::ViewportPercentage(ViewportPercentageLength::Vh(value))
|
||||
}
|
||||
"vmin" if !context.in_page_rule() => {
|
||||
NoCalcLength::ViewportPercentage(ViewportPercentageLength::Vmin(value))
|
||||
}
|
||||
"vmax" if !context.in_page_rule() => {
|
||||
NoCalcLength::ViewportPercentage(ViewportPercentageLength::Vmax(value))
|
||||
}
|
||||
_ => return Err(())
|
||||
})
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
|
|
@ -39,7 +39,7 @@ pub use self::font::{FontFeatureSettings, FontVariantLigatures, FontVariantNumer
|
|||
pub use self::font::{MozScriptLevel, MozScriptMinSize, MozScriptSizeMultiplier, XLang, XTextZoom};
|
||||
pub use self::box_::{AnimationIterationCount, AnimationName, Contain, Display};
|
||||
pub use self::box_::{Appearance, Clear, Float};
|
||||
pub use self::box_::{OverflowClipBox, OverscrollBehavior, Perspective};
|
||||
pub use self::box_::{OverflowClipBox, OverscrollBehavior, Perspective, Resize};
|
||||
pub use self::box_::{ScrollSnapType, TouchAction, TransitionProperty, VerticalAlign, WillChange};
|
||||
pub use self::color::{Color, ColorPropertyValue, RGBAColor};
|
||||
pub use self::counters::{Content, ContentItem, CounterIncrement, CounterReset};
|
||||
|
|
|
@ -744,7 +744,7 @@ pub enum TextEmphasisVerticalWritingModeValue {
|
|||
}
|
||||
|
||||
/// Specified value of `text-emphasis-position` property.
|
||||
#[derive(Clone, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo,
|
||||
#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo,
|
||||
ToComputedValue, ToCss)]
|
||||
pub struct TextEmphasisPosition(
|
||||
pub TextEmphasisHorizontalWritingModeValue,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue