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:
bors-servo 2018-08-19 07:57:53 -04:00 committed by GitHub
commit e7791f9a00
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
43 changed files with 1930 additions and 1348 deletions

View file

@ -66,6 +66,7 @@ reftest-wait
reset
right
sans-serif
scan
screen
scroll-position
search
@ -86,3 +87,4 @@ url
waiting
webglcontextcreationerror
week
width

View file

@ -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"

View file

@ -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)));
}
}

View file

@ -9,7 +9,6 @@
extern crate bitflags;
#[macro_use]
extern crate cssparser;
extern crate fnv;
extern crate fxhash;
#[macro_use]
extern crate log;

View file

@ -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"

View file

@ -22,5 +22,5 @@ derive_helper_methods = true
[export]
prefix = "Style"
include = ["StyleDisplay", "StyleAppearance"]
include = ["StyleDisplay", "StyleAppearance", "StyleDisplayMode"]
item_types = ["enums"]

View file

@ -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)
},

View file

@ -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.

View 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(),
),
];
}

View file

@ -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
}
}
}
}
}
}

View file

@ -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;

View file

@ -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

View file

@ -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)

View file

@ -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]

View file

@ -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)
}
},
}
}
}
}

View file

@ -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]

View file

@ -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,
{

View file

@ -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) {

View file

@ -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 {

View file

@ -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)]

View 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()
}
}

View 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()))
}
})
}
}

View file

@ -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;

View file

@ -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):

View file

@ -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);
}

View file

@ -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,

View file

@ -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",

View file

@ -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",

View file

@ -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

View file

@ -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(())
}

View file

@ -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(),
),
];
}

View file

@ -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;

View file

@ -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,
}
}
}

View file

@ -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;

View file

@ -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 {

View file

@ -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

View file

@ -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`

View file

@ -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]

View file

@ -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};

View file

@ -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,