mirror of
https://github.com/servo/servo.git
synced 2025-08-06 14:10:11 +01:00
Auto merge of #15077 - emilio:stylo-medialist, r=heycam,Manishearth,upsuper
Media query parsing and evaluation in stylo using nsMediaFeatures <!-- Please describe your changes on the following line: --> Reviewed in the bug. <!-- 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/15077) <!-- Reviewable:end -->
This commit is contained in:
commit
1755ad7b75
13 changed files with 3529 additions and 238 deletions
|
@ -99,7 +99,7 @@ mod bindings {
|
||||||
|
|
||||||
fn add_include(name: &str) -> String {
|
fn add_include(name: &str) -> String {
|
||||||
let mut added_paths = ADDED_PATHS.lock().unwrap();
|
let mut added_paths = ADDED_PATHS.lock().unwrap();
|
||||||
let file = search_include(name).unwrap();
|
let file = search_include(name).expect("Include not found!");
|
||||||
let result = String::from(file.to_str().unwrap());
|
let result = String::from(file.to_str().unwrap());
|
||||||
add_headers_recursively(file, &mut *added_paths);
|
add_headers_recursively(file, &mut *added_paths);
|
||||||
result
|
result
|
||||||
|
@ -235,6 +235,8 @@ mod bindings {
|
||||||
.include(add_include("mozilla/ServoElementSnapshot.h"))
|
.include(add_include("mozilla/ServoElementSnapshot.h"))
|
||||||
.include(add_include("mozilla/dom/Element.h"))
|
.include(add_include("mozilla/dom/Element.h"))
|
||||||
.include(add_include("mozilla/ServoBindings.h"))
|
.include(add_include("mozilla/ServoBindings.h"))
|
||||||
|
.include(add_include("nsMediaFeatures.h"))
|
||||||
|
.include(add_include("nsMediaList.h"))
|
||||||
// FIXME(emilio): Incrementally remove these "pub use"s. Probably
|
// FIXME(emilio): Incrementally remove these "pub use"s. Probably
|
||||||
// mozilla::css and mozilla::dom are easier.
|
// mozilla::css and mozilla::dom are easier.
|
||||||
.raw_line("pub use self::root::*;")
|
.raw_line("pub use self::root::*;")
|
||||||
|
@ -296,6 +298,14 @@ mod bindings {
|
||||||
"nsChangeHint",
|
"nsChangeHint",
|
||||||
"nsCSSKeyword",
|
"nsCSSKeyword",
|
||||||
"nsCSSPropertyID",
|
"nsCSSPropertyID",
|
||||||
|
"nsCSSProps",
|
||||||
|
|
||||||
|
// FIXME(emilio): These three can go away once
|
||||||
|
// https://github.com/servo/rust-bindgen/pull/397 has landed.
|
||||||
|
"nsStyleStructID",
|
||||||
|
"nsStyleAnimType",
|
||||||
|
"UseCounter",
|
||||||
|
|
||||||
"nsCSSRect",
|
"nsCSSRect",
|
||||||
"nsCSSRect_heap",
|
"nsCSSRect_heap",
|
||||||
"nsCSSShadowArray",
|
"nsCSSShadowArray",
|
||||||
|
@ -316,6 +326,10 @@ mod bindings {
|
||||||
"nsMainThreadPtrHandle",
|
"nsMainThreadPtrHandle",
|
||||||
"nsMainThreadPtrHolder",
|
"nsMainThreadPtrHolder",
|
||||||
"nsMargin",
|
"nsMargin",
|
||||||
|
"nsMediaExpression",
|
||||||
|
"nsMediaFeature",
|
||||||
|
"nsMediaFeatures",
|
||||||
|
"nsMediaList",
|
||||||
"nsRect",
|
"nsRect",
|
||||||
"nsRestyleHint",
|
"nsRestyleHint",
|
||||||
"nsresult",
|
"nsresult",
|
||||||
|
@ -376,6 +390,7 @@ mod bindings {
|
||||||
"mozilla::DefaultDelete",
|
"mozilla::DefaultDelete",
|
||||||
];
|
];
|
||||||
let opaque_types = [
|
let opaque_types = [
|
||||||
|
"std::pair__PCCP",
|
||||||
"std::namespace::atomic___base", "std::atomic__My_base",
|
"std::namespace::atomic___base", "std::atomic__My_base",
|
||||||
"nsAString_internal_char_traits",
|
"nsAString_internal_char_traits",
|
||||||
"nsAString_internal_incompatible_char_type",
|
"nsAString_internal_incompatible_char_type",
|
||||||
|
@ -401,7 +416,6 @@ mod bindings {
|
||||||
// for clang.
|
// for clang.
|
||||||
"nsPIDOMWindow", // <- Takes the vtable from a template parameter, and we can't
|
"nsPIDOMWindow", // <- Takes the vtable from a template parameter, and we can't
|
||||||
// generate it conditionally.
|
// generate it conditionally.
|
||||||
"RawGeckoPresContext", // Just passing it through.
|
|
||||||
"JS::Rooted",
|
"JS::Rooted",
|
||||||
"mozilla::Maybe",
|
"mozilla::Maybe",
|
||||||
"gfxSize", // <- union { struct { T width; T height; }; T components[2] };
|
"gfxSize", // <- union { struct { T width; T height; }; T components[2] };
|
||||||
|
@ -483,6 +497,7 @@ mod bindings {
|
||||||
"RawGeckoAnimationValueList",
|
"RawGeckoAnimationValueList",
|
||||||
"RawServoAnimationValue",
|
"RawServoAnimationValue",
|
||||||
"RawGeckoPresContext",
|
"RawGeckoPresContext",
|
||||||
|
"RawGeckoPresContextOwned",
|
||||||
"ThreadSafeURIHolder",
|
"ThreadSafeURIHolder",
|
||||||
"ThreadSafePrincipalHolder",
|
"ThreadSafePrincipalHolder",
|
||||||
"CSSPseudoClassType",
|
"CSSPseudoClassType",
|
||||||
|
@ -503,6 +518,7 @@ mod bindings {
|
||||||
"nsCursorImage",
|
"nsCursorImage",
|
||||||
"nsFont",
|
"nsFont",
|
||||||
"nsIAtom",
|
"nsIAtom",
|
||||||
|
"nsMediaFeature",
|
||||||
"nsRestyleHint",
|
"nsRestyleHint",
|
||||||
"nsStyleBackground",
|
"nsStyleBackground",
|
||||||
"nsStyleBorder",
|
"nsStyleBorder",
|
||||||
|
|
|
@ -7,11 +7,10 @@
|
||||||
use animation::Animation;
|
use animation::Animation;
|
||||||
use atomic_refcell::{AtomicRef, AtomicRefCell, AtomicRefMut};
|
use atomic_refcell::{AtomicRef, AtomicRefCell, AtomicRefMut};
|
||||||
use dom::OpaqueNode;
|
use dom::OpaqueNode;
|
||||||
use euclid::size::TypedSize2D;
|
|
||||||
use gecko_bindings::bindings::RawGeckoPresContextBorrowed;
|
|
||||||
use gecko_bindings::bindings::RawServoStyleSet;
|
use gecko_bindings::bindings::RawServoStyleSet;
|
||||||
|
use gecko_bindings::structs::RawGeckoPresContextOwned;
|
||||||
use gecko_bindings::sugar::ownership::{HasBoxFFI, HasFFI, HasSimpleFFI};
|
use gecko_bindings::sugar::ownership::{HasBoxFFI, HasFFI, HasSimpleFFI};
|
||||||
use media_queries::{Device, MediaType};
|
use media_queries::Device;
|
||||||
use num_cpus;
|
use num_cpus;
|
||||||
use parking_lot::RwLock;
|
use parking_lot::RwLock;
|
||||||
use properties::ComputedValues;
|
use properties::ComputedValues;
|
||||||
|
@ -21,7 +20,6 @@ use std::collections::HashMap;
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::sync::mpsc::{Receiver, Sender, channel};
|
use std::sync::mpsc::{Receiver, Sender, channel};
|
||||||
use style_traits::ViewportPx;
|
|
||||||
use stylesheets::Stylesheet;
|
use stylesheets::Stylesheet;
|
||||||
use stylist::Stylist;
|
use stylist::Stylist;
|
||||||
|
|
||||||
|
@ -37,6 +35,9 @@ pub struct PerDocumentStyleDataImpl {
|
||||||
/// Whether the stylesheets list above has changed since the last restyle.
|
/// Whether the stylesheets list above has changed since the last restyle.
|
||||||
pub stylesheets_changed: bool,
|
pub stylesheets_changed: bool,
|
||||||
|
|
||||||
|
/// Whether the device has changed since the last restyle.
|
||||||
|
pub device_changed: bool,
|
||||||
|
|
||||||
// FIXME(bholley): Hook these up to something.
|
// FIXME(bholley): Hook these up to something.
|
||||||
/// Unused. Will go away when we actually implement transitions and
|
/// Unused. Will go away when we actually implement transitions and
|
||||||
/// animations properly.
|
/// animations properly.
|
||||||
|
@ -57,9 +58,6 @@ pub struct PerDocumentStyleDataImpl {
|
||||||
|
|
||||||
/// The number of threads of the work queue.
|
/// The number of threads of the work queue.
|
||||||
pub num_threads: usize,
|
pub num_threads: usize,
|
||||||
|
|
||||||
/// Default computed values for this document.
|
|
||||||
pub default_computed_values: Arc<ComputedValues>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The data itself is an `AtomicRefCell`, which guarantees the proper semantics
|
/// The data itself is an `AtomicRefCell`, which guarantees the proper semantics
|
||||||
|
@ -78,15 +76,8 @@ lazy_static! {
|
||||||
|
|
||||||
impl PerDocumentStyleData {
|
impl PerDocumentStyleData {
|
||||||
/// Create a dummy `PerDocumentStyleData`.
|
/// Create a dummy `PerDocumentStyleData`.
|
||||||
pub fn new(pres_context: RawGeckoPresContextBorrowed) -> Self {
|
pub fn new(pres_context: RawGeckoPresContextOwned) -> Self {
|
||||||
// FIXME(bholley): Real window size.
|
let device = Device::new(pres_context);
|
||||||
let window_size: TypedSize2D<f32, ViewportPx> = TypedSize2D::new(800.0, 600.0);
|
|
||||||
let default_computed_values = ComputedValues::default_values(pres_context);
|
|
||||||
|
|
||||||
// FIXME(bz): We're going to need to either update the computed values
|
|
||||||
// in the Stylist's Device or give the Stylist a new Device when our
|
|
||||||
// default_computed_values changes.
|
|
||||||
let device = Device::new(MediaType::Screen, window_size, &default_computed_values);
|
|
||||||
|
|
||||||
let (new_anims_sender, new_anims_receiver) = channel();
|
let (new_anims_sender, new_anims_receiver) = channel();
|
||||||
|
|
||||||
|
@ -94,6 +85,7 @@ impl PerDocumentStyleData {
|
||||||
stylist: Arc::new(Stylist::new(device)),
|
stylist: Arc::new(Stylist::new(device)),
|
||||||
stylesheets: vec![],
|
stylesheets: vec![],
|
||||||
stylesheets_changed: true,
|
stylesheets_changed: true,
|
||||||
|
device_changed: true,
|
||||||
new_animations_sender: new_anims_sender,
|
new_animations_sender: new_anims_sender,
|
||||||
new_animations_receiver: new_anims_receiver,
|
new_animations_receiver: new_anims_receiver,
|
||||||
running_animations: Arc::new(RwLock::new(HashMap::new())),
|
running_animations: Arc::new(RwLock::new(HashMap::new())),
|
||||||
|
@ -106,7 +98,6 @@ impl PerDocumentStyleData {
|
||||||
rayon::ThreadPool::new(configuration).ok()
|
rayon::ThreadPool::new(configuration).ok()
|
||||||
},
|
},
|
||||||
num_threads: *NUM_THREADS,
|
num_threads: *NUM_THREADS,
|
||||||
default_computed_values: default_computed_values,
|
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -124,15 +115,30 @@ impl PerDocumentStyleData {
|
||||||
impl PerDocumentStyleDataImpl {
|
impl PerDocumentStyleDataImpl {
|
||||||
/// Recreate the style data if the stylesheets have changed.
|
/// Recreate the style data if the stylesheets have changed.
|
||||||
pub fn flush_stylesheets(&mut self) {
|
pub fn flush_stylesheets(&mut self) {
|
||||||
// The stylist wants to be flushed if either the stylesheets change or the
|
let mut stylist = if self.device_changed || self.stylesheets_changed {
|
||||||
// device dimensions change. When we add support for media queries, we'll
|
Some(Arc::get_mut(&mut self.stylist).unwrap())
|
||||||
// need to detect the latter case and trigger a flush as well.
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
if self.device_changed {
|
||||||
|
Arc::get_mut(&mut stylist.as_mut().unwrap().device).unwrap().reset();
|
||||||
|
self.device_changed = false;
|
||||||
|
// Force a stylesheet flush if the device has changed.
|
||||||
|
self.stylesheets_changed = true;
|
||||||
|
}
|
||||||
|
|
||||||
if self.stylesheets_changed {
|
if self.stylesheets_changed {
|
||||||
let _ = Arc::get_mut(&mut self.stylist).unwrap()
|
let _ = stylist.unwrap().update(&self.stylesheets, None, true);
|
||||||
.update(&self.stylesheets, None, true);
|
|
||||||
self.stylesheets_changed = false;
|
self.stylesheets_changed = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the default computed values for this document.
|
||||||
|
pub fn default_computed_values(&self) -> &Arc<ComputedValues> {
|
||||||
|
debug_assert!(!self.device_changed, "A device flush was pending");
|
||||||
|
self.stylist.device.default_values_arc()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe impl HasFFI for PerDocumentStyleData {
|
unsafe impl HasFFI for PerDocumentStyleData {
|
||||||
|
|
566
components/style/gecko/media_queries.rs
Normal file
566
components/style/gecko/media_queries.rs
Normal file
|
@ -0,0 +1,566 @@
|
||||||
|
/* 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-query device and expression representation.
|
||||||
|
|
||||||
|
use app_units::Au;
|
||||||
|
use cssparser::{CssStringWriter, Parser, Token};
|
||||||
|
use euclid::Size2D;
|
||||||
|
use gecko_bindings::bindings;
|
||||||
|
use gecko_bindings::structs::{nsCSSValue, nsCSSUnit, nsStringBuffer, nsresult};
|
||||||
|
use gecko_bindings::structs::{nsMediaExpression_Range, nsMediaFeature};
|
||||||
|
use gecko_bindings::structs::{nsMediaFeature_ValueType, nsMediaFeature_RangeType, nsMediaFeature_RequirementFlags};
|
||||||
|
use gecko_bindings::structs::RawGeckoPresContextOwned;
|
||||||
|
use media_queries::MediaType;
|
||||||
|
use properties::ComputedValues;
|
||||||
|
use std::ascii::AsciiExt;
|
||||||
|
use std::fmt::{self, Write};
|
||||||
|
use std::sync::Arc;
|
||||||
|
use string_cache::Atom;
|
||||||
|
use style_traits::ToCss;
|
||||||
|
use style_traits::viewport::ViewportConstraints;
|
||||||
|
use values::{CSSFloat, specified};
|
||||||
|
use values::computed::{self, ToComputedValue};
|
||||||
|
|
||||||
|
/// The `Device` in Gecko wraps a pres context, has a default values computed,
|
||||||
|
/// and contains all the viewport rule state.
|
||||||
|
pub struct Device {
|
||||||
|
/// NB: The pres context lifetime is tied to the styleset, who owns the
|
||||||
|
/// stylist, and thus the `Device`, so having a raw pres context pointer
|
||||||
|
/// here is fine.
|
||||||
|
pres_context: RawGeckoPresContextOwned,
|
||||||
|
default_values: Arc<ComputedValues>,
|
||||||
|
viewport_override: Option<ViewportConstraints>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Device {
|
||||||
|
/// Trivially constructs a new `Device`.
|
||||||
|
pub fn new(pres_context: RawGeckoPresContextOwned) -> Self {
|
||||||
|
assert!(!pres_context.is_null());
|
||||||
|
Device {
|
||||||
|
pres_context: pres_context,
|
||||||
|
default_values: ComputedValues::default_values(unsafe { &*pres_context }),
|
||||||
|
viewport_override: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Tells the device that a new viewport rule has been found, and stores the
|
||||||
|
/// relevant viewport constraints.
|
||||||
|
pub fn account_for_viewport_rule(&mut self,
|
||||||
|
constraints: &ViewportConstraints) {
|
||||||
|
self.viewport_override = Some(constraints.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the default computed values as a reference, in order to match
|
||||||
|
/// Servo.
|
||||||
|
pub fn default_values(&self) -> &ComputedValues {
|
||||||
|
&*self.default_values
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the default computed values as an `Arc`, in order to avoid
|
||||||
|
/// clones.
|
||||||
|
pub fn default_values_arc(&self) -> &Arc<ComputedValues> {
|
||||||
|
&self.default_values
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Recreates all the temporary state that the `Device` stores.
|
||||||
|
///
|
||||||
|
/// This includes the viewport override from `@viewport` rules, and also the
|
||||||
|
/// default computed values.
|
||||||
|
pub fn reset(&mut self) {
|
||||||
|
// NB: A following stylesheet flush will populate this if appropriate.
|
||||||
|
self.viewport_override = None;
|
||||||
|
self.default_values = ComputedValues::default_values(unsafe { &*self.pres_context });
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the current media type of the device.
|
||||||
|
pub fn media_type(&self) -> MediaType {
|
||||||
|
unsafe {
|
||||||
|
// FIXME(emilio): Gecko allows emulating random media with
|
||||||
|
// mIsEmulatingMedia / mMediaEmulated . Refactor both sides so that
|
||||||
|
// is supported (probably just making MediaType an Atom).
|
||||||
|
if (*self.pres_context).mMedium == atom!("screen").as_ptr() {
|
||||||
|
MediaType::Screen
|
||||||
|
} else {
|
||||||
|
debug_assert!((*self.pres_context).mMedium == atom!("print").as_ptr());
|
||||||
|
MediaType::Print
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the current viewport size in app units.
|
||||||
|
pub fn au_viewport_size(&self) -> Size2D<Au> {
|
||||||
|
self.viewport_override.as_ref().map(|v| {
|
||||||
|
Size2D::new(Au::from_f32_px(v.size.width),
|
||||||
|
Au::from_f32_px(v.size.height))
|
||||||
|
}).unwrap_or_else(|| {
|
||||||
|
// TODO(emilio): Grab from pres context.
|
||||||
|
Size2D::new(Au::from_f32_px(1024.0),
|
||||||
|
Au::from_f32_px(768.0))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl Sync for Device {}
|
||||||
|
unsafe impl Send for Device {}
|
||||||
|
|
||||||
|
/// A expression for gecko contains a reference to the media feature, the value
|
||||||
|
/// the media query contained, and the range to evaluate.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Expression {
|
||||||
|
feature: &'static nsMediaFeature,
|
||||||
|
value: Option<MediaExpressionValue>,
|
||||||
|
range: nsMediaExpression_Range
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToCss for Expression {
|
||||||
|
fn to_css<W>(&self, dest: &mut W) -> fmt::Result
|
||||||
|
where W: fmt::Write,
|
||||||
|
{
|
||||||
|
dest.write_str("(")?;
|
||||||
|
match self.range {
|
||||||
|
nsMediaExpression_Range::eMin => dest.write_str("min-")?,
|
||||||
|
nsMediaExpression_Range::eMax => dest.write_str("max-")?,
|
||||||
|
nsMediaExpression_Range::eEqual => {},
|
||||||
|
}
|
||||||
|
|
||||||
|
// NB: CssStringWriter not needed, feature names are under control.
|
||||||
|
write!(dest, "{}", Atom::from(unsafe { *self.feature.mName }))?;
|
||||||
|
|
||||||
|
if let Some(ref val) = self.value {
|
||||||
|
dest.write_str(": ")?;
|
||||||
|
val.to_css(dest)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
dest.write_str(")")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A resolution.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum Resolution {
|
||||||
|
/// Dots per inch.
|
||||||
|
Dpi(CSSFloat),
|
||||||
|
/// Dots per pixel.
|
||||||
|
Dppx(CSSFloat),
|
||||||
|
/// Dots per centimeter.
|
||||||
|
Dpcm(CSSFloat),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Resolution {
|
||||||
|
fn to_dpi(&self) -> CSSFloat {
|
||||||
|
match *self {
|
||||||
|
Resolution::Dpi(f) => f,
|
||||||
|
Resolution::Dppx(f) => f * 96.0,
|
||||||
|
Resolution::Dpcm(f) => f * 2.54,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse(input: &mut Parser) -> Result<Self, ()> {
|
||||||
|
let (value, unit) = match try!(input.next()) {
|
||||||
|
Token::Dimension(value, unit) => {
|
||||||
|
(value.value, unit)
|
||||||
|
},
|
||||||
|
_ => return Err(()),
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(match_ignore_ascii_case! { unit,
|
||||||
|
"dpi" => Resolution::Dpi(value),
|
||||||
|
"dppx" => Resolution::Dppx(value),
|
||||||
|
"dpcm" => Resolution::Dpcm(value),
|
||||||
|
_ => return Err(())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToCss for Resolution {
|
||||||
|
fn to_css<W>(&self, dest: &mut W) -> fmt::Result
|
||||||
|
where W: fmt::Write,
|
||||||
|
{
|
||||||
|
match *self {
|
||||||
|
Resolution::Dpi(v) => write!(dest, "{}dpi", v),
|
||||||
|
Resolution::Dppx(v) => write!(dest, "{}dppx", v),
|
||||||
|
Resolution::Dpcm(v) => write!(dest, "{}dpcm", v),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn string_from_ns_string_buffer(buffer: *const nsStringBuffer) -> String {
|
||||||
|
use std::slice;
|
||||||
|
debug_assert!(!buffer.is_null());
|
||||||
|
let data = buffer.offset(1) as *const u16;
|
||||||
|
let mut length = 0;
|
||||||
|
let mut iter = data;
|
||||||
|
while *iter != 0 {
|
||||||
|
length += 1;
|
||||||
|
iter = iter.offset(1);
|
||||||
|
}
|
||||||
|
String::from_utf16_lossy(slice::from_raw_parts(data, length))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A value found or expected in a media expression.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum MediaExpressionValue {
|
||||||
|
/// A length.
|
||||||
|
Length(specified::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(u32),
|
||||||
|
/// An identifier.
|
||||||
|
///
|
||||||
|
/// TODO(emilio): Maybe atomize?
|
||||||
|
Ident(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MediaExpressionValue {
|
||||||
|
fn from_css_value(for_expr: &Expression, 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!(css_value.mUnit == nsCSSUnit::eCSSUnit_Pixel);
|
||||||
|
let pixels = css_value.float_unchecked();
|
||||||
|
Some(MediaExpressionValue::Length(
|
||||||
|
specified::Length::Absolute(Au::from_f32_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!(css_value.mUnit == nsCSSUnit::eCSSUnit_Number);
|
||||||
|
Some(MediaExpressionValue::Float(css_value.float_unchecked()))
|
||||||
|
}
|
||||||
|
nsMediaFeature_ValueType::eBoolInteger => {
|
||||||
|
debug_assert!(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!(css_value.mUnit == nsCSSUnit::eCSSUnit_Inch);
|
||||||
|
Some(MediaExpressionValue::Resolution(Resolution::Dpi(css_value.float_unchecked())))
|
||||||
|
}
|
||||||
|
nsMediaFeature_ValueType::eEnumerated => {
|
||||||
|
debug_assert!(css_value.mUnit == nsCSSUnit::eCSSUnit_Enumerated);
|
||||||
|
let value = css_value.integer_unchecked();
|
||||||
|
debug_assert!(value >= 0);
|
||||||
|
Some(MediaExpressionValue::Enumerated(value as u32))
|
||||||
|
}
|
||||||
|
nsMediaFeature_ValueType::eIdent => {
|
||||||
|
debug_assert!(css_value.mUnit == nsCSSUnit::eCSSUnit_Ident);
|
||||||
|
let string = unsafe {
|
||||||
|
string_from_ns_string_buffer(*css_value.mValue.mString.as_ref())
|
||||||
|
};
|
||||||
|
Some(MediaExpressionValue::Ident(string))
|
||||||
|
}
|
||||||
|
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 ToCss for MediaExpressionValue {
|
||||||
|
fn to_css<W>(&self, dest: &mut W) -> fmt::Result
|
||||||
|
where W: fmt::Write,
|
||||||
|
{
|
||||||
|
match *self {
|
||||||
|
MediaExpressionValue::Length(ref l) => l.to_css(dest),
|
||||||
|
MediaExpressionValue::Integer(v) => write!(dest, "{}", v),
|
||||||
|
MediaExpressionValue::Float(v) => write!(dest, "{}", v),
|
||||||
|
MediaExpressionValue::BoolInteger(v) => {
|
||||||
|
dest.write_str(if v { "1" } else { "0" })
|
||||||
|
},
|
||||||
|
MediaExpressionValue::IntRatio(a, b) => {
|
||||||
|
write!(dest, "{}/{}", a, b)
|
||||||
|
},
|
||||||
|
MediaExpressionValue::Resolution(ref r) => r.to_css(dest),
|
||||||
|
MediaExpressionValue::Ident(ref ident) => {
|
||||||
|
CssStringWriter::new(dest).write_str(ident)
|
||||||
|
}
|
||||||
|
MediaExpressionValue::Enumerated(..) => {
|
||||||
|
// TODO(emilio): Use the CSS keyword table.
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn starts_with_ignore_ascii_case(string: &str, prefix: &str) -> bool {
|
||||||
|
string.len() > prefix.len() &&
|
||||||
|
string[0..prefix.len()].eq_ignore_ascii_case(prefix)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn find_feature<F>(mut f: F) -> Option<&'static nsMediaFeature>
|
||||||
|
where F: FnMut(&'static nsMediaFeature) -> bool,
|
||||||
|
{
|
||||||
|
// FIXME(emilio): With build-time bindgen, we would be able to use
|
||||||
|
// structs::nsMediaFeatures_features. That would unfortunately break MSVC
|
||||||
|
// builds, or require one bindings file per platform.
|
||||||
|
//
|
||||||
|
// I'm not into any of those, so meanwhile let's use a FFI function.
|
||||||
|
unsafe {
|
||||||
|
let mut features = bindings::Gecko_GetMediaFeatures();
|
||||||
|
while !(*features).mName.is_null() {
|
||||||
|
if f(&*features) {
|
||||||
|
return Some(&*features);
|
||||||
|
}
|
||||||
|
features = features.offset(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Expression {
|
||||||
|
/// Trivially construct a new expression.
|
||||||
|
fn new(feature: &'static nsMediaFeature,
|
||||||
|
value: Option<MediaExpressionValue>,
|
||||||
|
range: nsMediaExpression_Range) -> Self {
|
||||||
|
Expression {
|
||||||
|
feature: feature,
|
||||||
|
value: value,
|
||||||
|
range: range,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parse a media expression of the form:
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// (media-feature: media-value)
|
||||||
|
/// ```
|
||||||
|
pub fn parse(input: &mut Parser) -> Result<Self, ()> {
|
||||||
|
try!(input.expect_parenthesis_block());
|
||||||
|
input.parse_nested_block(|input| {
|
||||||
|
let ident = try!(input.expect_ident());
|
||||||
|
|
||||||
|
let mut flags = 0;
|
||||||
|
let mut feature_name = &*ident;
|
||||||
|
|
||||||
|
// TODO(emilio): this is under a pref in Gecko.
|
||||||
|
if starts_with_ignore_ascii_case(feature_name, "-webkit-") {
|
||||||
|
feature_name = &feature_name[8..];
|
||||||
|
flags |= nsMediaFeature_RequirementFlags::eHasWebkitPrefix as u8;
|
||||||
|
}
|
||||||
|
|
||||||
|
let range = if starts_with_ignore_ascii_case(feature_name, "min-") {
|
||||||
|
feature_name = &feature_name[4..];
|
||||||
|
nsMediaExpression_Range::eMin
|
||||||
|
} else if starts_with_ignore_ascii_case(feature_name, "max-") {
|
||||||
|
feature_name = &feature_name[4..];
|
||||||
|
nsMediaExpression_Range::eMax
|
||||||
|
} else {
|
||||||
|
nsMediaExpression_Range::eEqual
|
||||||
|
};
|
||||||
|
|
||||||
|
let atom = Atom::from(feature_name);
|
||||||
|
let feature =
|
||||||
|
match find_feature(|f| atom.as_ptr() == unsafe { *f.mName }) {
|
||||||
|
Some(f) => f,
|
||||||
|
None => return Err(()),
|
||||||
|
};
|
||||||
|
|
||||||
|
if (feature.mReqFlags & !flags) != 0 {
|
||||||
|
return Err(());
|
||||||
|
}
|
||||||
|
|
||||||
|
if range != nsMediaExpression_Range::eEqual &&
|
||||||
|
feature.mRangeType != nsMediaFeature_RangeType::eMinMaxAllowed {
|
||||||
|
return Err(());
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there's no colon, this is a media query of the form
|
||||||
|
// '(<feature>)', that is, there's no value specified.
|
||||||
|
//
|
||||||
|
// FIXME(emilio): We need to check for range operators too here when
|
||||||
|
// we support them, see:
|
||||||
|
//
|
||||||
|
// https://drafts.csswg.org/mediaqueries/#mq-ranges
|
||||||
|
if input.try(|i| i.expect_colon()).is_err() {
|
||||||
|
return Ok(Expression::new(feature, None, range));
|
||||||
|
}
|
||||||
|
|
||||||
|
let value = match feature.mValueType {
|
||||||
|
nsMediaFeature_ValueType::eLength => {
|
||||||
|
MediaExpressionValue::Length(
|
||||||
|
specified::Length::parse_non_negative(input)?)
|
||||||
|
},
|
||||||
|
nsMediaFeature_ValueType::eInteger => {
|
||||||
|
let i = input.expect_integer()?;
|
||||||
|
if i < 0 {
|
||||||
|
return Err(())
|
||||||
|
}
|
||||||
|
MediaExpressionValue::Integer(i as u32)
|
||||||
|
}
|
||||||
|
nsMediaFeature_ValueType::eBoolInteger => {
|
||||||
|
let i = input.expect_integer()?;
|
||||||
|
if i < 0 || i > 1 {
|
||||||
|
return Err(())
|
||||||
|
}
|
||||||
|
MediaExpressionValue::BoolInteger(i == 1)
|
||||||
|
}
|
||||||
|
nsMediaFeature_ValueType::eFloat => {
|
||||||
|
MediaExpressionValue::Float(input.expect_number()?)
|
||||||
|
}
|
||||||
|
nsMediaFeature_ValueType::eIntRatio => {
|
||||||
|
let a = input.expect_integer()?;
|
||||||
|
if a <= 0 {
|
||||||
|
return Err(())
|
||||||
|
}
|
||||||
|
|
||||||
|
input.expect_delim('/')?;
|
||||||
|
|
||||||
|
let b = input.expect_integer()?;
|
||||||
|
if b <= 0 {
|
||||||
|
return Err(())
|
||||||
|
}
|
||||||
|
MediaExpressionValue::IntRatio(a as u32, b as u32)
|
||||||
|
}
|
||||||
|
nsMediaFeature_ValueType::eResolution => {
|
||||||
|
MediaExpressionValue::Resolution(Resolution::parse(input)?)
|
||||||
|
}
|
||||||
|
nsMediaFeature_ValueType::eEnumerated => {
|
||||||
|
// TODO(emilio): Use Gecko's CSS keyword table to parse
|
||||||
|
// this.
|
||||||
|
return Err(())
|
||||||
|
}
|
||||||
|
nsMediaFeature_ValueType::eIdent => {
|
||||||
|
MediaExpressionValue::Ident(input.expect_ident()?.into_owned())
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Expression::new(feature, Some(value), range))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns whether this media query evaluates to true for the given device.
|
||||||
|
pub fn matches(&self, device: &Device) -> bool {
|
||||||
|
let mut css_value = nsCSSValue::null();
|
||||||
|
let result = unsafe {
|
||||||
|
(self.feature.mGetter.unwrap())(device.pres_context,
|
||||||
|
self.feature,
|
||||||
|
&mut css_value)
|
||||||
|
};
|
||||||
|
|
||||||
|
if result != nsresult::NS_OK {
|
||||||
|
// FIXME(emilio): This doesn't seem possible from reading gecko
|
||||||
|
// code, probably we should just clean up that function and return
|
||||||
|
// void.
|
||||||
|
error!("Media feature getter errored: {:?}, {:?}",
|
||||||
|
result, Atom::from(unsafe { *self.feature.mName }));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let value = match MediaExpressionValue::from_css_value(self, &css_value) {
|
||||||
|
Some(v) => v,
|
||||||
|
None => return false,
|
||||||
|
};
|
||||||
|
|
||||||
|
self.evaluate_against(device, &value)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn evaluate_against(&self,
|
||||||
|
device: &Device,
|
||||||
|
actual_value: &MediaExpressionValue)
|
||||||
|
-> bool {
|
||||||
|
use self::MediaExpressionValue::*;
|
||||||
|
use std::cmp::Ordering;
|
||||||
|
|
||||||
|
debug_assert!(self.range == nsMediaExpression_Range::eEqual ||
|
||||||
|
self.feature.mRangeType == nsMediaFeature_RangeType::eMinMaxAllowed,
|
||||||
|
"Whoops, wrong range");
|
||||||
|
|
||||||
|
let default_values = device.default_values();
|
||||||
|
|
||||||
|
// http://dev.w3.org/csswg/mediaqueries3/#units
|
||||||
|
// em units are relative to the initial font-size.
|
||||||
|
let context = computed::Context {
|
||||||
|
is_root_element: false,
|
||||||
|
viewport_size: device.au_viewport_size(),
|
||||||
|
inherited_style: default_values,
|
||||||
|
// This cloning business is kind of dumb.... It's because Context
|
||||||
|
// insists on having an actual ComputedValues inside itself.
|
||||||
|
style: default_values.clone(),
|
||||||
|
font_metrics_provider: None
|
||||||
|
};
|
||||||
|
|
||||||
|
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) => l.to_computed_value(&context) != Au(0),
|
||||||
|
_ => true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// FIXME(emilio): Handle the possible floating point errors?
|
||||||
|
let cmp = match (required_value, actual_value) {
|
||||||
|
(&Length(ref one), &Length(ref other)) => {
|
||||||
|
one.to_computed_value(&context)
|
||||||
|
.cmp(&other.to_computed_value(&context))
|
||||||
|
}
|
||||||
|
(&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)) => {
|
||||||
|
(one_num * other_den).partial_cmp(&(other_num * one_den)).unwrap()
|
||||||
|
}
|
||||||
|
(&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 {
|
||||||
|
other.to_dpi()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
one.to_dpi().partial_cmp(&actual_dpi).unwrap()
|
||||||
|
}
|
||||||
|
(&Ident(ref one), &Ident(ref other)) => {
|
||||||
|
debug_assert!(self.feature.mRangeType != nsMediaFeature_RangeType::eMinMaxAllowed);
|
||||||
|
return one == other;
|
||||||
|
}
|
||||||
|
(&Enumerated(..), &Enumerated(..)) => {
|
||||||
|
// TODO(emilio)
|
||||||
|
unimplemented!();
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
|
||||||
|
cmp == Ordering::Equal || match self.range {
|
||||||
|
nsMediaExpression_Range::eMin => cmp == Ordering::Less,
|
||||||
|
nsMediaExpression_Range::eEqual => false,
|
||||||
|
nsMediaExpression_Range::eMax => cmp == Ordering::Greater,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,12 +6,7 @@
|
||||||
|
|
||||||
pub mod conversions;
|
pub mod conversions;
|
||||||
pub mod data;
|
pub mod data;
|
||||||
|
|
||||||
// TODO(emilio): Implement Gecko media query parsing and evaluation using
|
|
||||||
// nsMediaFeatures.
|
|
||||||
#[path = "../servo/media_queries.rs"]
|
|
||||||
pub mod media_queries;
|
pub mod media_queries;
|
||||||
|
|
||||||
pub mod restyle_damage;
|
pub mod restyle_damage;
|
||||||
pub mod selector_parser;
|
pub mod selector_parser;
|
||||||
pub mod snapshot;
|
pub mod snapshot;
|
||||||
|
|
|
@ -9,6 +9,7 @@ use gecko_bindings::structs::RawGeckoNode;
|
||||||
use gecko_bindings::structs::RawGeckoAnimationValueList;
|
use gecko_bindings::structs::RawGeckoAnimationValueList;
|
||||||
use gecko_bindings::structs::RawServoAnimationValue;
|
use gecko_bindings::structs::RawServoAnimationValue;
|
||||||
use gecko_bindings::structs::RawGeckoPresContext;
|
use gecko_bindings::structs::RawGeckoPresContext;
|
||||||
|
use gecko_bindings::structs::RawGeckoPresContextOwned;
|
||||||
use gecko_bindings::structs::ThreadSafeURIHolder;
|
use gecko_bindings::structs::ThreadSafeURIHolder;
|
||||||
use gecko_bindings::structs::ThreadSafePrincipalHolder;
|
use gecko_bindings::structs::ThreadSafePrincipalHolder;
|
||||||
use gecko_bindings::structs::CSSPseudoClassType;
|
use gecko_bindings::structs::CSSPseudoClassType;
|
||||||
|
@ -29,6 +30,7 @@ use gecko_bindings::structs::nsChangeHint;
|
||||||
use gecko_bindings::structs::nsCursorImage;
|
use gecko_bindings::structs::nsCursorImage;
|
||||||
use gecko_bindings::structs::nsFont;
|
use gecko_bindings::structs::nsFont;
|
||||||
use gecko_bindings::structs::nsIAtom;
|
use gecko_bindings::structs::nsIAtom;
|
||||||
|
use gecko_bindings::structs::nsMediaFeature;
|
||||||
use gecko_bindings::structs::nsRestyleHint;
|
use gecko_bindings::structs::nsRestyleHint;
|
||||||
use gecko_bindings::structs::nsStyleBackground;
|
use gecko_bindings::structs::nsStyleBackground;
|
||||||
unsafe impl Send for nsStyleBackground {}
|
unsafe impl Send for nsStyleBackground {}
|
||||||
|
@ -748,6 +750,9 @@ extern "C" {
|
||||||
pub fn Gecko_CSSValue_GetArrayItem(css_value: nsCSSValueBorrowedMut,
|
pub fn Gecko_CSSValue_GetArrayItem(css_value: nsCSSValueBorrowedMut,
|
||||||
index: i32) -> nsCSSValueBorrowedMut;
|
index: i32) -> nsCSSValueBorrowedMut;
|
||||||
}
|
}
|
||||||
|
extern "C" {
|
||||||
|
pub fn Gecko_CSSValue_Drop(css_value: nsCSSValueBorrowedMut);
|
||||||
|
}
|
||||||
extern "C" {
|
extern "C" {
|
||||||
pub fn Gecko_AddRefCSSValueSharedListArbitraryThread(aPtr:
|
pub fn Gecko_AddRefCSSValueSharedListArbitraryThread(aPtr:
|
||||||
*mut nsCSSValueSharedList);
|
*mut nsCSSValueSharedList);
|
||||||
|
@ -759,6 +764,9 @@ extern "C" {
|
||||||
extern "C" {
|
extern "C" {
|
||||||
pub fn Gecko_PropertyId_IsPrefEnabled(id: nsCSSPropertyID) -> bool;
|
pub fn Gecko_PropertyId_IsPrefEnabled(id: nsCSSPropertyID) -> bool;
|
||||||
}
|
}
|
||||||
|
extern "C" {
|
||||||
|
pub fn Gecko_GetMediaFeatures() -> *const nsMediaFeature;
|
||||||
|
}
|
||||||
extern "C" {
|
extern "C" {
|
||||||
pub fn Gecko_Construct_Default_nsStyleFont(ptr: *mut nsStyleFont,
|
pub fn Gecko_Construct_Default_nsStyleFont(ptr: *mut nsStyleFont,
|
||||||
pres_context:
|
pres_context:
|
||||||
|
@ -1114,14 +1122,11 @@ extern "C" {
|
||||||
-> ServoCssRulesStrong;
|
-> ServoCssRulesStrong;
|
||||||
}
|
}
|
||||||
extern "C" {
|
extern "C" {
|
||||||
pub fn Servo_StyleSet_Init(pres_context: RawGeckoPresContextBorrowed)
|
pub fn Servo_StyleSet_Init(pres_context: RawGeckoPresContextOwned)
|
||||||
-> RawServoStyleSetOwned;
|
-> RawServoStyleSetOwned;
|
||||||
}
|
}
|
||||||
extern "C" {
|
extern "C" {
|
||||||
pub fn Servo_StyleSet_RecomputeDefaultStyles(set:
|
pub fn Servo_StyleSet_RebuildData(set: RawServoStyleSetBorrowed);
|
||||||
RawServoStyleSetBorrowed,
|
|
||||||
pres_context:
|
|
||||||
RawGeckoPresContextBorrowed);
|
|
||||||
}
|
}
|
||||||
extern "C" {
|
extern "C" {
|
||||||
pub fn Servo_StyleSet_AppendStyleSheet(set: RawServoStyleSetBorrowed,
|
pub fn Servo_StyleSet_AppendStyleSheet(set: RawServoStyleSetBorrowed,
|
||||||
|
|
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
@ -6,6 +6,7 @@
|
||||||
|
|
||||||
mod ns_com_ptr;
|
mod ns_com_ptr;
|
||||||
mod ns_css_shadow_array;
|
mod ns_css_shadow_array;
|
||||||
|
mod ns_css_value;
|
||||||
mod ns_style_auto_array;
|
mod ns_style_auto_array;
|
||||||
pub mod ns_style_coord;
|
pub mod ns_style_coord;
|
||||||
mod ns_t_array;
|
mod ns_t_array;
|
||||||
|
|
78
components/style/gecko_bindings/sugar/ns_css_value.rs
Normal file
78
components/style/gecko_bindings/sugar/ns_css_value.rs
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
/* 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/. */
|
||||||
|
|
||||||
|
//! Little helpers for `nsCSSValue`.
|
||||||
|
|
||||||
|
use gecko_bindings::bindings::Gecko_CSSValue_Drop;
|
||||||
|
use gecko_bindings::structs::{nsCSSValue, nsCSSUnit, nsCSSValue_Array};
|
||||||
|
use std::mem;
|
||||||
|
use std::ops::Index;
|
||||||
|
use std::slice;
|
||||||
|
|
||||||
|
impl nsCSSValue {
|
||||||
|
/// Create a CSSValue with null unit, useful to be used as a return value.
|
||||||
|
#[inline]
|
||||||
|
pub fn null() -> Self {
|
||||||
|
unsafe { mem::zeroed() }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns this nsCSSValue value as an integer, unchecked in release
|
||||||
|
/// builds.
|
||||||
|
pub fn integer_unchecked(&self) -> i32 {
|
||||||
|
debug_assert!(self.mUnit == nsCSSUnit::eCSSUnit_Integer ||
|
||||||
|
self.mUnit == nsCSSUnit::eCSSUnit_Enumerated ||
|
||||||
|
self.mUnit == nsCSSUnit::eCSSUnit_EnumColor);
|
||||||
|
unsafe { *self.mValue.mInt.as_ref() }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns this nsCSSValue value as a floating point value, unchecked in
|
||||||
|
/// release builds.
|
||||||
|
pub fn float_unchecked(&self) -> f32 {
|
||||||
|
debug_assert!(nsCSSUnit::eCSSUnit_Number as u32 <= self.mUnit as u32);
|
||||||
|
unsafe { *self.mValue.mFloat.as_ref() }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns this nsCSSValue as a nsCSSValue::Array, unchecked in release
|
||||||
|
/// builds.
|
||||||
|
pub unsafe fn array_unchecked(&self) -> &nsCSSValue_Array {
|
||||||
|
debug_assert!(nsCSSUnit::eCSSUnit_Array as u32 <= self.mUnit as u32 &&
|
||||||
|
self.mUnit as u32 <= nsCSSUnit::eCSSUnit_Calc_Divided as u32);
|
||||||
|
let array = *self.mValue.mArray.as_ref();
|
||||||
|
debug_assert!(!array.is_null());
|
||||||
|
&*array
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for nsCSSValue {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
unsafe { Gecko_CSSValue_Drop(self) };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl nsCSSValue_Array {
|
||||||
|
/// Return the length of this `nsCSSShadowArray`
|
||||||
|
#[inline]
|
||||||
|
pub fn len(&self) -> usize {
|
||||||
|
self.mCount
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn buffer(&self) -> *const nsCSSValue {
|
||||||
|
self.mArray.as_ptr()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the array as a slice of nsCSSValues.
|
||||||
|
#[inline]
|
||||||
|
pub fn as_slice(&self) -> &[nsCSSValue] {
|
||||||
|
unsafe { slice::from_raw_parts(self.buffer(), self.len()) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Index<usize> for nsCSSValue_Array {
|
||||||
|
type Output = nsCSSValue;
|
||||||
|
#[inline]
|
||||||
|
fn index(&self, i: usize) -> &nsCSSValue {
|
||||||
|
&self.as_slice()[i]
|
||||||
|
}
|
||||||
|
}
|
|
@ -19,7 +19,7 @@ pub use servo::media_queries::{Device, Expression};
|
||||||
pub use gecko::media_queries::{Device, Expression};
|
pub use gecko::media_queries::{Device, Expression};
|
||||||
|
|
||||||
/// A type that encapsulates a media query list.
|
/// A type that encapsulates a media query list.
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug)]
|
||||||
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
|
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
|
||||||
pub struct MediaList {
|
pub struct MediaList {
|
||||||
/// The list of media queries.
|
/// The list of media queries.
|
||||||
|
@ -66,7 +66,7 @@ impl ToCss for Qualifier {
|
||||||
/// A [media query][mq].
|
/// A [media query][mq].
|
||||||
///
|
///
|
||||||
/// [mq]: https://drafts.csswg.org/mediaqueries/
|
/// [mq]: https://drafts.csswg.org/mediaqueries/
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug)]
|
||||||
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
|
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
|
||||||
pub struct MediaQuery {
|
pub struct MediaQuery {
|
||||||
/// The qualifier for this query.
|
/// The qualifier for this query.
|
||||||
|
|
|
@ -10,8 +10,6 @@ use euclid::{Size2D, TypedSize2D};
|
||||||
use media_queries::MediaType;
|
use media_queries::MediaType;
|
||||||
use properties::ComputedValues;
|
use properties::ComputedValues;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
#[cfg(feature = "gecko")]
|
|
||||||
use std::sync::Arc;
|
|
||||||
use style_traits::{ToCss, ViewportPx};
|
use style_traits::{ToCss, ViewportPx};
|
||||||
use style_traits::viewport::ViewportConstraints;
|
use style_traits::viewport::ViewportConstraints;
|
||||||
use values::computed::{self, ToComputedValue};
|
use values::computed::{self, ToComputedValue};
|
||||||
|
@ -21,24 +19,16 @@ use values::specified;
|
||||||
/// is displayed in.
|
/// is displayed in.
|
||||||
///
|
///
|
||||||
/// This is the struct against which media queries are evaluated.
|
/// This is the struct against which media queries are evaluated.
|
||||||
#[derive(Debug)]
|
#[derive(Debug, HeapSizeOf)]
|
||||||
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
|
|
||||||
pub struct Device {
|
pub struct Device {
|
||||||
/// The current media type used by de device.
|
/// The current media type used by de device.
|
||||||
media_type: MediaType,
|
media_type: MediaType,
|
||||||
/// The current viewport size, in viewport pixels.
|
/// The current viewport size, in viewport pixels.
|
||||||
viewport_size: TypedSize2D<f32, ViewportPx>,
|
viewport_size: TypedSize2D<f32, ViewportPx>,
|
||||||
/// A set of default computed values for this document.
|
|
||||||
///
|
|
||||||
/// This allows handling zoom correctly, among other things. Gecko-only for
|
|
||||||
/// now, see #14773.
|
|
||||||
#[cfg(feature = "gecko")]
|
|
||||||
default_values: Arc<ComputedValues>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Device {
|
impl Device {
|
||||||
/// Trivially construct a new `Device`.
|
/// Trivially construct a new `Device`.
|
||||||
#[cfg(feature = "servo")]
|
|
||||||
pub fn new(media_type: MediaType,
|
pub fn new(media_type: MediaType,
|
||||||
viewport_size: TypedSize2D<f32, ViewportPx>)
|
viewport_size: TypedSize2D<f32, ViewportPx>)
|
||||||
-> Device {
|
-> Device {
|
||||||
|
@ -48,30 +38,11 @@ impl Device {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Trivially construct a new `Device`.
|
|
||||||
#[cfg(feature = "gecko")]
|
|
||||||
pub fn new(media_type:
|
|
||||||
MediaType, viewport_size: TypedSize2D<f32, ViewportPx>,
|
|
||||||
default_values: &Arc<ComputedValues>) -> Device {
|
|
||||||
Device {
|
|
||||||
media_type: media_type,
|
|
||||||
viewport_size: viewport_size,
|
|
||||||
default_values: default_values.clone(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Return the default computed values for this device.
|
/// Return the default computed values for this device.
|
||||||
#[cfg(feature = "servo")]
|
|
||||||
pub fn default_values(&self) -> &ComputedValues {
|
pub fn default_values(&self) -> &ComputedValues {
|
||||||
ComputedValues::initial_values()
|
ComputedValues::initial_values()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return the default computed values for this device.
|
|
||||||
#[cfg(feature = "gecko")]
|
|
||||||
pub fn default_values(&self) -> &ComputedValues {
|
|
||||||
&*self.default_values
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the viewport size of the current device in app units, needed,
|
/// Returns the viewport size of the current device in app units, needed,
|
||||||
/// among other things, to resolve viewport units.
|
/// among other things, to resolve viewport units.
|
||||||
#[inline]
|
#[inline]
|
||||||
|
|
|
@ -49,6 +49,20 @@ pub use ::fnv::FnvHashMap;
|
||||||
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
|
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
|
||||||
pub struct Stylist {
|
pub struct Stylist {
|
||||||
/// Device that the stylist is currently evaluating against.
|
/// Device that the stylist is currently evaluating against.
|
||||||
|
///
|
||||||
|
/// This field deserves a bigger comment due to the different use that Gecko
|
||||||
|
/// and Servo give to it (that we should eventually unify).
|
||||||
|
///
|
||||||
|
/// With Gecko, the device is never changed. Gecko manually tracks whether
|
||||||
|
/// the device data should be reconstructed, and "resets" the state of the
|
||||||
|
/// device.
|
||||||
|
///
|
||||||
|
/// On Servo, on the other hand, the device is a really cheap representation
|
||||||
|
/// that is recreated each time some constraint changes and calling
|
||||||
|
/// `set_device`.
|
||||||
|
///
|
||||||
|
/// In both cases, the device is actually _owned_ by the Stylist, and it's
|
||||||
|
/// only an `Arc` so we can implement `add_stylesheet` more idiomatically.
|
||||||
pub device: Arc<Device>,
|
pub device: Arc<Device>,
|
||||||
|
|
||||||
/// Viewport constraints based on the current device.
|
/// Viewport constraints based on the current device.
|
||||||
|
@ -146,6 +160,18 @@ impl Stylist {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let cascaded_rule = ViewportRule {
|
||||||
|
declarations: viewport::Cascade::from_stylesheets(doc_stylesheets, &self.device).finish(),
|
||||||
|
};
|
||||||
|
|
||||||
|
self.viewport_constraints =
|
||||||
|
ViewportConstraints::maybe_new(&self.device, &cascaded_rule);
|
||||||
|
|
||||||
|
if let Some(ref constraints) = self.viewport_constraints {
|
||||||
|
Arc::get_mut(&mut self.device).unwrap()
|
||||||
|
.account_for_viewport_rule(constraints);
|
||||||
|
}
|
||||||
|
|
||||||
self.element_map = PerPseudoElementSelectorMap::new();
|
self.element_map = PerPseudoElementSelectorMap::new();
|
||||||
self.pseudos_map = Default::default();
|
self.pseudos_map = Default::default();
|
||||||
self.animations = Default::default();
|
self.animations = Default::default();
|
||||||
|
@ -394,6 +420,13 @@ impl Stylist {
|
||||||
///
|
///
|
||||||
/// Probably worth to make the stylist own a single `Device`, and have a
|
/// Probably worth to make the stylist own a single `Device`, and have a
|
||||||
/// `update_device` function?
|
/// `update_device` function?
|
||||||
|
///
|
||||||
|
/// feature = "servo" because gecko only has one device, and manually tracks
|
||||||
|
/// when the device is dirty.
|
||||||
|
///
|
||||||
|
/// FIXME(emilio): The semantics of the device for Servo and Gecko are
|
||||||
|
/// different enough we may want to unify them.
|
||||||
|
#[cfg(feature = "servo")]
|
||||||
pub fn set_device(&mut self, mut device: Device, stylesheets: &[Arc<Stylesheet>]) {
|
pub fn set_device(&mut self, mut device: Device, stylesheets: &[Arc<Stylesheet>]) {
|
||||||
let cascaded_rule = ViewportRule {
|
let cascaded_rule = ViewportRule {
|
||||||
declarations: viewport::Cascade::from_stylesheets(stylesheets, &device).finish(),
|
declarations: viewport::Cascade::from_stylesheets(stylesheets, &device).finish(),
|
||||||
|
|
|
@ -47,6 +47,7 @@ use style::gecko_bindings::structs::{SheetParsingMode, nsIAtom, nsCSSPropertyID}
|
||||||
use style::gecko_bindings::structs::{ThreadSafePrincipalHolder, ThreadSafeURIHolder};
|
use style::gecko_bindings::structs::{ThreadSafePrincipalHolder, ThreadSafeURIHolder};
|
||||||
use style::gecko_bindings::structs::{nsRestyleHint, nsChangeHint};
|
use style::gecko_bindings::structs::{nsRestyleHint, nsChangeHint};
|
||||||
use style::gecko_bindings::structs::Loader;
|
use style::gecko_bindings::structs::Loader;
|
||||||
|
use style::gecko_bindings::structs::RawGeckoPresContextOwned;
|
||||||
use style::gecko_bindings::structs::ServoStyleSheet;
|
use style::gecko_bindings::structs::ServoStyleSheet;
|
||||||
use style::gecko_bindings::structs::nsresult;
|
use style::gecko_bindings::structs::nsresult;
|
||||||
use style::gecko_bindings::sugar::ownership::{FFIArcHelpers, HasArcFFI, HasBoxFFI};
|
use style::gecko_bindings::sugar::ownership::{FFIArcHelpers, HasArcFFI, HasBoxFFI};
|
||||||
|
@ -111,7 +112,7 @@ fn create_shared_context(per_doc_data: &PerDocumentStyleDataImpl) -> SharedStyle
|
||||||
timer: Timer::new(),
|
timer: Timer::new(),
|
||||||
// FIXME Find the real QuirksMode information for this document
|
// FIXME Find the real QuirksMode information for this document
|
||||||
quirks_mode: QuirksMode::NoQuirks,
|
quirks_mode: QuirksMode::NoQuirks,
|
||||||
default_computed_values: per_doc_data.default_computed_values.clone(),
|
default_computed_values: per_doc_data.default_computed_values().clone(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -251,7 +252,7 @@ pub extern "C" fn Servo_RestyleWithAddedDeclaration(raw_data: RawServoStyleSetBo
|
||||||
/* is_root_element = */ false,
|
/* is_root_element = */ false,
|
||||||
declarations,
|
declarations,
|
||||||
previous_style,
|
previous_style,
|
||||||
&data.default_computed_values,
|
data.default_computed_values(),
|
||||||
None,
|
None,
|
||||||
Box::new(StdoutErrorReporter),
|
Box::new(StdoutErrorReporter),
|
||||||
None,
|
None,
|
||||||
|
@ -597,7 +598,7 @@ pub extern "C" fn Servo_ComputedValues_GetForAnonymousBox(parent_style_or_null:
|
||||||
|
|
||||||
let maybe_parent = ComputedValues::arc_from_borrowed(&parent_style_or_null);
|
let maybe_parent = ComputedValues::arc_from_borrowed(&parent_style_or_null);
|
||||||
data.stylist.precomputed_values_for_pseudo(&pseudo, maybe_parent,
|
data.stylist.precomputed_values_for_pseudo(&pseudo, maybe_parent,
|
||||||
&data.default_computed_values, false)
|
data.default_computed_values(), false)
|
||||||
.values
|
.values
|
||||||
.into_strong()
|
.into_strong()
|
||||||
}
|
}
|
||||||
|
@ -618,7 +619,7 @@ pub extern "C" fn Servo_ResolvePseudoStyle(element: RawGeckoElementBorrowed,
|
||||||
return if is_probe {
|
return if is_probe {
|
||||||
Strong::null()
|
Strong::null()
|
||||||
} else {
|
} else {
|
||||||
doc_data.borrow().default_computed_values.clone().into_strong()
|
doc_data.borrow().default_computed_values().clone().into_strong()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -640,7 +641,7 @@ fn get_pseudo_style(element: GeckoElement, pseudo_tag: *mut nsIAtom,
|
||||||
PseudoElementCascadeType::Lazy => {
|
PseudoElementCascadeType::Lazy => {
|
||||||
let d = doc_data.borrow_mut();
|
let d = doc_data.borrow_mut();
|
||||||
let base = &styles.primary.values;
|
let base = &styles.primary.values;
|
||||||
d.stylist.lazily_compute_pseudo_element_style(&element, &pseudo, base, &d.default_computed_values)
|
d.stylist.lazily_compute_pseudo_element_style(&element, &pseudo, base, &d.default_computed_values())
|
||||||
.map(|s| s.values.clone())
|
.map(|s| s.values.clone())
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -654,37 +655,37 @@ pub extern "C" fn Servo_ComputedValues_Inherit(
|
||||||
let data = PerDocumentStyleData::from_ffi(raw_data).borrow();
|
let data = PerDocumentStyleData::from_ffi(raw_data).borrow();
|
||||||
let maybe_arc = ComputedValues::arc_from_borrowed(&parent_style);
|
let maybe_arc = ComputedValues::arc_from_borrowed(&parent_style);
|
||||||
let style = if let Some(reference) = maybe_arc.as_ref() {
|
let style = if let Some(reference) = maybe_arc.as_ref() {
|
||||||
ComputedValues::inherit_from(reference, &data.default_computed_values)
|
ComputedValues::inherit_from(reference, &data.default_computed_values())
|
||||||
} else {
|
} else {
|
||||||
data.default_computed_values.clone()
|
data.default_computed_values().clone()
|
||||||
};
|
};
|
||||||
style.into_strong()
|
style.into_strong()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn Servo_ComputedValues_AddRef(ptr: ServoComputedValuesBorrowed) -> () {
|
pub extern "C" fn Servo_ComputedValues_AddRef(ptr: ServoComputedValuesBorrowed) {
|
||||||
unsafe { ComputedValues::addref(ptr) };
|
unsafe { ComputedValues::addref(ptr) };
|
||||||
}
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn Servo_ComputedValues_Release(ptr: ServoComputedValuesBorrowed) -> () {
|
pub extern "C" fn Servo_ComputedValues_Release(ptr: ServoComputedValuesBorrowed) {
|
||||||
unsafe { ComputedValues::release(ptr) };
|
unsafe { ComputedValues::release(ptr) };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// See the comment in `Device` to see why it's ok to pass an owned reference to
|
||||||
|
/// the pres context (hint: the context outlives the StyleSet, that holds the
|
||||||
|
/// device alive).
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn Servo_StyleSet_Init(pres_context: RawGeckoPresContextBorrowed)
|
pub extern "C" fn Servo_StyleSet_Init(pres_context: RawGeckoPresContextOwned)
|
||||||
-> RawServoStyleSetOwned {
|
-> RawServoStyleSetOwned {
|
||||||
let data = Box::new(PerDocumentStyleData::new(pres_context));
|
let data = Box::new(PerDocumentStyleData::new(pres_context));
|
||||||
data.into_ffi()
|
data.into_ffi()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn Servo_StyleSet_RecomputeDefaultStyles(
|
pub extern "C" fn Servo_StyleSet_RebuildData(raw_data: RawServoStyleSetBorrowed) {
|
||||||
raw_data: RawServoStyleSetBorrowed,
|
|
||||||
pres_context: RawGeckoPresContextBorrowed) {
|
|
||||||
let mut data = PerDocumentStyleData::from_ffi(raw_data).borrow_mut();
|
let mut data = PerDocumentStyleData::from_ffi(raw_data).borrow_mut();
|
||||||
data.default_computed_values = ComputedValues::default_values(pres_context);
|
data.device_changed = true;
|
||||||
// FIXME(bz): We need to update our Stylist's Device's computed values, but how?
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
|
@ -1034,7 +1035,7 @@ pub extern "C" fn Servo_ResolveStyle(element: RawGeckoElementBorrowed,
|
||||||
if !data.has_current_styles() {
|
if !data.has_current_styles() {
|
||||||
error!("Resolving style on unstyled element with lazy computation forbidden.");
|
error!("Resolving style on unstyled element with lazy computation forbidden.");
|
||||||
let per_doc_data = PerDocumentStyleData::from_ffi(raw_data).borrow();
|
let per_doc_data = PerDocumentStyleData::from_ffi(raw_data).borrow();
|
||||||
return per_doc_data.default_computed_values.clone().into_strong();
|
return per_doc_data.default_computed_values().clone().into_strong();
|
||||||
}
|
}
|
||||||
|
|
||||||
data.styles().primary.values.clone().into_strong()
|
data.styles().primary.values.clone().into_strong()
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue