Auto merge of #29772 - Loirooriol:sync, r=mrobinson

Backport several style changes from Gecko (2)

<!-- Please describe your changes on the following line: -->
This continues #29748.

---
<!-- Thank you for contributing to Servo! Please replace each `[ ]` by `[X]` when the step is complete, and replace `___` with appropriate data: -->
- [X] `./mach build -d` does not report any errors
- [X] `./mach test-tidy` does not report any errors
- [ ] These changes fix #___ (GitHub issue number if applicable)

<!-- Either: -->
- [X] There are tests for these changes OR
- [ ] These changes do not require tests because ___

<!-- Also, please make sure that "Allow edits from maintainers" checkbox is checked, so that we can help you if you get stuck somewhere along the way.-->

<!-- Pull requests that do not address these steps are welcome, but they will require additional verification as part of the review process. -->
This commit is contained in:
bors-servo 2023-05-25 19:20:50 +02:00 committed by GitHub
commit 867326c46a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
146 changed files with 2524 additions and 1683 deletions

View file

@ -127,6 +127,7 @@ stroke-opacity
storage
submit
suspend
system-ui
tel
text
time

View file

@ -1387,6 +1387,8 @@ fn to_font_kit_family(font_family: &font::SingleFontFamily) -> FamilyName {
font::GenericFontFamily::Monospace => FamilyName::Monospace,
font::GenericFontFamily::Fantasy => FamilyName::Fantasy,
font::GenericFontFamily::Cursive => FamilyName::Cursive,
// TODO: There is no FontFamily::SystemUi.
font::GenericFontFamily::SystemUi => unreachable!("system-ui should be disabled"),
font::GenericFontFamily::None => unreachable!("Shouldn't appear in computed values"),
},
}

View file

@ -569,6 +569,7 @@ impl<'a> From<&'a SingleFontFamily> for FontFamilyName {
GenericFontFamily::Monospace => atom!("monospace"),
GenericFontFamily::Cursive => atom!("cursive"),
GenericFontFamily::Fantasy => atom!("fantasy"),
GenericFontFamily::SystemUi => atom!("system-ui"),
}),
}
}

View file

@ -120,7 +120,9 @@ fn font_family(names: Vec<&str>) -> FontFamily {
.collect();
FontFamily {
families: FontFamilyList::new(names.into_boxed_slice()),
families: FontFamilyList {
list: names.into_boxed_slice(),
},
is_system_font: false,
}
}

View file

@ -733,9 +733,9 @@ impl<'dom> LayoutElementHelpers<'dom> for LayoutDom<'dom, Element> {
hints.push(from_declaration(
shared_lock,
PropertyDeclaration::FontFamily(font_family::SpecifiedValue::Values(
computed::font::FontFamilyList::new(Box::new([
computed::font::SingleFontFamily::from_atom(font_family),
])),
computed::font::FontFamilyList {
list: Box::new([computed::font::SingleFontFamily::from_atom(font_family)]),
},
)),
));
}

View file

@ -21,7 +21,6 @@ use crate::dom::node::{
use crate::dom::virtualmethods::VirtualMethods;
use dom_struct::dom_struct;
use html5ever::{LocalName, Prefix};
use parking_lot::RwLock;
use servo_arc::Arc;
use servo_config::pref;
use std::sync::atomic::AtomicBool;
@ -112,15 +111,12 @@ impl HTMLMetaElement {
let shared_lock = document.style_shared_lock();
let rule = CssRule::Viewport(Arc::new(shared_lock.wrap(translated_rule)));
let sheet = Arc::new(Stylesheet {
contents: StylesheetContents {
rules: CssRules::new(vec![rule], shared_lock),
origin: Origin::Author,
namespaces: Default::default(),
quirks_mode: document.quirks_mode(),
url_data: RwLock::new(window_from_node(self).get_url()),
source_map_url: RwLock::new(None),
source_url: RwLock::new(None),
},
contents: StylesheetContents::from_shared_data(
CssRules::new(vec![rule], shared_lock),
Origin::Author,
window_from_node(self).get_url(),
document.quirks_mode(),
),
media: Arc::new(shared_lock.wrap(MediaList::empty())),
shared_lock: shared_lock.clone(),
disabled: AtomicBool::new(false),

View file

@ -29,7 +29,6 @@ use net_traits::{
FetchMetadata, FetchResponseListener, FilteredMetadata, Metadata, NetworkError, ReferrerPolicy,
};
use net_traits::{ResourceFetchTiming, ResourceTimingType};
use parking_lot::RwLock;
use servo_arc::Arc;
use servo_url::ImmutableOrigin;
use servo_url::ServoUrl;
@ -41,9 +40,7 @@ use style::parser::ParserContext;
use style::shared_lock::{Locked, SharedRwLock};
use style::stylesheets::import_rule::ImportSheet;
use style::stylesheets::StylesheetLoader as StyleStylesheetLoader;
use style::stylesheets::{
CssRules, ImportRule, Namespaces, Origin, Stylesheet, StylesheetContents,
};
use style::stylesheets::{CssRules, ImportRule, Origin, Stylesheet, StylesheetContents};
use style::values::CssUrl;
pub trait StylesheetOwner {
@ -363,15 +360,12 @@ impl<'a> StyleStylesheetLoader for StylesheetLoader<'a> {
media: Arc<Locked<MediaList>>,
) -> Arc<Locked<ImportRule>> {
let sheet = Arc::new(Stylesheet {
contents: StylesheetContents {
rules: CssRules::new(Vec::new(), lock),
origin: context.stylesheet_origin,
url_data: RwLock::new(context.url_data.clone()),
quirks_mode: context.quirks_mode,
namespaces: RwLock::new(Namespaces::default()),
source_map_url: RwLock::new(None),
source_url: RwLock::new(None),
},
contents: StylesheetContents::from_shared_data(
CssRules::new(Vec::new(), lock),
context.stylesheet_origin,
context.url_data.clone(),
context.quirks_mode,
),
media: media,
shared_lock: lock.clone(),
disabled: AtomicBool::new(false),

View file

@ -256,11 +256,6 @@ pub trait Parser<'i> {
false
}
/// The error recovery that selector lists inside :is() and :where() have.
fn is_and_where_error_recovery(&self) -> ParseErrorRecovery {
ParseErrorRecovery::IgnoreInvalidSelector
}
/// Whether the given function name is an alias for the `:is()` function.
fn is_is_alias(&self, _name: &str) -> bool {
false
@ -344,7 +339,7 @@ pub struct SelectorList<Impl: SelectorImpl>(
);
/// How to treat invalid selectors in a selector list.
pub enum ParseErrorRecovery {
enum ParseErrorRecovery {
/// Discard the entire selector list, this is the default behavior for
/// almost all of CSS.
DiscardList,
@ -2277,7 +2272,7 @@ where
state |
SelectorParsingState::SKIP_DEFAULT_NAMESPACE |
SelectorParsingState::DISALLOW_PSEUDOS,
parser.is_and_where_error_recovery(),
ParseErrorRecovery::IgnoreInvalidSelector,
)?;
Ok(component(inner.0.into_vec().into_boxed_slice()))
}
@ -2686,10 +2681,6 @@ pub mod tests {
true
}
fn is_and_where_error_recovery(&self) -> ParseErrorRecovery {
ParseErrorRecovery::DiscardList
}
fn parse_part(&self) -> bool {
true
}
@ -3294,9 +3285,9 @@ pub mod tests {
assert!(parse("::slotted(div)::before").is_ok());
assert!(parse("slot::slotted(div,foo)").is_err());
assert!(parse("foo:where()").is_err());
assert!(parse("foo:where()").is_ok());
assert!(parse("foo:where(div, foo, .bar baz)").is_ok());
assert!(parse("foo:where(::before)").is_err());
assert!(parse_expected("foo:where(::before)", Some("foo:where()")).is_ok());
}
#[test]

View file

@ -248,6 +248,14 @@ impl<T> Arc<T> {
}
}
/// Like from_raw, but returns an addrefed arc instead.
#[inline]
pub unsafe fn from_raw_addrefed(ptr: *const T) -> Self {
let arc = Self::from_raw(ptr);
mem::forget(arc.clone());
arc
}
/// Create a new static Arc<T> (one that won't reference count the object)
/// and place it in the allocation provided by the specified `alloc`
/// function.

View file

@ -27,7 +27,6 @@ servo-layout-2013 = []
servo-layout-2020 = []
gecko_debug = []
gecko_refcount_logging = []
gecko_profiler = []
[dependencies]
app_units = "0.7"

View file

@ -46,11 +46,15 @@ lazy_static! {
.join("layout/style/ServoBindings.toml");
read_config(&path)
};
static ref BUILD_CONFIG: Table = {
static ref BINDGEN_FLAGS: Vec<String> = {
// Load build-specific config overrides.
let path = PathBuf::from(env::var_os("MOZ_TOPOBJDIR").unwrap())
.join("layout/style/bindgen.toml");
read_config(&path)
.join("layout/style/extra-bindgen-flags");
println!("cargo:rerun-if-changed={}", path.to_str().unwrap());
fs::read_to_string(path).expect("Failed to read extra-bindgen-flags file")
.split_whitespace()
.map(std::borrow::ToOwned::to_owned)
.collect()
};
static ref INCLUDE_RE: Regex = Regex::new(r#"#include\s*"(.+?)""#).unwrap();
static ref DISTDIR_PATH: PathBuf = {
@ -159,12 +163,8 @@ impl BuilderExt for Builder {
builder = builder.clang_arg("-DDEBUG=1").clang_arg("-DJS_DEBUG=1");
}
let build_config = BUILD_CONFIG["build"]
.as_table()
.expect("Malformed config file");
let extra_bindgen_flags = build_config["args"].as_array().unwrap().as_slice();
for item in extra_bindgen_flags.iter() {
builder = builder.clang_arg(item.as_str().expect("Expect string in list"));
for item in &*BINDGEN_FLAGS {
builder = builder.clang_arg(item);
}
builder

View file

@ -55,7 +55,12 @@ pub fn parse_counter_style_name<'i, 't>(
}
fn is_valid_name_definition(ident: &CustomIdent) -> bool {
ident.0 != atom!("decimal") && ident.0 != atom!("disc")
ident.0 != atom!("decimal")
&& ident.0 != atom!("disc")
&& ident.0 != atom!("circle")
&& ident.0 != atom!("square")
&& ident.0 != atom!("disclosure-closed")
&& ident.0 != atom!("disclosure-open")
}
/// Parse the prelude of an @counter-style rule

View file

@ -88,7 +88,7 @@ pub type Name = Atom;
///
/// <https://drafts.csswg.org/css-variables/#typedef-custom-property-name>
pub fn parse_name(s: &str) -> Result<&str, ()> {
if s.starts_with("--") {
if s.starts_with("--") && s.len() > 2 {
Ok(&s[2..])
} else {
Err(())
@ -318,11 +318,6 @@ fn parse_declaration_value<'i, 't>(
missing_closing_characters: &mut String,
) -> Result<(TokenSerializationType, TokenSerializationType), ParseError<'i>> {
input.parse_until_before(Delimiter::Bang | Delimiter::Semicolon, |input| {
// Need at least one token
let start = input.state();
input.next_including_whitespace()?;
input.reset(&start);
parse_declaration_value_block(input, references, missing_closing_characters)
})
}
@ -334,6 +329,7 @@ fn parse_declaration_value_block<'i, 't>(
mut references: Option<&mut VarOrEnvReferences>,
missing_closing_characters: &mut String,
) -> Result<(TokenSerializationType, TokenSerializationType), ParseError<'i>> {
input.skip_whitespace();
let mut token_start = input.position();
let mut token = match input.next_including_whitespace_and_comments() {
Ok(token) => token,
@ -477,10 +473,8 @@ fn parse_fallback<'i, 't>(input: &mut Parser<'i, 't>) -> Result<(), ParseError<'
// Exclude `!` and `;` at the top level
// https://drafts.csswg.org/css-syntax/#typedef-declaration-value
input.parse_until_before(Delimiter::Bang | Delimiter::Semicolon, |input| {
// At least one non-comment token.
input.next_including_whitespace()?;
// Skip until the end.
while let Ok(_) = input.next_including_whitespace_and_comments() {}
while input.next_including_whitespace_and_comments().is_ok() {}
Ok(())
})
}
@ -996,12 +990,12 @@ fn substitute_block<'i>(
while input.next().is_ok() {}
} else {
input.expect_comma()?;
input.skip_whitespace();
let after_comma = input.state();
let first_token_type = input
.next_including_whitespace_and_comments()
// parse_var_function() ensures that .unwrap() will not fail.
.unwrap()
.serialization_type();
.ok()
.map_or_else(TokenSerializationType::nothing, |t| t.serialization_type());
input.reset(&after_comma);
let mut position = (after_comma.position(), first_token_type);
last_token_type = substitute_block(

View file

@ -175,6 +175,25 @@ impl ElementStyles {
self.primary().get_box().clone_display().is_none()
}
/// Whether this element uses viewport units.
pub fn uses_viewport_units(&self) -> bool {
use crate::computed_value_flags::ComputedValueFlags;
if self.primary().flags.intersects(ComputedValueFlags::USES_VIEWPORT_UNITS) {
return true;
}
for pseudo_style in self.pseudos.as_array() {
if let Some(ref pseudo_style) = pseudo_style {
if pseudo_style.flags.intersects(ComputedValueFlags::USES_VIEWPORT_UNITS) {
return true;
}
}
}
false
}
#[cfg(feature = "gecko")]
fn size_of_excluding_cvs(&self, _ops: &mut MallocSizeOfOps) -> usize {
// As the method name suggests, we don't measures the ComputedValues

View file

@ -151,7 +151,7 @@ pub trait TNode: Sized + Copy + Clone + Debug + NodeInfo + PartialEq {
/// Get this node's first child.
fn first_child(&self) -> Option<Self>;
/// Get this node's first child.
/// Get this node's last child.
fn last_child(&self) -> Option<Self>;
/// Get this node's previous sibling.
@ -881,7 +881,7 @@ pub trait TElement:
}
}
// TODO: Could be more granular.
if !shadow.host().exports_any_part() {
if !inner_shadow_host.exports_any_part() {
break;
}
inner_shadow = shadow;

View file

@ -140,7 +140,8 @@ where
// ensures that we process all the elements at a given depth before
// proceeding to the next depth, which is important for style sharing.
rayon::scope_fifo(|scope| {
profiler_label!(Style);
#[cfg(feature = "gecko")]
gecko_profiler_label!(Layout, StyleComputation);
parallel::traverse_nodes(
drain,
DispatchMode::TailCall,

View file

@ -56,10 +56,17 @@ impl GeckoStyleSheet {
/// already holds a strong reference.
#[inline]
pub unsafe fn from_addrefed(s: *const DomStyleSheet) -> Self {
debug_assert!(!s.is_null());
assert!(!s.is_null());
GeckoStyleSheet(s)
}
/// HACK(emilio): This is so that we can avoid crashing release due to
/// bug 1719963 and can hopefully get a useful report from fuzzers.
#[inline]
pub fn hack_is_null(&self) -> bool {
self.0.is_null()
}
/// Get the raw `StyleSheet` that we're wrapping.
pub fn raw(&self) -> &DomStyleSheet {
unsafe { &*self.0 }

View file

@ -590,6 +590,23 @@ fn eval_moz_os_version(
query_value.as_ptr() == os_version
}
fn eval_moz_windows_non_native_menus(
device: &Device,
query_value: Option<bool>,
_: Option<RangeOrOperator>,
) -> bool {
let use_non_native_menus = match static_prefs::pref!("browser.display.windows.non_native_menus") {
0 => false,
1 => true,
_ => {
eval_moz_os_version(device, Some(atom!("windows-win10")), None) &&
get_lnf_int_as_bool(bindings::LookAndFeel_IntID::WindowsDefaultTheme as i32)
},
};
query_value.map_or(use_non_native_menus, |v| v == use_non_native_menus)
}
fn get_lnf_int(int_id: i32) -> i32 {
unsafe { bindings::Gecko_GetLookAndFeelInt(int_id) }
}
@ -663,7 +680,7 @@ macro_rules! bool_pref_feature {
/// 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 MEDIA_FEATURES: [MediaFeatureDescription; 62] = [
pub static MEDIA_FEATURES: [MediaFeatureDescription; 60] = [
feature!(
atom!("width"),
AllowsRanges::Yes,
@ -879,6 +896,12 @@ pub static MEDIA_FEATURES: [MediaFeatureDescription; 62] = [
keyword_evaluator!(eval_toolbar_prefers_color_scheme, ToolbarPrefersColorScheme),
ParsingRequirements::CHROME_AND_UA_ONLY,
),
feature!(
atom!("-moz-windows-non-native-menus"),
AllowsRanges::No,
Evaluator::BoolInteger(eval_moz_windows_non_native_menus),
ParsingRequirements::CHROME_AND_UA_ONLY,
),
lnf_int_feature!(atom!("-moz-scrollbar-start-backward"), ScrollArrowStyle, get_scrollbar_start_backward),
lnf_int_feature!(atom!("-moz-scrollbar-start-forward"), ScrollArrowStyle, get_scrollbar_start_forward),
@ -903,10 +926,6 @@ pub static MEDIA_FEATURES: [MediaFeatureDescription; 62] = [
lnf_int_feature!(atom!("-moz-gtk-csd-close-button"), GTKCSDCloseButton),
lnf_int_feature!(atom!("-moz-gtk-csd-reversed-placement"), GTKCSDReversedPlacement),
lnf_int_feature!(atom!("-moz-system-dark-theme"), SystemUsesDarkTheme),
bool_pref_feature!(atom!("-moz-proton"), "browser.proton.enabled"),
bool_pref_feature!(atom!("-moz-proton-modals"), "browser.proton.modals.enabled"),
bool_pref_feature!(atom!("-moz-proton-contextmenus"), "browser.proton.contextmenus.enabled"),
bool_pref_feature!(atom!("-moz-proton-doorhangers"), "browser.proton.doorhangers.enabled"),
bool_pref_feature!(atom!("-moz-proton-places-tooltip"), "browser.proton.places-tooltip.enabled"),
];

View file

@ -93,7 +93,7 @@ impl Device {
document,
default_values: ComputedValues::default_values(doc),
root_font_size: AtomicU32::new(FONT_MEDIUM_PX.to_bits()),
body_text_color: AtomicUsize::new(prefs.mDefaultColor as usize),
body_text_color: AtomicUsize::new(prefs.mColors.mDefault as usize),
used_root_font_size: AtomicBool::new(false),
used_font_metrics: AtomicBool::new(false),
used_viewport_size: AtomicBool::new(false),
@ -387,12 +387,12 @@ impl Device {
/// Returns the default background color.
pub fn default_background_color(&self) -> RGBA {
convert_nscolor_to_rgba(self.pref_sheet_prefs().mDefaultBackgroundColor)
convert_nscolor_to_rgba(self.pref_sheet_prefs().mColors.mDefaultBackground)
}
/// Returns the default foreground color.
pub fn default_color(&self) -> RGBA {
convert_nscolor_to_rgba(self.pref_sheet_prefs().mDefaultColor)
convert_nscolor_to_rgba(self.pref_sheet_prefs().mColors.mDefault)
}
/// Returns the current effective text zoom.

View file

@ -13,8 +13,6 @@ pub mod conversions;
pub mod data;
pub mod media_features;
pub mod media_queries;
#[cfg(feature = "gecko_profiler")]
pub mod profiler;
pub mod pseudo_element;
pub mod restyle_damage;
pub mod selector_parser;

View file

@ -1,75 +0,0 @@
/* 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 https://mozilla.org/MPL/2.0/. */
//! Gecko profiler support.
//!
//! Use the `profiler_label!` macro from macros.rs.
use crate::gecko_bindings::structs;
/// A label describing a category of work that style threads can perform.
pub enum ProfilerLabel {
/// Style computation.
Style,
/// Style sheet parsing.
Parse,
}
/// RAII object that constructs and destroys a C++ AutoProfilerLabel object
/// pointed to be the specified reference.
#[cfg(feature = "gecko_profiler")]
pub struct AutoProfilerLabel<'a>(&'a mut structs::AutoProfilerLabel);
#[cfg(feature = "gecko_profiler")]
impl<'a> AutoProfilerLabel<'a> {
/// Creates a new AutoProfilerLabel with the specified label type.
///
/// unsafe since the caller must ensure that `label` is allocated on the
/// stack.
#[inline]
pub unsafe fn new(
label: &mut std::mem::MaybeUninit<structs::AutoProfilerLabel>,
label_type: ProfilerLabel,
) -> AutoProfilerLabel {
let category_pair = match label_type {
ProfilerLabel::Style => structs::JS::ProfilingCategoryPair_LAYOUT_StyleComputation,
ProfilerLabel::Parse => structs::JS::ProfilingCategoryPair_LAYOUT_CSSParsing,
};
structs::Gecko_Construct_AutoProfilerLabel(label.as_mut_ptr(), category_pair);
AutoProfilerLabel(&mut *label.as_mut_ptr())
}
}
#[cfg(feature = "gecko_profiler")]
impl<'a> Drop for AutoProfilerLabel<'a> {
#[inline]
fn drop(&mut self) {
unsafe {
structs::Gecko_Destroy_AutoProfilerLabel(self.0);
}
}
}
/// Whether the Gecko profiler is currently active.
///
/// This implementation must be kept in sync with
/// `mozilla::profiler::detail::RacyFeatures::IsActive`.
#[cfg(feature = "gecko_profiler")]
#[inline]
pub fn profiler_is_active() -> bool {
use self::structs::profiler::detail;
use std::mem;
use std::sync::atomic::{AtomicU32, Ordering};
let active_and_features: &AtomicU32 =
unsafe { mem::transmute(&detail::RacyFeatures_sActiveAndFeatures) };
(active_and_features.load(Ordering::Relaxed) & detail::RacyFeatures_Active) != 0
}
/// Always false when the Gecko profiler is disabled.
#[cfg(not(feature = "gecko_profiler"))]
#[inline]
pub fn profiler_is_active() -> bool {
false
}

View file

@ -14,7 +14,7 @@ use crate::string_cache::{Atom, Namespace, WeakAtom, WeakNamespace};
use crate::values::{AtomIdent, AtomString};
use cssparser::{BasicParseError, BasicParseErrorKind, Parser};
use cssparser::{CowRcStr, SourceLocation, ToCss, Token};
use selectors::parser::{ParseErrorRecovery, SelectorParseErrorKind};
use selectors::parser::SelectorParseErrorKind;
use selectors::SelectorList;
use std::fmt;
use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss as ToCss_};
@ -311,15 +311,6 @@ impl<'a, 'i> ::selectors::Parser<'i> for SelectorParser<'a> {
true
}
#[inline]
fn is_and_where_error_recovery(&self) -> ParseErrorRecovery {
if static_prefs::pref!("layout.css.is-and-where-better-error-recovery.enabled") {
ParseErrorRecovery::IgnoreInvalidSelector
} else {
ParseErrorRecovery::DiscardList
}
}
#[inline]
fn parse_part(&self) -> bool {
true

View file

@ -409,9 +409,11 @@ impl<'ln> TNode for GeckoNode<'ln> {
#[inline]
fn prev_sibling(&self) -> Option<Self> {
unsafe {
bindings::Gecko_GetPreviousSibling(self.0)
.as_ref()
.map(GeckoNode)
let prev_or_last = GeckoNode::from_content(self.0.mPreviousOrLastSibling.as_ref()?);
if prev_or_last.0.mNextSibling.raw::<nsIContent>().is_null() {
return None;
}
Some(prev_or_last)
}
}

View file

@ -308,11 +308,6 @@ impl_threadsafe_refcount!(
bindings::Gecko_AddRefnsIURIArbitraryThread,
bindings::Gecko_ReleasensIURIArbitraryThread
);
impl_threadsafe_refcount!(
structs::SharedFontList,
bindings::Gecko_AddRefSharedFontListArbitraryThread,
bindings::Gecko_ReleaseSharedFontListArbitraryThread
);
impl_threadsafe_refcount!(
structs::SheetLoadDataHolder,
bindings::Gecko_AddRefSheetLoadDataHolderArbitraryThread,

View file

@ -10,6 +10,8 @@ use crate::gecko_bindings::bindings;
use crate::parallel::STYLE_THREAD_STACK_SIZE_KB;
use crate::shared_lock::SharedRwLock;
use crate::thread_state;
#[cfg(feature = "gecko")]
use gecko_profiler;
use parking_lot::{RwLock, RwLockReadGuard};
use rayon;
use std::env;
@ -49,20 +51,16 @@ fn thread_startup(_index: usize) {
thread_state::initialize_layout_worker_thread();
#[cfg(feature = "gecko")]
unsafe {
use std::ffi::CString;
bindings::Gecko_SetJemallocThreadLocalArena(true);
let name = thread_name(_index);
let name = CString::new(name).unwrap();
// Gecko_RegisterProfilerThread copies the passed name here.
bindings::Gecko_RegisterProfilerThread(name.as_ptr());
gecko_profiler::register_thread(&name);
}
}
fn thread_shutdown(_: usize) {
#[cfg(feature = "gecko")]
unsafe {
bindings::Gecko_UnregisterProfilerThread();
gecko_profiler::unregister_thread();
bindings::Gecko_SetJemallocThreadLocalArena(false);
}
ALIVE_WORKER_THREADS.fetch_sub(1, Ordering::Relaxed);

View file

@ -60,7 +60,12 @@ impl RestyleHint {
/// Returns whether this hint invalidates the element and all its
/// descendants.
pub fn contains_subtree(&self) -> bool {
self.contains(RestyleHint::RESTYLE_SELF | RestyleHint::RESTYLE_DESCENDANTS)
self.contains(Self::restyle_subtree())
}
/// Returns whether we'll recascade all of the descendants.
pub fn will_recascade_subtree(&self) -> bool {
self.contains_subtree() || self.contains(Self::recascade_subtree())
}
/// Returns whether we need to restyle this element.

View file

@ -7,3 +7,4 @@
pub mod element;
pub mod media_queries;
pub mod stylesheets;
pub mod viewport_units;

View file

@ -0,0 +1,57 @@
/* 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 https://mozilla.org/MPL/2.0/. */
//! Invalidates style of all elements that depend on viewport units.
use crate::dom::{TElement, TNode};
use crate::invalidation::element::restyle_hints::RestyleHint;
/// Invalidates style of all elements that depend on viewport units.
///
/// Returns whether any element was invalidated.
pub fn invalidate<E>(root: E) -> bool
where
E: TElement,
{
debug!("invalidation::viewport_units::invalidate({:?})", root);
invalidate_recursively(root)
}
fn invalidate_recursively<E>(element: E) -> bool
where
E: TElement,
{
let mut data = match element.mutate_data() {
Some(data) => data,
None => return false,
};
if data.hint.will_recascade_subtree() {
debug!("invalidate_recursively: {:?} was already invalid", element);
return false;
}
let uses_viewport_units = data.styles.uses_viewport_units();
if uses_viewport_units {
debug!("invalidate_recursively: {:?} uses viewport units", element);
data.hint.insert(RestyleHint::RECASCADE_SELF);
}
let mut any_children_invalid = false;
for child in element.traversal_children() {
if let Some(child) = child.as_element() {
any_children_invalid |= invalidate_recursively(child);
}
}
if any_children_invalid {
debug!(
"invalidate_recursively: Children of {:?} changed, setting dirty descendants",
element
);
unsafe { element.set_dirty_descendants() }
}
uses_viewport_units || any_children_invalid
}

View file

@ -33,6 +33,9 @@ extern crate cssparser;
extern crate debug_unreachable;
#[macro_use]
extern crate derive_more;
#[macro_use]
#[cfg(feature = "gecko")]
extern crate gecko_profiler;
#[cfg(feature = "gecko")]
#[macro_use]
pub mod gecko_string_cache;

View file

@ -105,35 +105,6 @@ macro_rules! define_keyword_type {
};
}
/// Place a Gecko profiler label on the stack.
///
/// The `label_type` argument must be the name of a variant of `ProfilerLabel`.
#[cfg(feature = "gecko_profiler")]
#[macro_export]
macro_rules! profiler_label {
($label_type:ident) => {
let mut _profiler_label =
::std::mem::MaybeUninit::<$crate::gecko_bindings::structs::AutoProfilerLabel>::uninit();
let _profiler_label = if $crate::gecko::profiler::profiler_is_active() {
unsafe {
Some($crate::gecko::profiler::AutoProfilerLabel::new(
&mut _profiler_label,
$crate::gecko::profiler::ProfilerLabel::$label_type,
))
}
} else {
None
};
};
}
/// No-op when the Gecko profiler is not available.
#[cfg(not(feature = "gecko_profiler"))]
#[macro_export]
macro_rules! profiler_label {
($label_type:ident) => {};
}
#[cfg(feature = "servo")]
macro_rules! local_name {
($s:tt) => {

View file

@ -274,7 +274,8 @@ pub fn traverse_nodes<'a, 'scope, E, D, I>(
top_down_dom(&work, root, traversal_data, scope, pool, traversal, tls);
} else {
scope.spawn_fifo(move |scope| {
profiler_label!(Style);
#[cfg(feature = "gecko")]
gecko_profiler_label!(Layout, StyleComputation);
let work = work;
top_down_dom(&work, root, traversal_data, scope, pool, traversal, tls);
});
@ -284,7 +285,8 @@ pub fn traverse_nodes<'a, 'scope, E, D, I>(
let nodes: WorkUnit<E::ConcreteNode> = chunk.collect();
let traversal_data_copy = traversal_data.clone();
scope.spawn_fifo(move |scope| {
profiler_label!(Style);
#[cfg(feature = "gecko")]
gecko_profiler_label!(Layout, StyleComputation);
let n = nodes;
top_down_dom(&*n, root, traversal_data_copy, scope, pool, traversal, tls)
});

View file

@ -464,7 +464,22 @@ fn tweak_when_ignoring_colors(
}
}
},
_ => {},
_ => {
// We honor transparent and system colors more generally for all
// colors.
//
// NOTE(emilio): This doesn't handle caret-color and
// accent-color because those use a slightly different syntax
// (<color> | auto for example). That's probably fine though, as
// using a system color for caret-color doesn't make sense (using
// currentColor is fine), and we ignore accent-color in
// high-contrast-mode anyways.
if let Some(color) = declaration.color_value() {
if color.is_system() || alpha_channel(color, context) == 0 {
return;
}
}
},
}
*declaration.to_mut() =
@ -786,6 +801,35 @@ impl<'a, 'b: 'a> Cascade<'a, 'b> {
builder.add_flags(ComputedValueFlags::HAS_AUTHOR_SPECIFIED_PADDING);
}
if self
.author_specified
.contains(LonghandId::FontFamily)
{
builder.add_flags(ComputedValueFlags::HAS_AUTHOR_SPECIFIED_FONT_FAMILY);
}
if self
.author_specified
.contains(LonghandId::LetterSpacing)
{
builder.add_flags(ComputedValueFlags::HAS_AUTHOR_SPECIFIED_LETTER_SPACING);
}
if self
.author_specified
.contains(LonghandId::WordSpacing)
{
builder.add_flags(ComputedValueFlags::HAS_AUTHOR_SPECIFIED_WORD_SPACING);
}
#[cfg(feature = "gecko")]
if self
.author_specified
.contains(LonghandId::FontSynthesis)
{
builder.add_flags(ComputedValueFlags::HAS_AUTHOR_SPECIFIED_FONT_SYNTHESIS);
}
#[cfg(feature = "servo")]
{
if let Some(font) = builder.get_font_if_mutated() {
@ -849,18 +893,19 @@ impl<'a, 'b: 'a> Cascade<'a, 'b> {
// System fonts are all right, and should have the default font type
// set to none already, so bail out early.
if font.mFont.systemFont {
if font.mFont.family.is_system_font {
debug_assert_eq!(
font.mFont.fontlist.mDefaultFontType,
font.mFont.family.families.fallback,
GenericFontFamily::None
);
return;
}
let generic = font.mFont.family.families.single_generic().unwrap_or(GenericFontFamily::None);
let default_font_type = unsafe {
bindings::Gecko_nsStyleFont_ComputeDefaultFontType(
builder.device.document(),
font.mGenericID,
generic,
font.mLanguage.mRawPtr,
)
};
@ -870,15 +915,15 @@ impl<'a, 'b: 'a> Cascade<'a, 'b> {
// cursive or fantasy, since they're ignored, see bug 789788), and
// we have a generic family to actually replace it with.
let prioritize_user_fonts = !use_document_fonts &&
default_font_type != GenericFontFamily::None &&
matches!(
font.mGenericID,
generic,
GenericFontFamily::None |
GenericFontFamily::Fantasy |
GenericFontFamily::Cursive
) &&
default_font_type != GenericFontFamily::None;
GenericFontFamily::Fantasy |
GenericFontFamily::Cursive
);
if !prioritize_user_fonts && default_font_type == font.mFont.fontlist.mDefaultFontType {
if !prioritize_user_fonts && default_font_type == font.mFont.family.families.fallback {
// Nothing to do.
return;
}
@ -886,9 +931,9 @@ impl<'a, 'b: 'a> Cascade<'a, 'b> {
};
let font = builder.mutate_font().gecko_mut();
font.mFont.fontlist.mDefaultFontType = default_font_type;
font.mFont.family.families.fallback = default_font_type;
if prioritize_user_fonts {
unsafe { bindings::Gecko_nsStyleFont_PrioritizeUserFonts(font, default_font_type) }
font.mFont.family.families.prioritize_first_generic_or_prepend(default_font_type);
}
}

View file

@ -12,7 +12,7 @@ bitflags! {
/// If we ever want to add some flags that shouldn't inherit for them,
/// we might want to add a function to handle this.
#[repr(C)]
pub struct ComputedValueFlags: u16 {
pub struct ComputedValueFlags: u32 {
/// Whether the style or any of the ancestors has a text-decoration-line
/// property that should get propagated to descendants.
///
@ -89,6 +89,21 @@ bitflags! {
/// FIXME(emilio): Try to merge this with BORDER_BACKGROUND, see
/// https://github.com/w3c/csswg-drafts/issues/4777
const HAS_AUTHOR_SPECIFIED_PADDING = 1 << 15;
/// Whether there are author-specified rules for `font-family`.
const HAS_AUTHOR_SPECIFIED_FONT_FAMILY = 1 << 16;
/// Whether there are author-specified rules for `font-synthesis`.
const HAS_AUTHOR_SPECIFIED_FONT_SYNTHESIS = 1 << 17;
/// Whether there are author-specified rules for `letter-spacing`.
const HAS_AUTHOR_SPECIFIED_LETTER_SPACING = 1 << 18;
/// Whether there are author-specified rules for `word-spacing`.
const HAS_AUTHOR_SPECIFIED_WORD_SPACING = 1 << 19;
/// Whether the style depends on viewport units.
const USES_VIEWPORT_UNITS = 1 << 20;
}
}

View file

@ -25,7 +25,6 @@ COUNTED_UNKNOWN_PROPERTIES = [
"-webkit-user-modify",
"-webkit-margin-before",
"-webkit-margin-after",
"tab-size",
"-webkit-margin-start",
"-webkit-column-break-inside",
"-webkit-padding-start",
@ -63,7 +62,6 @@ COUNTED_UNKNOWN_PROPERTIES = [
"-webkit-text-combine",
"-webkit-text-emphasis-style",
"-webkit-text-emphasis",
"d",
"-webkit-mask-box-image-width",
"-webkit-mask-box-image-source",
"-webkit-mask-box-image-outset",
@ -123,4 +121,5 @@ COUNTED_UNKNOWN_PROPERTIES = [
"-webkit-columns",
"-webkit-column-rule-color",
"-webkit-shape-margin",
"content-visibility",
]

View file

@ -340,13 +340,14 @@ class Longhand(Property):
assert (
has_effect_on_gecko_scrollbars in [None, False, True]
and not style_struct.inherited
or (gecko_pref is None) == (has_effect_on_gecko_scrollbars is None)
or (gecko_pref is None and enabled_in != "")
== (has_effect_on_gecko_scrollbars is None)
), (
"Property "
+ name
+ ": has_effect_on_gecko_scrollbars must be "
+ "specified, and must have a value of True or False, iff a "
+ "property is inherited and is behind a Gecko pref"
+ "property is inherited and is behind a Gecko pref or internal"
)
self.need_index = need_index
self.gecko_ffi_name = gecko_ffi_name or "m" + self.camel_case
@ -759,7 +760,7 @@ def _add_logical_props(data, props):
# These are probably Gecko bugs and should be supported per spec.
def _remove_common_first_line_and_first_letter_properties(props, engine):
if engine == "gecko":
props.remove("-moz-tab-size")
props.remove("tab-size")
props.remove("hyphens")
props.remove("line-break")
props.remove("text-align-last")

View file

@ -385,7 +385,7 @@ impl PropertyDeclarationBlock {
// Step 1.2.3
// We don't print !important when serializing individual properties,
// so we treat this as a normal-importance property
match shorthand.get_shorthand_appendable_value(list.iter().cloned()) {
match shorthand.get_shorthand_appendable_value(&list) {
Some(appendable_value) => append_declaration_value(dest, appendable_value),
None => return Ok(()),
}
@ -911,9 +911,6 @@ impl PropertyDeclarationBlock {
///
/// https://drafts.csswg.org/cssom/#serialize-a-css-declaration-block
pub fn to_css(&self, dest: &mut CssStringWriter) -> fmt::Result {
use std::iter::Cloned;
use std::slice;
let mut is_first_serialization = true; // trailing serializations should have a prepended space
// Step 1 -> dest = result list
@ -938,7 +935,7 @@ impl PropertyDeclarationBlock {
// properties in a declaration block, and that custom
// properties can't be part of a shorthand, we can just care
// about them here.
append_serialization::<Cloned<slice::Iter<_>>, _>(
append_serialization(
dest,
&property,
AppendableValue::Declaration(declaration),
@ -992,7 +989,7 @@ impl PropertyDeclarationBlock {
// Step 3.4.3:
// Let current longhands be an empty array.
let mut current_longhands = SmallVec::<[_; 10]>::new();
let mut current_longhands = SmallVec::<[&_; 10]>::new();
let mut logical_groups = LogicalGroupSet::new();
let mut saw_one = false;
let mut logical_mismatch = false;
@ -1066,7 +1063,7 @@ impl PropertyDeclarationBlock {
// Let value be the result of invoking serialize a CSS value
// of current longhands.
let appendable_value = match shorthand
.get_shorthand_appendable_value(current_longhands.iter().cloned())
.get_shorthand_appendable_value(&current_longhands)
{
None => continue,
Some(appendable_value) => appendable_value,
@ -1076,15 +1073,9 @@ impl PropertyDeclarationBlock {
// AppendableValue::Css.
let mut v = CssString::new();
let value = match appendable_value {
AppendableValue::Css {
css,
with_variables,
} => {
AppendableValue::Css(css) => {
debug_assert!(!css.is_empty());
AppendableValue::Css {
css,
with_variables,
}
appendable_value
},
other => {
append_declaration_value(&mut v, other)?;
@ -1096,14 +1087,13 @@ impl PropertyDeclarationBlock {
continue;
}
AppendableValue::Css {
AppendableValue::Css({
// Safety: serialization only generates valid utf-8.
#[cfg(feature = "gecko")]
css: unsafe { v.as_str_unchecked() },
unsafe { v.as_str_unchecked() }
#[cfg(feature = "servo")]
css: &v,
with_variables: false,
}
&v
})
},
};
@ -1116,7 +1106,7 @@ impl PropertyDeclarationBlock {
//
// 3.4.10:
// Append serialized declaration to list.
append_serialization::<Cloned<slice::Iter<_>>, _>(
append_serialization(
dest,
&shorthand,
value,
@ -1152,7 +1142,7 @@ impl PropertyDeclarationBlock {
// its important flag set.
//
// Append serialized declaration to list.
append_serialization::<Cloned<slice::Iter<_>>, _>(
append_serialization(
dest,
&property,
AppendableValue::Declaration(declaration),
@ -1172,25 +1162,17 @@ impl PropertyDeclarationBlock {
/// A convenient enum to represent different kinds of stuff that can represent a
/// _value_ in the serialization of a property declaration.
pub enum AppendableValue<'a, I>
where
I: Iterator<Item = &'a PropertyDeclaration>,
{
pub enum AppendableValue<'a, 'b: 'a> {
/// A given declaration, of which we'll serialize just the value.
Declaration(&'a PropertyDeclaration),
/// A set of declarations for a given shorthand.
///
/// FIXME: This needs more docs, where are the shorthands expanded? We print
/// the property name before-hand, don't we?
DeclarationsForShorthand(ShorthandId, I),
DeclarationsForShorthand(ShorthandId, &'a [&'b PropertyDeclaration]),
/// A raw CSS string, coming for example from a property with CSS variables,
/// or when storing a serialized shorthand value before appending directly.
Css {
/// The raw CSS string.
css: &'a str,
/// Whether the original serialization contained variables or not.
with_variables: bool,
},
Css(&'a str),
}
/// Potentially appends whitespace after the first (property: value;) pair.
@ -1207,56 +1189,34 @@ where
}
/// Append a given kind of appendable value to a serialization.
pub fn append_declaration_value<'a, I>(
pub fn append_declaration_value<'a, 'b: 'a>(
dest: &mut CssStringWriter,
appendable_value: AppendableValue<'a, I>,
) -> fmt::Result
where
I: Iterator<Item = &'a PropertyDeclaration>,
{
appendable_value: AppendableValue<'a, 'b>,
) -> fmt::Result {
match appendable_value {
AppendableValue::Css { css, .. } => dest.write_str(css),
AppendableValue::Css(css) => dest.write_str(css),
AppendableValue::Declaration(decl) => decl.to_css(dest),
AppendableValue::DeclarationsForShorthand(shorthand, decls) => {
shorthand.longhands_to_css(decls, &mut CssWriter::new(dest))
shorthand.longhands_to_css(decls, dest)
},
}
}
/// Append a given property and value pair to a serialization.
pub fn append_serialization<'a, I, N>(
pub fn append_serialization<'a, 'b: 'a, N>(
dest: &mut CssStringWriter,
property_name: &N,
appendable_value: AppendableValue<'a, I>,
appendable_value: AppendableValue<'a, 'b>,
importance: Importance,
is_first_serialization: &mut bool,
) -> fmt::Result
where
I: Iterator<Item = &'a PropertyDeclaration>,
N: ToCss,
{
handle_first_serialization(dest, is_first_serialization)?;
property_name.to_css(&mut CssWriter::new(dest))?;
dest.write_char(':')?;
// for normal parsed values, add a space between key: and value
match appendable_value {
AppendableValue::Declaration(decl) => {
if !decl.value_is_unparsed() {
// For normal parsed values, add a space between key: and value.
dest.write_str(" ")?
}
},
AppendableValue::Css { with_variables, .. } => {
if !with_variables {
dest.write_str(" ")?
}
},
// Currently append_serialization is only called with a Css or
// a Declaration AppendableValue.
AppendableValue::DeclarationsForShorthand(..) => unreachable!(),
}
dest.write_str(": ")?;
append_declaration_value(dest, appendable_value)?;

View file

@ -22,7 +22,6 @@ use crate::gecko_bindings::bindings::Gecko_CopyConstruct_${style_struct.gecko_ff
use crate::gecko_bindings::bindings::Gecko_Destroy_${style_struct.gecko_ffi_name};
% endfor
use crate::gecko_bindings::bindings::Gecko_CopyCounterStyle;
use crate::gecko_bindings::bindings::Gecko_CopyFontFamilyFrom;
use crate::gecko_bindings::bindings::Gecko_EnsureImageLayersLength;
use crate::gecko_bindings::bindings::Gecko_nsStyleFont_SetLang;
use crate::gecko_bindings::bindings::Gecko_nsStyleFont_CopyLangFrom;
@ -799,11 +798,8 @@ fn static_assert() {
${impl_simple_type_with_conversion("masonry_auto_flow", "mMasonryAutoFlow")}
</%self:impl_trait>
<% skip_outline_longhands = " ".join("outline-style outline-width".split() +
["-moz-outline-radius-{0}".format(x.replace("_", ""))
for x in CORNERS]) %>
<%self:impl_trait style_struct_name="Outline"
skip_longhands="${skip_outline_longhands}">
skip_longhands="outline-style outline-width">
pub fn set_outline_style(&mut self, v: longhands::outline_style::computed_value::T) {
self.gecko.mOutlineStyle = v;
@ -831,12 +827,6 @@ fn static_assert() {
inherit_from="mOutlineWidth",
round_to_pixels=True) %>
% for corner in CORNERS:
<% impl_corner_style_coord("_moz_outline_radius_%s" % corner.replace("_", ""),
"mOutlineRadius",
corner) %>
% endfor
pub fn outline_has_nonzero_width(&self) -> bool {
self.gecko.mActualOutlineWidth != 0
}
@ -857,52 +847,6 @@ fn static_assert() {
<% impl_font_settings("font_feature_settings", "gfxFontFeature", "FeatureTagValue", "i32", "u32") %>
<% impl_font_settings("font_variation_settings", "gfxFontVariation", "VariationValue", "f32", "f32") %>
pub fn set_font_family(&mut self, v: longhands::font_family::computed_value::T) {
use crate::values::computed::font::GenericFontFamily;
let is_system_font = v.is_system_font;
self.gecko.mFont.systemFont = is_system_font;
self.gecko.mGenericID = if is_system_font {
GenericFontFamily::None
} else {
v.families.single_generic().unwrap_or(GenericFontFamily::None)
};
self.gecko.mFont.fontlist.mFontlist.mBasePtr.set_move(
v.families.shared_font_list().clone()
);
// Fixed-up if needed in Cascade::fixup_font_stuff.
self.gecko.mFont.fontlist.mDefaultFontType = GenericFontFamily::None;
}
pub fn copy_font_family_from(&mut self, other: &Self) {
unsafe { Gecko_CopyFontFamilyFrom(&mut self.gecko.mFont, &other.gecko.mFont); }
self.gecko.mGenericID = other.gecko.mGenericID;
self.gecko.mFont.systemFont = other.gecko.mFont.systemFont;
}
pub fn reset_font_family(&mut self, other: &Self) {
self.copy_font_family_from(other)
}
pub fn clone_font_family(&self) -> longhands::font_family::computed_value::T {
use crate::values::computed::font::{FontFamily, SingleFontFamily, FontFamilyList};
let fontlist = &self.gecko.mFont.fontlist;
let shared_fontlist = unsafe { fontlist.mFontlist.mBasePtr.to_safe() };
let families = if shared_fontlist.mNames.is_empty() {
let default = SingleFontFamily::Generic(fontlist.mDefaultFontType);
FontFamilyList::new(Box::new([default]))
} else {
FontFamilyList::SharedFontList(shared_fontlist)
};
FontFamily {
families,
is_system_font: self.gecko.mFont.systemFont,
}
}
pub fn unzoom_fonts(&mut self, device: &Device) {
use crate::values::generics::NonNegative;
self.gecko.mSize = NonNegative(device.unzoom_text(self.gecko.mSize.0));
@ -1008,26 +952,9 @@ fn static_assert() {
${impl_simple("font_variant_alternates", "mFont.variantAlternates")}
pub fn set_font_size_adjust(&mut self, v: longhands::font_size_adjust::computed_value::T) {
use crate::properties::longhands::font_size_adjust::computed_value::T;
match v {
T::None => self.gecko.mFont.sizeAdjust = -1.0 as f32,
T::Number(n) => self.gecko.mFont.sizeAdjust = n,
}
}
${impl_simple("font_size_adjust", "mFont.sizeAdjust")}
pub fn copy_font_size_adjust_from(&mut self, other: &Self) {
self.gecko.mFont.sizeAdjust = other.gecko.mFont.sizeAdjust;
}
pub fn reset_font_size_adjust(&mut self, other: &Self) {
self.copy_font_size_adjust_from(other)
}
pub fn clone_font_size_adjust(&self) -> longhands::font_size_adjust::computed_value::T {
use crate::properties::longhands::font_size_adjust::computed_value::T;
T::from_gecko_adjust(self.gecko.mFont.sizeAdjust)
}
${impl_simple("font_family", "mFont.family")}
#[allow(non_snake_case)]
pub fn set__x_lang(&mut self, v: longhands::_x_lang::computed_value::T) {

View file

@ -845,10 +845,7 @@
impl<'a> LonghandsToSerialize<'a> {
/// Tries to get a serializable set of longhands given a set of
/// property declarations.
pub fn from_iter<I>(iter: I) -> Result<Self, ()>
where
I: Iterator<Item=&'a PropertyDeclaration>,
{
pub fn from_iter(iter: impl Iterator<Item = &'a PropertyDeclaration>) -> Result<Self, ()> {
// Define all of the expected variables that correspond to the shorthand
% for sub_property in shorthand.sub_properties:
let mut ${sub_property.ident} =
@ -856,8 +853,8 @@
% endfor
// Attempt to assign the incoming declarations to the expected variables
for longhand in iter {
match *longhand {
for declaration in iter {
match *declaration {
% for sub_property in shorthand.sub_properties:
PropertyDeclaration::${sub_property.camel_case}(ref value) => {
${sub_property.ident} = Some(value)
@ -918,6 +915,14 @@
})
}
/// Try to serialize a given shorthand to a string.
pub fn to_css(declarations: &[&PropertyDeclaration], dest: &mut crate::str::CssStringWriter) -> fmt::Result {
match LonghandsToSerialize::from_iter(declarations.iter().cloned()) {
Ok(longhands) => longhands.to_css(&mut CssWriter::new(dest)),
Err(_) => Ok(())
}
}
${caller.body()}
}
% endif

View file

@ -503,7 +503,6 @@ ${helpers.predefined_type(
"BreakWithin",
"computed::BreakWithin::Auto",
engines="gecko",
aliases="page-break-inside",
spec="https://drafts.csswg.org/css-break/#propdef-break-inside",
animation_value_type="discrete",
)}

View file

@ -13,7 +13,6 @@ ${helpers.predefined_type(
engines="gecko servo-2013 servo-2020",
servo_2020_pref="layout.2020.unimplemented",
initial_specified_value="specified::length::NonNegativeLengthOrAuto::auto()",
extra_prefixes="moz:layout.css.prefixes.columns",
animation_value_type="NonNegativeLengthOrAuto",
servo_2013_pref="layout.columns.enabled",
spec="https://drafts.csswg.org/css-multicol/#propdef-column-width",
@ -29,7 +28,6 @@ ${helpers.predefined_type(
initial_specified_value="specified::ColumnCount::auto()",
servo_2013_pref="layout.columns.enabled",
animation_value_type="AnimatedColumnCount",
extra_prefixes="moz:layout.css.prefixes.columns",
spec="https://drafts.csswg.org/css-multicol/#propdef-column-count",
servo_restyle_damage="rebuild_and_reflow",
)}
@ -38,7 +36,6 @@ ${helpers.single_keyword(
"column-fill",
"balance auto",
engines="gecko",
extra_prefixes="moz:layout.css.prefixes.columns",
animation_value_type="discrete",
gecko_enum_prefix="StyleColumnFill",
spec="https://drafts.csswg.org/css-multicol/#propdef-column-fill",
@ -53,7 +50,6 @@ ${helpers.predefined_type(
computed_type="crate::values::computed::NonNegativeLength",
spec="https://drafts.csswg.org/css-multicol/#propdef-column-rule-width",
animation_value_type="NonNegativeLength",
extra_prefixes="moz:layout.css.prefixes.columns",
)}
// https://drafts.csswg.org/css-multicol-1/#crc
@ -64,7 +60,6 @@ ${helpers.predefined_type(
engines="gecko",
initial_specified_value="specified::Color::currentcolor()",
animation_value_type="AnimatedColor",
extra_prefixes="moz:layout.css.prefixes.columns",
ignored_when_colors_disabled=True,
spec="https://drafts.csswg.org/css-multicol/#propdef-column-rule-color",
)}
@ -76,7 +71,6 @@ ${helpers.single_keyword(
animation_value_type="discrete",
gecko_enum_prefix="StyleColumnSpan",
spec="https://drafts.csswg.org/css-multicol/#propdef-column-span",
extra_prefixes="moz:layout.css.prefixes.columns",
)}
${helpers.predefined_type(
@ -85,7 +79,6 @@ ${helpers.predefined_type(
"computed::BorderStyle::None",
engines="gecko",
initial_specified_value="specified::BorderStyle::None",
extra_prefixes="moz:layout.css.prefixes.columns",
animation_value_type="discrete",
spec="https://drafts.csswg.org/css-multicol/#propdef-column-rule-style",
)}

View file

@ -213,6 +213,7 @@ ${helpers.predefined_type(
initial_value="computed::XLang::get_initial_value()",
animation_value_type="none",
enabled_in="",
has_effect_on_gecko_scrollbars=False,
spec="Internal (not web-exposed)",
)}
@ -224,6 +225,7 @@ ${helpers.predefined_type(
animation_value_type="none",
gecko_ffi_name="mScriptSizeMultiplier",
enabled_in="",
has_effect_on_gecko_scrollbars=False,
spec="Internal (not web-exposed)",
)}
@ -263,6 +265,7 @@ ${helpers.single_keyword(
spec="Internal (not web-exposed)",
animation_value_type="none",
enabled_in="",
has_effect_on_gecko_scrollbars=False,
needs_conversion=True,
)}
@ -273,6 +276,7 @@ ${helpers.predefined_type(
engines="gecko",
animation_value_type="none",
enabled_in="",
has_effect_on_gecko_scrollbars=False,
gecko_ffi_name="mScriptMinSize",
spec="Internal (not web-exposed)",
)}
@ -284,6 +288,7 @@ ${helpers.predefined_type(
engines="gecko",
animation_value_type="none",
enabled_in="",
has_effect_on_gecko_scrollbars=False,
spec="Internal (not web-exposed)",
)}
@ -309,7 +314,6 @@ pub mod system_font {
//! variable reference. We may want to improve this behavior at some
//! point. See also https://github.com/w3c/csswg-drafts/issues/1586.
use crate::values::computed::font::GenericFontFamily;
use crate::properties::longhands;
use std::hash::{Hash, Hasher};
use crate::values::computed::{ToComputedValue, Context};
@ -351,7 +355,7 @@ pub mod system_font {
use std::mem;
use crate::values::computed::Percentage;
use crate::values::specified::font::KeywordInfo;
use crate::values::computed::font::{FontFamily, FontSize, FontStretch, FontStyle, FontFamilyList};
use crate::values::computed::font::{FontSize, FontStretch, FontStyle};
use crate::values::generics::NonNegative;
let mut system = mem::MaybeUninit::<nsFont>::uninit();
@ -370,12 +374,7 @@ pub mod system_font {
})));
let font_style = FontStyle::from_gecko(system.style);
let ret = ComputedSystemFont {
font_family: FontFamily {
families: FontFamilyList::SharedFontList(
unsafe { system.fontlist.mFontlist.mBasePtr.to_safe() }
),
is_system_font: true,
},
font_family: system.family.clone(),
font_size: FontSize {
size: NonNegative(cx.maybe_zoom_text(system.size.0)),
keyword_info: KeywordInfo::none()
@ -383,8 +382,7 @@ pub mod system_font {
font_weight,
font_stretch,
font_style,
font_size_adjust: longhands::font_size_adjust::computed_value
::T::from_gecko_adjust(system.sizeAdjust),
font_size_adjust: system.sizeAdjust,
% for kwprop in kw_font_props:
${kwprop}: longhands::${kwprop}::computed_value::T::from_gecko_keyword(
system.${to_camel_case_lower(kwprop.replace('font_', ''))}
@ -399,7 +397,6 @@ pub mod system_font {
font_variation_settings: longhands::font_variation_settings::get_initial_value(),
font_variant_alternates: longhands::font_variant_alternates::get_initial_value(),
system_font: *self,
default_font_type: system.fontlist.mDefaultFontType,
};
unsafe { bindings::Gecko_nsFont_Destroy(system); }
ret
@ -431,7 +428,6 @@ pub mod system_font {
pub ${name}: longhands::${name}::computed_value::T,
% endfor
pub system_font: SystemFont,
pub default_font_type: GenericFontFamily,
}
}

View file

@ -209,6 +209,9 @@ ${helpers.predefined_type(
"MozContextProperties",
"computed::MozContextProperties::default()",
engines="gecko",
enabled_in="chrome",
gecko_pref="svg.context-properties.content.enabled",
has_effect_on_gecko_scrollbars=False,
animation_value_type="none",
spec="Nonstandard (https://developer.mozilla.org/en-US/docs/Web/CSS/-moz-context-properties)",
)}

View file

@ -238,12 +238,13 @@ ${helpers.predefined_type(
)}
${helpers.predefined_type(
"-moz-tab-size",
"tab-size",
"NonNegativeLengthOrNumber",
"generics::length::LengthOrNumber::Number(From::from(8.0))",
engines="gecko",
animation_value_type="LengthOrNumber",
spec="https://drafts.csswg.org/css-text-3/#tab-size-property",
aliases="-moz-tab-size",
)}
${helpers.predefined_type(
@ -336,6 +337,9 @@ ${helpers.predefined_type(
"text::MozControlCharacterVisibility",
"Default::default()",
engines="gecko",
enabled_in="chrome",
gecko_pref="layout.css.moz-control-character-visibility.enabled",
has_effect_on_gecko_scrollbars=False,
animation_value_type="none",
spec="Nonstandard"
)}

View file

@ -95,6 +95,17 @@ ${helpers.predefined_type(
has_effect_on_gecko_scrollbars=False,
)}
${helpers.predefined_type(
"color-scheme",
"ColorScheme",
"specified::color::ColorScheme::normal()",
engines="gecko",
spec="https://drafts.csswg.org/css-color-adjust/#color-scheme-prop",
gecko_pref="layout.css.color-scheme.enabled",
animation_value_type="discrete",
has_effect_on_gecko_scrollbars=False,
)}
${helpers.predefined_type(
"scrollbar-color",
"ui::ScrollbarColor",

View file

@ -42,20 +42,6 @@ ${helpers.predefined_type(
spec="https://drafts.csswg.org/css-ui/#propdef-outline-width",
)}
// The -moz-outline-radius-* properties are non-standard and not on a standards track.
% for corner in ["topleft", "topright", "bottomright", "bottomleft"]:
${helpers.predefined_type(
"-moz-outline-radius-" + corner,
"BorderCornerRadius",
"computed::BorderCornerRadius::zero()",
engines="gecko",
boxed=True,
animation_value_type="BorderCornerRadius",
gecko_pref="layout.css.moz-outline-radius.enabled",
spec="Nonstandard (https://developer.mozilla.org/en-US/docs/Web/CSS/-moz-outline-radius)",
)}
% endfor
${helpers.predefined_type(
"outline-offset",
"Length",

View file

@ -434,7 +434,6 @@ ${helpers.predefined_type(
"computed::length::NonNegativeLengthPercentageOrNormal::normal()",
engines="gecko servo-2013",
aliases="grid-column-gap" if engine == "gecko" else "",
extra_prefixes="moz:layout.css.prefixes.columns",
servo_2013_pref="layout.columns.enabled",
spec="https://drafts.csswg.org/css-align-3/#propdef-column-gap",
animation_value_type="NonNegativeLengthPercentageOrNormal",

View file

@ -246,3 +246,13 @@ ${helpers.predefined_type(
animation_value_type="LengthPercentage",
spec="https://svgwg.org/svg2-draft/geometry.html#R",
)}
${helpers.predefined_type(
"d",
"DProperty",
"specified::DProperty::none()",
engines="gecko",
animation_value_type="ComputedValue",
gecko_pref="layout.css.d-property.enabled",
spec="https://svgwg.org/svg2-draft/paths.html#TheDProperty",
)}

View file

@ -232,7 +232,7 @@ pub mod shorthands {
// which don't exist in `LonghandId`.
<%
extra = [
extra_variants = [
{
"name": "CSSWideKeyword",
"type": "WideKeywordDeclaration",
@ -252,7 +252,7 @@ pub mod shorthands {
"copy": False,
},
]
for v in extra:
for v in extra_variants:
variants.append(v)
groups[v["type"]] = [v]
%>
@ -394,6 +394,17 @@ impl MallocSizeOf for PropertyDeclaration {
impl PropertyDeclaration {
/// Returns whether this is a variant of the Longhand(Value) type, rather
/// than one of the special variants in extra_variants.
fn is_longhand_value(&self) -> bool {
match *self {
% for v in extra_variants:
PropertyDeclaration::${v["name"]}(..) => false,
% endfor
_ => true,
}
}
/// Like the method on ToCss, but without the type parameter to avoid
/// accidentally monomorphizing this large function multiple times for
/// different writers.
@ -409,6 +420,24 @@ impl PropertyDeclaration {
% endfor
}
}
/// Returns the color value of a given property, for high-contrast-mode
/// tweaks.
pub(crate) fn color_value(&self) -> Option<<&crate::values::specified::Color> {
${static_longhand_id_set("COLOR_PROPERTIES", lambda p: p.predefined_type == "Color")}
<%
# sanity check
assert data.longhands_by_name["background-color"].predefined_type == "Color"
color_specified_type = data.longhands_by_name["background-color"].specified_type()
%>
let id = self.id().as_longhand()?;
if !COLOR_PROPERTIES.contains(id) || !self.is_longhand_value() {
return None;
}
let repr = self as *const _ as *const PropertyDeclarationVariantRepr<${color_specified_type}>;
Some(unsafe { &(*repr).value })
}
}
/// A module with all the code related to animated properties.
@ -1366,6 +1395,9 @@ impl LonghandId {
// font-size depends on math-depth's computed value.
LonghandId::MathDepth |
// color-scheme affects how system colors resolve.
LonghandId::ColorScheme |
% endif
// Needed to compute the first available font, in order to
@ -1471,74 +1503,66 @@ impl ShorthandId {
///
/// Returns an error if writing to the stream fails, or if the declarations
/// do not map to a shorthand.
pub fn longhands_to_css<'a, W, I>(
pub fn longhands_to_css(
&self,
declarations: I,
dest: &mut CssWriter<W>,
) -> fmt::Result
where
W: Write,
I: Iterator<Item=&'a PropertyDeclaration>,
{
match *self {
ShorthandId::All => {
// No need to try to serialize the declarations as the 'all'
// shorthand, since it only accepts CSS-wide keywords (and
// variable references), which will be handled in
// get_shorthand_appendable_value.
Err(fmt::Error)
}
% for property in data.shorthands_except_all():
ShorthandId::${property.camel_case} => {
match shorthands::${property.ident}::LonghandsToSerialize::from_iter(declarations) {
Ok(longhands) => longhands.to_css(dest),
Err(_) => Err(fmt::Error)
}
},
% endfor
declarations: &[&PropertyDeclaration],
dest: &mut CssStringWriter,
) -> fmt::Result {
type LonghandsToCssFn = for<'a, 'b> fn(&'a [&'b PropertyDeclaration], &mut CssStringWriter) -> fmt::Result;
fn all_to_css(_: &[&PropertyDeclaration], _: &mut CssStringWriter) -> fmt::Result {
// No need to try to serialize the declarations as the 'all'
// shorthand, since it only accepts CSS-wide keywords (and variable
// references), which will be handled in
// get_shorthand_appendable_value.
Ok(())
}
static LONGHANDS_TO_CSS: [LonghandsToCssFn; ${len(data.shorthands)}] = [
% for shorthand in data.shorthands:
% if shorthand.ident == "all":
all_to_css,
% else:
shorthands::${shorthand.ident}::to_css,
% endif
% endfor
];
LONGHANDS_TO_CSS[*self as usize](declarations, dest)
}
/// Finds and returns an appendable value for the given declarations.
///
/// Returns the optional appendable value.
pub fn get_shorthand_appendable_value<'a, I>(
pub fn get_shorthand_appendable_value<'a, 'b: 'a>(
self,
declarations: I,
) -> Option<AppendableValue<'a, I::IntoIter>>
where
I: IntoIterator<Item=&'a PropertyDeclaration>,
I::IntoIter: Clone,
{
let declarations = declarations.into_iter();
// Only cloning iterators (a few pointers each) not declarations.
let mut declarations2 = declarations.clone();
let mut declarations3 = declarations.clone();
let first_declaration = declarations2.next()?;
declarations: &'a [&'b PropertyDeclaration],
) -> Option<AppendableValue<'a, 'b>> {
let first_declaration = declarations.get(0)?;
let rest = || declarations.iter().skip(1);
// https://drafts.csswg.org/css-variables/#variables-in-shorthands
if let Some(css) = first_declaration.with_variables_from_shorthand(self) {
if declarations2.all(|d| d.with_variables_from_shorthand(self) == Some(css)) {
return Some(AppendableValue::Css { css, with_variables: true });
if rest().all(|d| d.with_variables_from_shorthand(self) == Some(css)) {
return Some(AppendableValue::Css(css));
}
return None;
}
// Check whether they are all the same CSS-wide keyword.
if let Some(keyword) = first_declaration.get_css_wide_keyword() {
if declarations2.all(|d| d.get_css_wide_keyword() == Some(keyword)) {
return Some(AppendableValue::Css {
css: keyword.to_str(),
with_variables: false,
});
if rest().all(|d| d.get_css_wide_keyword() == Some(keyword)) {
return Some(AppendableValue::Css(keyword.to_str()))
}
return None;
}
if self == ShorthandId::All {
// 'all' only supports variables and CSS wide keywords.
return None;
}
// Check whether all declarations can be serialized as part of shorthand.
if declarations3.all(|d| d.may_serialize_as_part_of_shorthand()) {
if declarations.iter().all(|d| d.may_serialize_as_part_of_shorthand()) {
return Some(AppendableValue::DeclarationsForShorthand(self, declarations));
}
@ -1596,23 +1620,20 @@ impl ShorthandId {
input: &mut Parser<'i, 't>,
) -> Result<(), ParseError<'i>>;
fn unreachable<'i, 't>(
fn parse_all<'i, 't>(
_: &mut SourcePropertyDeclaration,
_: &ParserContext,
_: &mut Parser<'i, 't>
input: &mut Parser<'i, 't>
) -> Result<(), ParseError<'i>> {
unreachable!()
// 'all' accepts no value other than CSS-wide keywords
Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
}
// 'all' accepts no value other than CSS-wide keywords
if *self == ShorthandId::All {
return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
}
static PARSE_INTO: [ParseIntoFn; ${len(data.shorthands)}] = [
% for shorthand in data.shorthands:
% if shorthand.ident == "all":
unreachable,
parse_all,
% else:
shorthands::${shorthand.ident}::parse_into,
% endif
@ -1720,7 +1741,8 @@ impl UnparsedValue {
let mut input = ParserInput::new(&css);
let mut input = Parser::new(&mut input);
input.skip_whitespace(); // Unnecessary for correctness, but may help try() rewind less.
input.skip_whitespace();
if let Ok(keyword) = input.try_parse(CSSWideKeyword::parse) {
return Cow::Owned(PropertyDeclaration::css_wide_keyword(longhand_id, keyword));
}
@ -2436,12 +2458,11 @@ impl PropertyDeclaration {
debug_assert!(id.allowed_in(context), "{:?}", id);
let non_custom_id = id.non_custom_id();
input.skip_whitespace();
let start = input.state();
match id {
PropertyId::Custom(property_name) => {
// FIXME: fully implement https://github.com/w3c/csswg-drafts/issues/774
// before adding skip_whitespace here.
// This probably affects some test results.
let value = match input.try_parse(CSSWideKeyword::parse) {
Ok(keyword) => CustomDeclarationValue::CSSWideKeyword(keyword),
Err(()) => CustomDeclarationValue::Value(
@ -2456,7 +2477,6 @@ impl PropertyDeclaration {
}
PropertyId::LonghandAlias(id, _) |
PropertyId::Longhand(id) => {
input.skip_whitespace(); // Unnecessary for correctness, but may help try() rewind less.
input.try_parse(CSSWideKeyword::parse).map(|keyword| {
PropertyDeclaration::css_wide_keyword(id, keyword)
}).or_else(|()| {
@ -2486,7 +2506,6 @@ impl PropertyDeclaration {
}
PropertyId::ShorthandAlias(id, _) |
PropertyId::Shorthand(id) => {
input.skip_whitespace(); // Unnecessary for correctness, but may help try() rewind less.
if let Ok(keyword) = input.try_parse(CSSWideKeyword::parse) {
if id == ShorthandId::All {
declarations.all_shorthand = AllShorthand::CSSWideKeyword(keyword)

View file

@ -316,15 +316,15 @@ ${helpers.two_properties_shorthand(
name="page-break-before"
flags="SHORTHAND_IN_GETCS IS_LEGACY_SHORTHAND"
sub_properties="break-before"
spec="https://drafts.csswg.org/css2/page.html#propdef-page-break-before"
spec="https://drafts.csswg.org/css-break-3/#page-break-properties"
>
pub fn parse_value<'i>(
_: &ParserContext,
context: &ParserContext,
input: &mut Parser<'i, '_>,
) -> Result<Longhands, ParseError<'i>> {
use crate::values::specified::box_::BreakBetween;
Ok(expanded! {
break_before: BreakBetween::parse_legacy(input)?,
break_before: BreakBetween::parse_legacy(context, input)?,
})
}
@ -340,15 +340,15 @@ ${helpers.two_properties_shorthand(
name="page-break-after"
flags="SHORTHAND_IN_GETCS IS_LEGACY_SHORTHAND"
sub_properties="break-after"
spec="https://drafts.csswg.org/css2/page.html#propdef-page-break-after"
spec="https://drafts.csswg.org/css-break-3/#page-break-properties"
>
pub fn parse_value<'i>(
_: &ParserContext,
context: &ParserContext,
input: &mut Parser<'i, '_>,
) -> Result<Longhands, ParseError<'i>> {
use crate::values::specified::box_::BreakBetween;
Ok(expanded! {
break_after: BreakBetween::parse_legacy(input)?,
break_after: BreakBetween::parse_legacy(context, input)?,
})
}
@ -359,6 +359,30 @@ ${helpers.two_properties_shorthand(
}
</%helpers:shorthand>
<%helpers:shorthand
engines="gecko"
name="page-break-inside"
flags="SHORTHAND_IN_GETCS IS_LEGACY_SHORTHAND"
sub_properties="break-inside"
spec="https://drafts.csswg.org/css-break-3/#page-break-properties"
>
pub fn parse_value<'i>(
context: &ParserContext,
input: &mut Parser<'i, '_>,
) -> Result<Longhands, ParseError<'i>> {
use crate::values::specified::box_::BreakWithin;
Ok(expanded! {
break_inside: BreakWithin::parse_legacy(context, input)?,
})
}
impl<'a> ToCss for LonghandsToSerialize<'a> {
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: fmt::Write {
self.break_inside.to_css_legacy(dest)
}
}
</%helpers:shorthand>
<%helpers:shorthand name="offset"
engines="gecko"
sub_properties="offset-path offset-distance offset-rotate offset-anchor"

View file

@ -9,7 +9,6 @@
sub_properties="column-width column-count"
servo_2013_pref="layout.columns.enabled",
derive_serialize="True"
extra_prefixes="moz:layout.css.prefixes.columns"
spec="https://drafts.csswg.org/css-multicol/#propdef-columns">
use crate::properties::longhands::{column_count, column_width};
@ -60,7 +59,6 @@
<%helpers:shorthand
name="column-rule"
engines="gecko"
extra_prefixes="moz:layout.css.prefixes.columns"
sub_properties="column-rule-width column-rule-style column-rule-color"
derive_serialize="True"
spec="https://drafts.csswg.org/css-multicol/#propdef-column-rule"

View file

@ -137,7 +137,7 @@
return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
}
let family = FontFamily::parse_specified(input)?;
let family = FontFamily::parse(context, input)?;
Ok(expanded! {
% for name in "style weight stretch variant_caps".split():
font_${name}: unwrap_or_initial!(font_${name}, ${name}),

View file

@ -78,50 +78,3 @@
}
}
</%helpers:shorthand>
// The -moz-outline-radius shorthand is non-standard and not on a standards track.
<%helpers:shorthand
name="-moz-outline-radius"
engines="gecko"
gecko_pref="layout.css.moz-outline-radius.enabled"
sub_properties="${' '.join(
'-moz-outline-radius-%s' % corner
for corner in ['topleft', 'topright', 'bottomright', 'bottomleft']
)}"
spec="Nonstandard (https://developer.mozilla.org/en-US/docs/Web/CSS/-moz-outline-radius)"
>
use crate::values::generics::rect::Rect;
use crate::values::specified::border::BorderRadius;
use crate::parser::Parse;
pub fn parse_value<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
) -> Result<Longhands, ParseError<'i>> {
let radii = BorderRadius::parse(context, input)?;
Ok(expanded! {
_moz_outline_radius_topleft: radii.top_left,
_moz_outline_radius_topright: radii.top_right,
_moz_outline_radius_bottomright: radii.bottom_right,
_moz_outline_radius_bottomleft: radii.bottom_left,
})
}
impl<'a> ToCss for LonghandsToSerialize<'a> {
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: fmt::Write {
use crate::values::generics::border::BorderCornerRadius;
let LonghandsToSerialize {
_moz_outline_radius_topleft: &BorderCornerRadius(ref tl),
_moz_outline_radius_topright: &BorderCornerRadius(ref tr),
_moz_outline_radius_bottomright: &BorderCornerRadius(ref br),
_moz_outline_radius_bottomleft: &BorderCornerRadius(ref bl),
} = *self;
let widths = Rect::new(tl.width(), tr.width(), br.width(), bl.width());
let heights = Rect::new(tl.height(), tr.height(), br.height(), bl.height());
BorderRadius::serialize_rects(widths, heights, dest)
}
}
</%helpers:shorthand>

View file

@ -232,9 +232,6 @@ impl<'a, 'b: 'a> StyleAdjuster<'a, 'b> {
where
E: TElement,
{
#[cfg(any(feature = "servo-layout-2013", feature = "gecko"))]
use crate::computed_values::list_style_position::T as ListStylePosition;
let mut blockify = false;
macro_rules! blockify_if {
($if_what:expr) => {
@ -254,16 +251,6 @@ impl<'a, 'b: 'a> StyleAdjuster<'a, 'b> {
blockify_if!(self.style.is_floating());
blockify_if!(self.style.is_absolutely_positioned());
#[cfg(any(feature = "servo-layout-2013", feature = "gecko"))]
blockify_if!(
self.style.pseudo.map_or(false, |p| p.is_marker()) &&
self.style.get_parent_list().clone_list_style_position() ==
ListStylePosition::Outside &&
!layout_parent_style
.get_box()
.clone_display()
.is_inline_flow()
);
if !blockify {
return;
@ -818,6 +805,46 @@ impl<'a, 'b: 'a> StyleAdjuster<'a, 'b> {
}
}
/// A legacy ::marker (i.e. no 'content') without an author-specified 'font-family'
/// and 'list-style-type:disc|circle|square|disclosure-closed|disclosure-open'
/// is assigned 'font-family:-moz-bullet-font'. (This is for <ul><li> etc.)
/// We don't want synthesized italic/bold for this font, so turn that off too.
/// Likewise for 'letter/word-spacing' -- unless the author specified it then reset
/// them to their initial value because traditionally we never added such spacing
/// between a legacy bullet and the list item's content, so we keep that behavior
/// for web-compat reasons.
/// We intentionally don't check 'list-style-image' below since we want it to use
/// the same font as its fallback ('list-style-type') in case it fails to load.
#[cfg(feature = "gecko")]
fn adjust_for_marker_pseudo(&mut self) {
use crate::values::computed::font::{FontFamily, FontSynthesis};
use crate::values::computed::text::{LetterSpacing, WordSpacing};
use crate::values::computed::counters::{Content};
let is_legacy_marker = self.style.pseudo.map_or(false, |p| p.is_marker()) &&
self.style.get_list().clone_list_style_type().is_bullet() &&
self.style.get_counters().clone_content() == Content::Normal;
if !is_legacy_marker {
return;
}
if !self.style.flags.get().contains(ComputedValueFlags::HAS_AUTHOR_SPECIFIED_FONT_FAMILY) {
self.style.mutate_font().set_font_family(FontFamily::moz_bullet().clone());
// FIXME(mats): We can remove this if support for font-synthesis is added to @font-face rules.
// Then we can add it to the @font-face rule in html.css instead.
// https://github.com/w3c/csswg-drafts/issues/6081
if !self.style.flags.get().contains(ComputedValueFlags::HAS_AUTHOR_SPECIFIED_FONT_SYNTHESIS) {
self.style.mutate_font().set_font_synthesis(FontSynthesis::none());
}
}
if !self.style.flags.get().contains(ComputedValueFlags::HAS_AUTHOR_SPECIFIED_LETTER_SPACING) {
self.style.mutate_inherited_text().set_letter_spacing(LetterSpacing::normal());
}
if !self.style.flags.get().contains(ComputedValueFlags::HAS_AUTHOR_SPECIFIED_WORD_SPACING) {
self.style.mutate_inherited_text().set_word_spacing(WordSpacing::normal());
}
}
/// Adjusts the style to account for various fixups that don't fit naturally
/// into the cascade.
///
@ -883,6 +910,7 @@ impl<'a, 'b: 'a> StyleAdjuster<'a, 'b> {
{
self.adjust_for_appearance(element);
self.adjust_for_inert();
self.adjust_for_marker_pseudo();
}
self.set_bits();
}

View file

@ -56,7 +56,13 @@ impl ImportSheet {
/// exists.
pub fn as_sheet(&self) -> Option<&crate::gecko::data::GeckoStyleSheet> {
match *self {
ImportSheet::Sheet(ref s) => Some(s),
ImportSheet::Sheet(ref s) => {
debug_assert!(!s.hack_is_null());
if s.hack_is_null() {
return None;
}
Some(s)
},
ImportSheet::Pending(_) => None,
}
}

View file

@ -65,6 +65,10 @@ pub struct StylesheetContents {
pub source_map_url: RwLock<Option<String>>,
/// This stylesheet's source URL.
pub source_url: RwLock<Option<String>>,
/// We don't want to allow construction outside of this file, to guarantee
/// that all contents are created with Arc<>.
_forbid_construction: (),
}
impl StylesheetContents {
@ -82,7 +86,7 @@ impl StylesheetContents {
use_counters: Option<&UseCounters>,
allow_import_rules: AllowImportRules,
sanitization_data: Option<&mut SanitizationData>,
) -> Self {
) -> Arc<Self> {
let namespaces = RwLock::new(Namespaces::default());
let (rules, source_map_url, source_url) = Stylesheet::parse_rules(
css,
@ -99,7 +103,7 @@ impl StylesheetContents {
sanitization_data,
);
Self {
Arc::new(Self {
rules: CssRules::new(rules, &shared_lock),
origin,
url_data: RwLock::new(url_data),
@ -107,7 +111,8 @@ impl StylesheetContents {
quirks_mode,
source_map_url: RwLock::new(source_map_url),
source_url: RwLock::new(source_url),
}
_forbid_construction: (),
})
}
/// Creates a new StylesheetContents with the specified pre-parsed rules,
@ -126,9 +131,9 @@ impl StylesheetContents {
origin: Origin,
url_data: UrlExtraData,
quirks_mode: QuirksMode,
) -> Self {
) -> Arc<Self> {
debug_assert!(rules.is_static());
Self {
Arc::new(Self {
rules,
origin,
url_data: RwLock::new(url_data),
@ -136,7 +141,8 @@ impl StylesheetContents {
quirks_mode,
source_map_url: RwLock::new(None),
source_url: RwLock::new(None),
}
_forbid_construction: (),
})
}
/// Returns a reference to the list of rules.
@ -178,6 +184,7 @@ impl DeepCloneWithLock for StylesheetContents {
namespaces: RwLock::new((*self.namespaces.read()).clone()),
source_map_url: RwLock::new((*self.source_map_url.read()).clone()),
source_url: RwLock::new((*self.source_url.read()).clone()),
_forbid_construction: (),
}
}
}
@ -186,7 +193,7 @@ impl DeepCloneWithLock for StylesheetContents {
#[derive(Debug)]
pub struct Stylesheet {
/// The contents of this stylesheet.
pub contents: StylesheetContents,
pub contents: Arc<StylesheetContents>,
/// The lock used for objects inside this stylesheet
pub shared_lock: SharedRwLock,
/// List of media associated with the Stylesheet.
@ -587,9 +594,9 @@ impl Clone for Stylesheet {
// Make a deep clone of the media, using the new lock.
let media = self.media.read_with(&guard).clone();
let media = Arc::new(lock.wrap(media));
let contents = self
let contents = Arc::new(self
.contents
.deep_clone_with_lock(&lock, &guard, &DeepCloneParams);
.deep_clone_with_lock(&lock, &guard, &DeepCloneParams));
Stylesheet {
contents,

View file

@ -11,7 +11,7 @@ use crate::element_state::{DocumentState, ElementState};
#[cfg(feature = "gecko")]
use crate::gecko_bindings::structs::{ServoStyleSetSizes, StyleRuleInclusion};
use crate::invalidation::element::invalidation_map::InvalidationMap;
use crate::invalidation::media_queries::EffectiveMediaQueryResults;
use crate::invalidation::media_queries::{EffectiveMediaQueryResults, MediaListKey, ToMediaListKey};
use crate::invalidation::stylesheets::RuleChangeKind;
use crate::media_queries::Device;
use crate::properties::{self, CascadeMode, ComputedValues};
@ -26,8 +26,7 @@ use crate::stylesheet_set::{DataValidity, DocumentStylesheetSet, SheetRebuildKin
use crate::stylesheet_set::{DocumentStylesheetFlusher, SheetCollectionFlusher};
use crate::stylesheets::keyframes_rule::KeyframesAnimation;
use crate::stylesheets::viewport_rule::{self, MaybeNew, ViewportRule};
use crate::stylesheets::StyleRule;
use crate::stylesheets::StylesheetInDocument;
use crate::stylesheets::{StyleRule, StylesheetInDocument, StylesheetContents};
#[cfg(feature = "gecko")]
use crate::stylesheets::{CounterStyleRule, FontFaceRule, FontFeatureValuesRule, PageRule};
use crate::stylesheets::{CssRule, Origin, OriginSet, PerOrigin, PerOriginIter};
@ -50,7 +49,9 @@ use smallbitvec::SmallBitVec;
use smallvec::SmallVec;
use std::sync::Mutex;
use std::{mem, ops};
use std::hash::{Hash, Hasher};
use style_traits::viewport::ViewportConstraints;
use fxhash::FxHashMap;
/// The type of the stylesheets that the stylist contains.
#[cfg(feature = "servo")]
@ -60,6 +61,37 @@ pub type StylistSheet = crate::stylesheets::DocumentStyleSheet;
#[cfg(feature = "gecko")]
pub type StylistSheet = crate::gecko::data::GeckoStyleSheet;
#[derive(Debug, Clone)]
struct StylesheetContentsPtr(Arc<StylesheetContents>);
impl PartialEq for StylesheetContentsPtr {
#[inline]
fn eq(&self, other: &Self) -> bool {
Arc::ptr_eq(&self.0, &other.0)
}
}
impl Eq for StylesheetContentsPtr {}
impl Hash for StylesheetContentsPtr {
fn hash<H: Hasher>(&self, state: &mut H) {
let contents: &StylesheetContents = &*self.0;
(contents as *const StylesheetContents).hash(state)
}
}
type StyleSheetContentList = Vec<StylesheetContentsPtr>;
/// A key in the cascade data cache.
#[derive(Debug, Hash, Default, PartialEq, Eq)]
struct CascadeDataCacheKey {
media_query_results: Vec<MediaListKey>,
contents: StyleSheetContentList,
}
unsafe impl Send for CascadeDataCacheKey {}
unsafe impl Sync for CascadeDataCacheKey {}
trait CascadeDataCacheEntry : Sized {
/// Returns a reference to the cascade data.
fn cascade_data(&self) -> &CascadeData;
@ -80,7 +112,7 @@ trait CascadeDataCacheEntry : Sized {
}
struct CascadeDataCache<Entry> {
entries: Vec<Arc<Entry>>,
entries: FxHashMap<CascadeDataCacheKey, Arc<Entry>>,
}
impl<Entry> CascadeDataCache<Entry>
@ -88,7 +120,7 @@ where
Entry: CascadeDataCacheEntry,
{
fn new() -> Self {
Self { entries: vec![] }
Self { entries: Default::default() }
}
fn len(&self) -> usize {
@ -110,51 +142,66 @@ where
where
S: StylesheetInDocument + PartialEq + 'static,
{
use std::collections::hash_map::Entry as HashMapEntry;
debug!("StyleSheetCache::lookup({})", self.len());
if !collection.dirty() {
return Ok(None);
}
let mut key = EffectiveMediaQueryResults::new();
let mut key = CascadeDataCacheKey::default();
for sheet in collection.sheets() {
CascadeData::collect_applicable_media_query_results_into(device, sheet, guard, &mut key)
CascadeData::collect_applicable_media_query_results_into(
device,
sheet,
guard,
&mut key.media_query_results,
&mut key.contents,
)
}
for entry in &self.entries {
if std::ptr::eq(&**entry, old_entry) {
let new_entry;
match self.entries.entry(key) {
HashMapEntry::Vacant(e) => {
debug!("> Picking the slow path (not in the cache)");
new_entry = Entry::rebuild(
device,
quirks_mode,
collection,
guard,
old_entry,
)?;
e.insert(new_entry.clone());
}
HashMapEntry::Occupied(mut e) => {
// Avoid reusing our old entry (this can happen if we get
// invalidated due to CSSOM mutations and our old stylesheet
// contents were already unique, for example). This old entry
// will be pruned from the cache with take_unused() afterwards.
continue;
}
if entry.cascade_data().effective_media_query_results != key {
continue;
}
if log_enabled!(log::Level::Debug) {
debug!("cache hit for:");
for sheet in collection.sheets() {
debug!(" > {:?}", sheet);
// contents were already unique, for example).
if !std::ptr::eq(&**e.get(), old_entry) {
if log_enabled!(log::Level::Debug) {
debug!("cache hit for:");
for sheet in collection.sheets() {
debug!(" > {:?}", sheet);
}
}
// The line below ensures the "committed" bit is updated
// properly.
collection.each(|_, _| true);
return Ok(Some(e.get().clone()));
}
debug!("> Picking the slow path due to same entry as old");
new_entry = Entry::rebuild(
device,
quirks_mode,
collection,
guard,
old_entry,
)?;
e.insert(new_entry.clone());
}
// The line below ensures the "committed" bit is updated properly
// below.
collection.each(|_, _| true);
return Ok(Some(entry.clone()));
}
debug!("> Picking the slow path");
let new_entry = Entry::rebuild(
device,
quirks_mode,
collection,
guard,
old_entry,
)?;
self.entries.push(new_entry.clone());
Ok(Some(new_entry))
}
@ -167,25 +214,27 @@ where
/// cache to not deadlock.
fn take_unused(&mut self) -> SmallVec<[Arc<Entry>; 3]> {
let mut unused = SmallVec::new();
for i in (0..self.entries.len()).rev() {
self.entries.retain(|_key, value| {
// is_unique() returns false for static references, but we never
// have static references to UserAgentCascadeDatas. If we did, it
// may not make sense to put them in the cache in the first place.
if self.entries[i].is_unique() {
unused.push(self.entries.remove(i));
if !value.is_unique() {
return true;
}
}
unused.push(value.clone());
false
});
unused
}
fn take_all(&mut self) -> Vec<Arc<Entry>> {
mem::replace(&mut self.entries, Vec::new())
fn take_all(&mut self) -> FxHashMap<CascadeDataCacheKey, Arc<Entry>> {
mem::take(&mut self.entries)
}
#[cfg(feature = "gecko")]
fn add_size_of(&self, ops: &mut MallocSizeOfOps, sizes: &mut ServoStyleSetSizes) {
sizes.mOther += self.entries.shallow_size_of(ops);
for arc in self.entries.iter() {
for (_key, arc) in self.entries.iter() {
// These are primary Arc references that can be measured
// unconditionally.
sizes.mOther += arc.unconditional_shallow_size_of(ops);
@ -2033,7 +2082,8 @@ impl CascadeData {
device: &Device,
stylesheet: &S,
guard: &SharedRwLockReadGuard,
results: &mut EffectiveMediaQueryResults,
results: &mut Vec<MediaListKey>,
contents_list: &mut StyleSheetContentList,
) where
S: StylesheetInDocument + 'static,
{
@ -2042,19 +2092,25 @@ impl CascadeData {
}
debug!(" + {:?}", stylesheet);
results.saw_effective(stylesheet.contents());
let contents = stylesheet.contents();
results.push(contents.to_media_list_key());
// Safety: StyleSheetContents are reference-counted with Arc.
contents_list.push(StylesheetContentsPtr(unsafe {
Arc::from_raw_addrefed(contents)
}));
for rule in stylesheet.effective_rules(device, guard) {
match *rule {
CssRule::Import(ref lock) => {
let import_rule = lock.read_with(guard);
debug!(" + {:?}", import_rule.stylesheet.media(guard));
results.saw_effective(import_rule);
results.push(import_rule.to_media_list_key());
},
CssRule::Media(ref lock) => {
let media_rule = lock.read_with(guard);
debug!(" + {:?}", media_rule.media_queries.read_with(guard));
results.saw_effective(media_rule);
results.push(media_rule.to_media_list_key());
},
_ => {},
}

View file

@ -7,6 +7,8 @@
use crate::values::animated::{Animate, Procedure, ToAnimatedZero};
use crate::values::distance::{ComputeSquaredDistance, SquaredDistance};
use crate::values::generics::color::{Color as GenericColor, ComplexColorRatios};
use crate::values::specified::color::{ColorSpaceKind, HueAdjuster};
use euclid::default::{Transform3D, Vector3D};
/// An animated RGBA color.
///
@ -41,6 +43,26 @@ impl RGBA {
alpha,
}
}
/// Returns whether or not the colour is in gamut for sRGB.
pub fn in_gamut(&self) -> bool {
0. <= self.red &&
self.red <= 1. &&
0. <= self.green &&
self.green <= 1. &&
0. <= self.blue &&
self.blue <= 1.
}
/// Returns the colour with coordinates clamped to the sRGB range.
pub fn clamp(&self) -> Self {
Self {
red: self.red.max(0.).min(1.),
green: self.green.max(0.).min(1.),
blue: self.blue.max(0.).min(1.),
alpha: self.alpha,
}
}
}
impl Animate for RGBA {
@ -106,43 +128,69 @@ impl Color {
/// Mix two colors into one.
pub fn mix(
color_space: ColorSpaceKind,
left_color: &Color,
left_weight: f32,
right_color: &Color,
right_weight: f32,
hue_adjuster: HueAdjuster,
) -> Self {
let left_bg = left_color.scaled_rgba();
let right_bg = right_color.scaled_rgba();
let alpha = (left_bg.alpha * left_weight +
right_bg.alpha * right_weight)
.min(1.);
let mut fg = 0.;
let mut red = 0.;
let mut green = 0.;
let mut blue = 0.;
let colors = [
(left_color, &left_bg, left_weight),
(right_color, &right_bg, right_weight),
];
for &(color, bg, weight) in &colors {
fg += color.ratios.fg * weight;
red += bg.red * bg.alpha * weight;
green += bg.green * bg.alpha * weight;
blue += bg.blue * bg.alpha * weight;
match color_space {
ColorSpaceKind::Srgb => Self::mix_in::<RGBA>(
left_color,
left_weight,
right_color,
right_weight,
hue_adjuster,
),
ColorSpaceKind::Xyz => Self::mix_in::<XYZA>(
left_color,
left_weight,
right_color,
right_weight,
hue_adjuster,
),
ColorSpaceKind::Lab => Self::mix_in::<LABA>(
left_color,
left_weight,
right_color,
right_weight,
hue_adjuster,
),
ColorSpaceKind::Lch => Self::mix_in::<LCHA>(
left_color,
left_weight,
right_color,
right_weight,
hue_adjuster,
),
}
}
let color = if alpha <= 0. {
RGBA::transparent()
fn mix_in<S>(
left_color: &Color,
left_weight: f32,
right_color: &Color,
right_weight: f32,
hue_adjuster: HueAdjuster,
) -> Self
where
S: ModelledColor,
{
let left_bg = S::from(left_color.scaled_rgba());
let right_bg = S::from(right_color.scaled_rgba());
let color = S::lerp(left_bg, left_weight, right_bg, right_weight, hue_adjuster);
let rgba: RGBA = color.into();
let rgba = if !rgba.in_gamut() {
// TODO: Better gamut mapping.
rgba.clamp()
} else {
let inv = 1. / alpha;
RGBA::new(red * inv, green * inv, blue * inv, alpha)
rgba
};
Self::new(color, ComplexColorRatios { bg: 1., fg })
let fg = left_color.ratios.fg * left_weight + right_color.ratios.fg * right_weight;
Self::new(rgba, ComplexColorRatios { bg: 1., fg })
}
fn scaled_rgba(&self) -> RGBA {
@ -309,3 +357,573 @@ impl ToAnimatedZero for Color {
Ok(RGBA::transparent().into())
}
}
/// A color modelled in a specific color space (such as sRGB or CIE XYZ).
///
/// For now, colors modelled in other spaces need to be convertible to and from
/// `RGBA` because we use sRGB for displaying colors.
trait ModelledColor: Clone + Copy + From<RGBA> + Into<RGBA> {
/// Linearly interpolate between the left and right colors.
///
/// The HueAdjuster parameter is only for color spaces where the hue is
/// represented as an angle (e.g., CIE LCH).
fn lerp(
left_bg: Self,
left_weight: f32,
right_bg: Self,
right_weight: f32,
hue_adjuster: HueAdjuster,
) -> Self;
}
impl ModelledColor for RGBA {
fn lerp(
left_bg: Self,
left_weight: f32,
right_bg: Self,
right_weight: f32,
_: HueAdjuster,
) -> Self {
// Interpolation with alpha, as per
// https://drafts.csswg.org/css-color/#interpolation-alpha.
let mut red = 0.;
let mut green = 0.;
let mut blue = 0.;
// sRGB is a rectangular othogonal color space, so all component values
// are multiplied by the alpha value.
for &(bg, weight) in &[(left_bg, left_weight), (right_bg, right_weight)] {
red += bg.red * bg.alpha * weight;
green += bg.green * bg.alpha * weight;
blue += bg.blue * bg.alpha * weight;
}
let alpha = (left_bg.alpha * left_weight + right_bg.alpha * right_weight).min(1.);
if alpha <= 0. {
RGBA::transparent()
} else {
let inv = 1. / alpha;
RGBA::new(red * inv, green * inv, blue * inv, alpha)
}
}
}
/// An animated XYZA colour.
#[derive(Clone, Copy, Debug)]
pub struct XYZA {
/// The x component.
pub x: f32,
/// The y component.
pub y: f32,
/// The z component.
pub z: f32,
/// The alpha component.
pub alpha: f32,
}
impl XYZA {
/// Returns a transparent color.
#[inline]
pub fn transparent() -> Self {
Self {
x: 0.,
y: 0.,
z: 0.,
alpha: 0.,
}
}
}
impl ModelledColor for XYZA {
fn lerp(
left_bg: Self,
left_weight: f32,
right_bg: Self,
right_weight: f32,
_: HueAdjuster,
) -> Self {
// Interpolation with alpha, as per
// https://drafts.csswg.org/css-color/#interpolation-alpha.
let mut x = 0.;
let mut y = 0.;
let mut z = 0.;
// CIE XYZ is a rectangular othogonal color space, so all component
// values are multiplied by the alpha value.
for &(bg, weight) in &[(left_bg, left_weight), (right_bg, right_weight)] {
x += bg.x * bg.alpha * weight;
y += bg.y * bg.alpha * weight;
z += bg.z * bg.alpha * weight;
}
let alpha = (left_bg.alpha * left_weight + right_bg.alpha * right_weight).min(1.);
if alpha <= 0. {
Self::transparent()
} else {
let inv = 1. / alpha;
Self {
x: x * inv,
y: y * inv,
z: z * inv,
alpha,
}
}
}
}
/// An animated LABA colour.
#[derive(Clone, Copy, Debug)]
pub struct LABA {
/// The lightness component.
pub lightness: f32,
/// The a component.
pub a: f32,
/// The b component.
pub b: f32,
/// The alpha component.
pub alpha: f32,
}
impl LABA {
/// Returns a transparent color.
#[inline]
pub fn transparent() -> Self {
Self {
lightness: 0.,
a: 0.,
b: 0.,
alpha: 0.,
}
}
}
impl ModelledColor for LABA {
fn lerp(
left_bg: Self,
left_weight: f32,
right_bg: Self,
right_weight: f32,
_: HueAdjuster,
) -> Self {
// Interpolation with alpha, as per
// https://drafts.csswg.org/css-color/#interpolation-alpha.
let mut lightness = 0.;
let mut a = 0.;
let mut b = 0.;
// CIE LAB is a rectangular othogonal color space, so all component
// values are multiplied by the alpha value.
for &(bg, weight) in &[(left_bg, left_weight), (right_bg, right_weight)] {
lightness += bg.lightness * bg.alpha * weight;
a += bg.a * bg.alpha * weight;
b += bg.b * bg.alpha * weight;
}
let alpha = (left_bg.alpha * left_weight + right_bg.alpha * right_weight).min(1.);
if alpha <= 0. {
Self::transparent()
} else {
let inv = 1. / alpha;
Self {
lightness: lightness * inv,
a: a * inv,
b: b * inv,
alpha,
}
}
}
}
/// An animated LCHA colour.
#[derive(Clone, Copy, Debug)]
pub struct LCHA {
/// The lightness component.
pub lightness: f32,
/// The chroma component.
pub chroma: f32,
/// The hua component.
pub hue: f32,
/// The alpha component.
pub alpha: f32,
}
impl LCHA {
/// Returns a transparent color.
#[inline]
pub fn transparent() -> Self {
Self {
lightness: 0.,
chroma: 0.,
hue: 0.,
alpha: 0.,
}
}
}
impl LCHA {
fn adjust(left_bg: Self, right_bg: Self, hue_adjuster: HueAdjuster) -> (Self, Self) {
use std::f32::consts::{PI, TAU};
let mut left_bg = left_bg;
let mut right_bg = right_bg;
// Adjust the hue angle as per
// https://drafts.csswg.org/css-color/#hue-interpolation.
//
// If both hue angles are NAN, they should be set to 0. Otherwise, if a
// single hue angle is NAN, it should use the other hue angle.
if left_bg.hue.is_nan() || right_bg.hue.is_nan() {
if left_bg.hue.is_nan() && right_bg.hue.is_nan() {
left_bg.hue = 0.;
right_bg.hue = 0.;
} else if left_bg.hue.is_nan() {
left_bg.hue = right_bg.hue;
} else if right_bg.hue.is_nan() {
right_bg.hue = left_bg.hue;
}
}
if hue_adjuster != HueAdjuster::Specified {
// Normalize hue into [0, 2 * PI)
while left_bg.hue < 0. {
left_bg.hue += TAU;
}
while left_bg.hue > TAU {
left_bg.hue -= TAU;
}
while right_bg.hue < 0. {
right_bg.hue += TAU;
}
while right_bg.hue >= TAU {
right_bg.hue -= TAU;
}
}
match hue_adjuster {
HueAdjuster::Shorter => {
let delta = right_bg.hue - left_bg.hue;
if delta > PI {
left_bg.hue += PI;
} else if delta < -1. * PI {
right_bg.hue += PI;
}
},
HueAdjuster::Longer => {
let delta = right_bg.hue - left_bg.hue;
if 0. < delta && delta < PI {
left_bg.hue += TAU;
} else if -1. * PI < delta && delta < 0. {
right_bg.hue += TAU;
}
},
HueAdjuster::Increasing => {
if right_bg.hue < left_bg.hue {
right_bg.hue += TAU;
}
},
HueAdjuster::Decreasing => {
if left_bg.hue < right_bg.hue {
left_bg.hue += TAU;
}
},
//Angles are not adjusted. They are interpolated like any other
//component.
HueAdjuster::Specified => {},
}
(left_bg, right_bg)
}
}
impl ModelledColor for LCHA {
fn lerp(
left_bg: Self,
left_weight: f32,
right_bg: Self,
right_weight: f32,
hue_adjuster: HueAdjuster,
) -> Self {
// Interpolation with alpha, as per
// https://drafts.csswg.org/css-color/#interpolation-alpha.
let (left_bg, right_bg) = Self::adjust(left_bg, right_bg, hue_adjuster);
let mut lightness = 0.;
let mut chroma = 0.;
let mut hue = 0.;
// CIE LCH is a cylindical polar color space, so all component values
// are multiplied by the alpha value.
for &(bg, weight) in &[(left_bg, left_weight), (right_bg, right_weight)] {
lightness += bg.lightness * bg.alpha * weight;
chroma += bg.chroma * bg.alpha * weight;
// LCHA is a cylindrical color space so the hue coordinate is not
// pre-multipled by the alpha component when interpolating.
hue += bg.hue * weight;
}
let alpha = (left_bg.alpha * left_weight + right_bg.alpha * right_weight).min(1.);
if alpha <= 0. {
Self::transparent()
} else {
let inv = 1. / alpha;
Self {
lightness: lightness * inv,
chroma: chroma * inv,
hue,
alpha,
}
}
}
}
impl From<RGBA> for XYZA {
/// Convert an RGBA colour to XYZ as specified in [1].
///
/// [1]: https://drafts.csswg.org/css-color/#rgb-to-lab
fn from(rgba: RGBA) -> Self {
fn linearize(value: f32) -> f32 {
let sign = if value < 0. { -1. } else { 1. };
let abs = value.abs();
if abs < 0.04045 {
return value / 12.92;
}
sign * ((abs + 0.055) / 1.055).powf(2.4)
}
#[cfg_attr(rustfmt, rustfmt_skip)]
const SRGB_TO_XYZ: Transform3D<f32> = Transform3D::new(
0.41239079926595934, 0.21263900587151027, 0.01933081871559182, 0.,
0.357584339383878, 0.715168678767756, 0.11919477979462598, 0.,
0.1804807884018343, 0.07219231536073371, 0.9505321522496607, 0.,
0., 0., 0., 1.,
);
#[cfg_attr(rustfmt, rustfmt_skip)]
const BRADFORD: Transform3D<f32> = Transform3D::new(
1.0479298208405488, 0.029627815688159344, -0.009243058152591178, 0.,
0.022946793341019088, 0.990434484573249, 0.015055144896577895, 0.,
-0.05019222954313557, -0.01707382502938514, 0.7518742899580008, 0.,
0., 0., 0., 1.,
);
// 1. Convert from sRGB to linear-light sRGB (undo gamma encoding).
let rgb = Vector3D::new(
linearize(rgba.red),
linearize(rgba.green),
linearize(rgba.blue),
);
// 2. Convert from linear sRGB to CIE XYZ.
// 3. Convert from a D65 whitepoint (used by sRGB) to the D50 whitepoint used in XYZ
// with the Bradford transform.
let xyz = SRGB_TO_XYZ.then(&BRADFORD).transform_vector3d(rgb);
XYZA {
x: xyz.x,
y: xyz.y,
z: xyz.z,
alpha: rgba.alpha,
}
}
}
impl From<XYZA> for LABA {
/// Convert an XYZ colour to LAB as specified in [1] and [2].
///
/// [1]: https://drafts.csswg.org/css-color/#rgb-to-lab
/// [2]: https://drafts.csswg.org/css-color/#color-conversion-code
fn from(xyza: XYZA) -> Self {
const WHITE: [f32; 3] = [0.96422, 1., 0.82521];
fn compute_f(value: f32) -> f32 {
const EPSILON: f32 = 216. / 24389.;
const KAPPA: f32 = 24389. / 27.;
if value > EPSILON {
value.cbrt()
} else {
(KAPPA * value + 16.) / 116.
}
}
// 4. Convert D50-adapted XYZ to Lab.
let f = [
compute_f(xyza.x / WHITE[0]),
compute_f(xyza.y / WHITE[1]),
compute_f(xyza.z / WHITE[2]),
];
let lightness = 116. * f[1] - 16.;
let a = 500. * (f[0] - f[1]);
let b = 200. * (f[1] - f[2]);
LABA {
lightness,
a,
b,
alpha: xyza.alpha,
}
}
}
impl From<LABA> for LCHA {
/// Convert a LAB color to LCH as specified in [1].
///
/// [1]: https://drafts.csswg.org/css-color/#color-conversion-code
fn from(laba: LABA) -> Self {
let hue = laba.b.atan2(laba.a);
let chroma = (laba.a * laba.a + laba.b * laba.b).sqrt();
LCHA {
lightness: laba.lightness,
chroma,
hue,
alpha: laba.alpha,
}
}
}
impl From<LCHA> for LABA {
/// Convert a LCH color to LAB as specified in [1].
///
/// [1]: https://drafts.csswg.org/css-color/#color-conversion-code
fn from(lcha: LCHA) -> Self {
let a = lcha.chroma * lcha.hue.cos();
let b = lcha.chroma * lcha.hue.sin();
LABA {
lightness: lcha.lightness,
a,
b,
alpha: lcha.alpha,
}
}
}
impl From<LABA> for XYZA {
/// Convert a CIELAB color to XYZ as specified in [1] and [2].
///
/// [1]: https://drafts.csswg.org/css-color/#lab-to-predefined
/// [2]: https://drafts.csswg.org/css-color/#color-conversion-code
fn from(laba: LABA) -> Self {
// 1. Convert LAB to (D50-adapated) XYZ.
const KAPPA: f32 = 24389. / 27.;
const EPSILON: f32 = 216. / 24389.;
const WHITE: [f32; 3] = [0.96422, 1., 0.82521];
let f1 = (laba.lightness + 16f32) / 116f32;
let f0 = (laba.a / 500.) + f1;
let f2 = f1 - laba.b / 200.;
let x = if f0.powf(3.) > EPSILON {
f0.powf(3.)
} else {
(116. * f0 - 16.) / KAPPA
};
let y = if laba.lightness > KAPPA * EPSILON {
((laba.lightness + 16.) / 116.).powf(3.)
} else {
laba.lightness / KAPPA
};
let z = if f2.powf(3.) > EPSILON {
f2.powf(3.)
} else {
(116. * f2 - 16.) / KAPPA
};
XYZA {
x: x * WHITE[0],
y: y * WHITE[1],
z: z * WHITE[2],
alpha: laba.alpha,
}
}
}
impl From<XYZA> for RGBA {
/// Convert an XYZ color to sRGB as specified in [1] and [2].
///
/// [1]: https://www.w3.org/TR/css-color-4/#lab-to-predefined
/// [2]: https://www.w3.org/TR/css-color-4/#color-conversion-code
fn from(xyza: XYZA) -> Self {
#[cfg_attr(rustfmt, rustfmt_skip)]
const BRADFORD_INVERSE: Transform3D<f32> = Transform3D::new(
0.9554734527042182, -0.028369706963208136, 0.012314001688319899, 0.,
-0.023098536874261423, 1.0099954580058226, -0.020507696433477912, 0.,
0.0632593086610217, 0.021041398966943008, 1.3303659366080753, 0.,
0., 0., 0., 1.,
);
#[cfg_attr(rustfmt, rustfmt_skip)]
const XYZ_TO_SRGB: Transform3D<f32> = Transform3D::new(
3.2409699419045226, -0.9692436362808796, 0.05563007969699366, 0.,
-1.537383177570094, 1.8759675015077202, -0.20397695888897652, 0.,
-0.4986107602930034, 0.04155505740717559, 1.0569715142428786, 0.,
0., 0., 0., 1.,
);
// 2. Convert from a D50 whitepoint (used by Lab) to the D65 whitepoint
// used in sRGB, with the Bradford transform.
// 3. Convert from (D65-adapted) CIE XYZ to linear-light srgb
let xyz = Vector3D::new(xyza.x, xyza.y, xyza.z);
let linear_rgb = BRADFORD_INVERSE.then(&XYZ_TO_SRGB).transform_vector3d(xyz);
// 4. Convert from linear-light srgb to srgb (do gamma encoding).
fn delinearize(value: f32) -> f32 {
let sign = if value < 0. { -1. } else { 1. };
let abs = value.abs();
if abs > 0.0031308 {
sign * (1.055 * abs.powf(1. / 2.4) - 0.055)
} else {
12.92 * value
}
}
let red = delinearize(linear_rgb.x);
let green = delinearize(linear_rgb.y);
let blue = delinearize(linear_rgb.z);
RGBA {
red,
green,
blue,
alpha: xyza.alpha,
}
}
}
impl From<RGBA> for LABA {
fn from(rgba: RGBA) -> Self {
let xyza: XYZA = rgba.into();
xyza.into()
}
}
impl From<LABA> for RGBA {
fn from(laba: LABA) -> Self {
let xyza: XYZA = laba.into();
xyza.into()
}
}
impl From<RGBA> for LCHA {
fn from(rgba: RGBA) -> Self {
let xyza: XYZA = rgba.into();
let laba: LABA = xyza.into();
laba.into()
}
}
impl From<LCHA> for RGBA {
fn from(lcha: LCHA) -> Self {
let laba: LABA = lcha.into();
let xyza: XYZA = laba.into();
xyza.into()
}
}

View file

@ -1105,21 +1105,27 @@ impl Animate for ComputedTransformOperation {
// interpolated as defined in section Interpolation of
// Matrices afterwards.
//
let from = create_perspective_matrix(fd.px());
let to = create_perspective_matrix(td.px());
let from = create_perspective_matrix(fd.infinity_or(|l| l.px()));
let to = create_perspective_matrix(td.infinity_or(|l| l.px()));
let interpolated = Matrix3D::from(from).animate(&Matrix3D::from(to), procedure)?;
let decomposed = decompose_3d_matrix(interpolated)?;
let perspective_z = decomposed.perspective.2;
let used_value = if perspective_z == 0. {
0.
// Clamp results outside of the -1 to 0 range so that we get perspective
// function values between 1 and infinity.
let used_value = if perspective_z >= 0. {
transform::PerspectiveFunction::None
} else {
-1. / perspective_z
transform::PerspectiveFunction::Length(CSSPixelLength::new(
if perspective_z <= -1. {
1.
} else {
-1. / perspective_z
}
))
};
Ok(TransformOperation::Perspective(CSSPixelLength::new(
used_value,
)))
Ok(TransformOperation::Perspective(used_value))
},
_ if self.is_translate() && other.is_translate() => self
.to_translate_3d()
@ -1198,14 +1204,18 @@ impl ComputeSquaredDistance for ComputedTransformOperation {
(
&TransformOperation::Perspective(ref fd),
&TransformOperation::Perspective(ref td),
) => fd.compute_squared_distance(td),
) => {
fd.infinity_or(|l| l.px())
.compute_squared_distance(&td.infinity_or(|l| l.px()))
},
(&TransformOperation::Perspective(ref p), &TransformOperation::Matrix3D(ref m)) |
(&TransformOperation::Matrix3D(ref m), &TransformOperation::Perspective(ref p)) => {
// FIXME(emilio): Is this right? Why interpolating this with
// Perspective but not with anything else?
let mut p_matrix = Matrix3D::identity();
if p.px() > 0. {
p_matrix.m34 = -1. / p.px();
let p = p.infinity_or(|p| p.px());
if p >= 0. {
p_matrix.m34 = -1. / p.max(1.);
}
p_matrix.compute_squared_distance(&m)
},

View file

@ -11,6 +11,8 @@ use cssparser::{Color as CSSParserColor, RGBA};
use std::fmt;
use style_traits::{CssWriter, ToCss};
pub use crate::values::specified::color::ColorScheme;
/// The computed value of the `color` property.
pub type ColorPropertyValue = RGBA;

View file

@ -4,13 +4,12 @@
//! Computed values for font properties
#[cfg(feature = "gecko")]
use crate::gecko_bindings::sugar::refptr::RefPtr;
#[cfg(feature = "gecko")]
use crate::gecko_bindings::{bindings, structs};
use crate::values::animated::{ToAnimatedValue, ToAnimatedZero};
use crate::parser::{Parse, ParserContext};
use crate::values::animated::ToAnimatedValue;
use crate::values::computed::{
Angle, Context, Integer, Length, NonNegativeLength, NonNegativePercentage,
Angle, Context, Integer, Length, NonNegativeLength, NonNegativeNumber, NonNegativePercentage,
};
use crate::values::computed::{Number, Percentage, ToComputedValue};
use crate::values::generics::font::{FeatureTagValue, FontSettings, VariationValue};
@ -26,13 +25,7 @@ use cssparser::{serialize_identifier, CssStringWriter, Parser};
use malloc_size_of::{MallocSizeOf, MallocSizeOfOps};
use std::fmt::{self, Write};
use std::hash::{Hash, Hasher};
#[cfg(feature = "gecko")]
use std::mem::{self, ManuallyDrop};
#[cfg(feature = "servo")]
use std::slice;
use style_traits::{CssWriter, ParseError, ToCss};
#[cfg(feature = "gecko")]
use to_shmem::{self, SharedMemoryBuilder, ToShmem};
pub use crate::values::computed::Length as MozScriptMinSize;
pub use crate::values::specified::font::{FontSynthesis, MozScriptSizeMultiplier};
@ -183,6 +176,7 @@ impl ToAnimatedValue for FontSize {
#[derive(Clone, Debug, Eq, PartialEq, ToComputedValue, ToResolvedValue)]
#[cfg_attr(feature = "servo", derive(Hash, MallocSizeOf, Serialize, Deserialize))]
/// Specifies a prioritized list of font family names or generic family names.
#[repr(C)]
pub struct FontFamily {
/// The actual list of family names.
pub families: FontFamilyList,
@ -190,27 +184,105 @@ pub struct FontFamily {
pub is_system_font: bool,
}
macro_rules! static_font_family {
($ident:ident, $family:expr) => {
lazy_static! {
static ref $ident: FontFamily = FontFamily {
families: FontFamilyList {
#[cfg(feature = "gecko")]
list: crate::ArcSlice::from_iter_leaked(std::iter::once($family)),
#[cfg(feature = "servo")]
list: Box::new([$family]),
#[cfg(feature = "gecko")]
fallback: GenericFontFamily::None,
},
is_system_font: false,
};
}
};
}
impl FontFamily {
#[inline]
/// Get default font family as `serif` which is a generic font-family
pub fn serif() -> Self {
FontFamily {
families: FontFamilyList::new(Box::new([SingleFontFamily::Generic(
GenericFontFamily::Serif,
)])),
is_system_font: false,
Self::generic(GenericFontFamily::Serif).clone()
}
/// Returns the font family for `-moz-bullet-font`.
#[cfg(feature = "gecko")]
pub(crate) fn moz_bullet() -> &'static Self {
static_font_family!(MOZ_BULLET, SingleFontFamily::FamilyName(FamilyName {
name: atom!("-moz-bullet-font"),
syntax: FontFamilyNameSyntax::Identifiers,
}));
&*MOZ_BULLET
}
/// Returns a font family for a single system font.
#[cfg(feature = "gecko")]
pub fn for_system_font(name: &str) -> Self {
Self {
families: FontFamilyList {
list: crate::ArcSlice::from_iter(std::iter::once(SingleFontFamily::FamilyName(FamilyName {
name: Atom::from(name),
syntax: FontFamilyNameSyntax::Identifiers,
}))),
fallback: GenericFontFamily::None,
},
is_system_font: true,
}
}
/// Returns a generic font family.
pub fn generic(generic: GenericFontFamily) -> &'static Self {
macro_rules! generic_font_family {
($ident:ident, $family:ident) => {
static_font_family!($ident, SingleFontFamily::Generic(GenericFontFamily::$family))
}
}
generic_font_family!(SERIF, Serif);
generic_font_family!(SANS_SERIF, SansSerif);
generic_font_family!(MONOSPACE, Monospace);
generic_font_family!(CURSIVE, Cursive);
generic_font_family!(FANTASY, Fantasy);
#[cfg(feature = "gecko")]
generic_font_family!(MOZ_EMOJI, MozEmoji);
generic_font_family!(SYSTEM_UI, SystemUi);
match generic {
GenericFontFamily::None => {
debug_assert!(false, "Bogus caller!");
&*SERIF
}
GenericFontFamily::Serif => &*SERIF,
GenericFontFamily::SansSerif => &*SANS_SERIF,
GenericFontFamily::Monospace => &*MONOSPACE,
GenericFontFamily::Cursive => &*CURSIVE,
GenericFontFamily::Fantasy => &*FANTASY,
#[cfg(feature = "gecko")]
GenericFontFamily::MozEmoji => &*MOZ_EMOJI,
GenericFontFamily::SystemUi => &*SYSTEM_UI,
}
}
}
#[cfg(feature = "gecko")]
impl MallocSizeOf for FontFamily {
fn size_of(&self, _ops: &mut MallocSizeOfOps) -> usize {
// SharedFontList objects are generally shared from the pointer
// stored in the specified value. So only count this if the
// SharedFontList is unshared.
let shared_font_list = self.families.shared_font_list().get();
unsafe { bindings::Gecko_SharedFontList_SizeOfIncludingThisIfUnshared(shared_font_list) }
fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize {
use malloc_size_of::MallocUnconditionalSizeOf;
// SharedFontList objects are generally measured from the pointer stored
// in the specified value. So only count this if the SharedFontList is
// unshared.
let shared_font_list = &self.families.list;
if shared_font_list.is_unique() {
shared_font_list.unconditional_size_of(ops)
} else {
0
}
}
}
@ -220,7 +292,15 @@ impl ToCss for FontFamily {
W: fmt::Write,
{
let mut iter = self.families.iter();
iter.next().unwrap().to_css(dest)?;
match iter.next() {
Some(f) => f.to_css(dest)?,
None => {
#[cfg(feature = "gecko")]
return self.families.fallback.to_css(dest);
#[cfg(feature = "servo")]
unreachable!();
},
}
for family in iter {
dest.write_str(", ")?;
family.to_css(dest)?;
@ -229,15 +309,16 @@ impl ToCss for FontFamily {
}
}
/// The name of a font family of choice.
#[derive(
Clone, Debug, Eq, Hash, MallocSizeOf, PartialEq, ToComputedValue, ToResolvedValue, ToShmem,
)]
#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
/// The name of a font family of choice
#[repr(C)]
pub struct FamilyName {
/// Name of the font family
/// Name of the font family.
pub name: Atom,
/// Syntax of the font family
/// Syntax of the font family.
pub syntax: FontFamilyNameSyntax,
}
@ -291,11 +372,13 @@ pub enum FontFamilyNameSyntax {
Identifiers,
}
/// A set of faces that vary in weight, width or slope.
/// cbindgen:derive-mut-casts=true
#[derive(
Clone, Debug, Eq, MallocSizeOf, PartialEq, ToCss, ToComputedValue, ToResolvedValue, ToShmem,
)]
#[cfg_attr(feature = "servo", derive(Deserialize, Serialize, Hash))]
/// A set of faces that vary in weight, width or slope.
#[repr(u8)]
pub enum SingleFontFamily {
/// The name of a font family of choice.
FamilyName(FamilyName),
@ -303,11 +386,22 @@ pub enum SingleFontFamily {
Generic(GenericFontFamily),
}
fn system_ui_enabled(_: &ParserContext) -> bool {
#[cfg(feature = "gecko")]
return static_prefs::pref!("layout.css.system-ui.enabled");
#[cfg(feature = "servo")]
return false;
}
/// A generic font-family name.
///
/// The order here is important, if you change it make sure that
/// `gfxPlatformFontList.h`s ranged array and `gfxFontFamilyList`'s
/// sSingleGenerics are updated as well.
///
/// NOTE(emilio): Should be u8, but it's a u32 because of ABI issues between GCC
/// and LLVM see https://bugs.llvm.org/show_bug.cgi?id=44228 / bug 1600735 /
/// bug 1726515.
#[derive(
Clone,
Copy,
@ -323,7 +417,7 @@ pub enum SingleFontFamily {
ToShmem,
)]
#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
#[repr(u8)]
#[repr(u32)]
#[allow(missing_docs)]
pub enum GenericFontFamily {
/// No generic family specified, only for internal usage.
@ -337,15 +431,17 @@ pub enum GenericFontFamily {
Monospace,
Cursive,
Fantasy,
#[parse(condition = "system_ui_enabled")]
SystemUi,
/// An internal value for emoji font selection.
#[css(skip)]
#[cfg(feature = "gecko")]
MozEmoji,
}
impl SingleFontFamily {
impl Parse for SingleFontFamily {
/// Parse a font-family value.
pub fn parse<'i, 't>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i>> {
fn parse<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i>> {
if let Ok(value) = input.try_parse(|i| i.expect_string_cloned()) {
return Ok(SingleFontFamily::FamilyName(FamilyName {
name: Atom::from(&*value),
@ -353,11 +449,11 @@ impl SingleFontFamily {
}));
}
let first_ident = input.expect_ident_cloned()?;
if let Ok(generic) = GenericFontFamily::from_ident(&first_ident) {
if let Ok(generic) = input.try_parse(|i| GenericFontFamily::parse(context, i)) {
return Ok(SingleFontFamily::Generic(generic));
}
let first_ident = input.expect_ident_cloned()?;
let reserved = match_ignore_ascii_case! { &first_ident,
// https://drafts.csswg.org/css-fonts/#propdef-font-family
// "Font family names that happen to be the same as a keyword value
@ -400,8 +496,10 @@ impl SingleFontFamily {
syntax,
}))
}
}
#[cfg(feature = "servo")]
#[cfg(feature = "servo")]
impl SingleFontFamily {
/// Get the corresponding font-family with Atom
pub fn from_atom(input: Atom) -> SingleFontFamily {
match input {
@ -410,6 +508,7 @@ impl SingleFontFamily {
atom!("cursive") => return SingleFontFamily::Generic(GenericFontFamily::Cursive),
atom!("fantasy") => return SingleFontFamily::Generic(GenericFontFamily::Fantasy),
atom!("monospace") => return SingleFontFamily::Generic(GenericFontFamily::Monospace),
atom!("system-ui") => return SingleFontFamily::Generic(GenericFontFamily::SystemUi),
_ => {},
}
@ -419,6 +518,7 @@ impl SingleFontFamily {
"cursive" => return SingleFontFamily::Generic(GenericFontFamily::Cursive),
"fantasy" => return SingleFontFamily::Generic(GenericFontFamily::Fantasy),
"monospace" => return SingleFontFamily::Generic(GenericFontFamily::Monospace),
"system-ui" => return SingleFontFamily::Generic(GenericFontFamily::SystemUi),
_ => {}
}
@ -429,22 +529,20 @@ impl SingleFontFamily {
syntax: FontFamilyNameSyntax::Quoted,
})
}
#[cfg(feature = "gecko")]
/// Get the corresponding font-family with family name
fn from_font_family_name(family: &structs::FontFamilyName) -> SingleFontFamily {
if family.mName.mRawPtr.is_null() {
debug_assert_ne!(family.mGeneric, GenericFontFamily::None);
return SingleFontFamily::Generic(family.mGeneric);
}
let name = unsafe { Atom::from_raw(family.mName.mRawPtr) };
SingleFontFamily::FamilyName(FamilyName {
name,
syntax: family.mSyntax,
})
}
}
/// A list of font families.
#[cfg(feature = "gecko")]
#[derive(Clone, Debug, ToComputedValue, ToResolvedValue, ToShmem, PartialEq, Eq)]
#[repr(C)]
pub struct FontFamilyList {
/// The actual list of font families specified.
pub list: crate::ArcSlice<SingleFontFamily>,
/// A fallback font type (none, serif, or sans-serif, generally).
pub fallback: GenericFontFamily,
}
/// A list of font families.
#[cfg(feature = "servo")]
#[derive(
Clone,
@ -459,115 +557,52 @@ impl SingleFontFamily {
ToResolvedValue,
ToShmem,
)]
/// A list of SingleFontFamily
pub struct FontFamilyList(Box<[SingleFontFamily]>);
#[cfg(feature = "gecko")]
#[derive(Clone, Debug, ToComputedValue, ToResolvedValue)]
/// A list of SingleFontFamily
pub enum FontFamilyList {
/// A strong reference to a Gecko SharedFontList object.
SharedFontList(
#[compute(no_field_bound)]
#[resolve(no_field_bound)]
RefPtr<structs::SharedFontList>,
),
/// A font-family generic ID.
Generic(GenericFontFamily),
pub struct FontFamilyList {
/// The actual list of font families specified.
pub list: Box<[SingleFontFamily]>,
}
#[cfg(feature = "gecko")]
impl ToShmem for FontFamilyList {
fn to_shmem(&self, _builder: &mut SharedMemoryBuilder) -> to_shmem::Result<Self> {
// In practice, the only SharedFontList objects we create from shared
// style sheets are ones with a single generic entry.
Ok(ManuallyDrop::new(match *self {
FontFamilyList::SharedFontList(ref r) => {
if !(r.mNames.len() == 1 && r.mNames[0].mName.mRawPtr.is_null()) {
return Err(String::from(
"ToShmem failed for FontFamilyList: cannot handle non-generic families",
));
}
FontFamilyList::Generic(r.mNames[0].mGeneric)
},
FontFamilyList::Generic(t) => FontFamilyList::Generic(t),
}))
}
}
#[cfg(feature = "gecko")]
impl PartialEq for FontFamilyList {
fn eq(&self, other: &FontFamilyList) -> bool {
let self_list = self.shared_font_list();
let other_list = other.shared_font_list();
if self_list.mNames.len() != other_list.mNames.len() {
return false;
}
for (a, b) in self_list.mNames.iter().zip(other_list.mNames.iter()) {
if a.mSyntax != b.mSyntax ||
a.mName.mRawPtr != b.mName.mRawPtr ||
a.mGeneric != b.mGeneric
{
return false;
}
}
true
}
}
#[cfg(feature = "gecko")]
impl Eq for FontFamilyList {}
impl FontFamilyList {
/// Return FontFamilyList with a vector of SingleFontFamily
#[cfg(feature = "servo")]
pub fn new(families: Box<[SingleFontFamily]>) -> FontFamilyList {
FontFamilyList(families)
/// Return iterator of SingleFontFamily
pub fn iter(&self) -> impl Iterator<Item = &SingleFontFamily> {
self.list.iter()
}
/// Return FontFamilyList with a vector of SingleFontFamily
/// Puts the fallback in the list if needed.
#[cfg(feature = "gecko")]
pub fn new(families: Box<[SingleFontFamily]>) -> FontFamilyList {
let fontlist;
let names;
unsafe {
fontlist = bindings::Gecko_SharedFontList_Create();
names = &mut (*fontlist).mNames;
names.ensure_capacity(families.len());
pub fn normalize(&mut self) {
if self.fallback == GenericFontFamily::None {
return;
}
let mut new_list = self.list.iter().cloned().collect::<Vec<_>>();
new_list.push(SingleFontFamily::Generic(self.fallback));
self.list = crate::ArcSlice::from_iter(new_list.into_iter());
}
/// If there's a generic font family on the list (which isn't cursive or
/// fantasy), then move it to the front of the list. Otherwise, prepend the
/// default generic.
#[cfg(feature = "gecko")]
pub (crate) fn prioritize_first_generic_or_prepend(&mut self, generic: GenericFontFamily) {
let index_of_first_generic = self.iter().position(|f| {
match *f {
SingleFontFamily::Generic(f) => f != GenericFontFamily::Cursive && f != GenericFontFamily::Fantasy,
_ => false,
}
});
if let Some(0) = index_of_first_generic {
return; // Already first
}
let mut new_list = self.list.iter().cloned().collect::<Vec<_>>();
let element_to_prepend = match index_of_first_generic {
Some(i) => new_list.remove(i),
None => SingleFontFamily::Generic(generic),
};
for family in families.iter() {
match *family {
SingleFontFamily::FamilyName(ref f) => unsafe {
bindings::Gecko_nsTArray_FontFamilyName_AppendNamed(
names,
f.name.as_ptr(),
f.syntax,
);
},
SingleFontFamily::Generic(family) => unsafe {
bindings::Gecko_nsTArray_FontFamilyName_AppendGeneric(names, family);
},
}
}
FontFamilyList::SharedFontList(unsafe { RefPtr::from_addrefed(fontlist) })
}
/// Return iterator of SingleFontFamily
#[cfg(feature = "servo")]
pub fn iter(&self) -> slice::Iter<SingleFontFamily> {
self.0.iter()
}
/// Return iterator of SingleFontFamily
#[cfg(feature = "gecko")]
pub fn iter(&self) -> FontFamilyNameIter {
FontFamilyNameIter {
names: &self.shared_font_list().mNames,
cur: 0,
}
new_list.insert(0, element_to_prepend);
self.list = crate::ArcSlice::from_iter(new_list.into_iter());
}
/// Return the generic ID if it is a single generic font
@ -580,67 +615,10 @@ impl FontFamilyList {
}
None
}
/// Return a reference to the Gecko SharedFontList.
#[cfg(feature = "gecko")]
pub fn shared_font_list(&self) -> &RefPtr<structs::SharedFontList> {
match *self {
FontFamilyList::SharedFontList(ref r) => r,
FontFamilyList::Generic(t) => {
unsafe {
// TODO(heycam): Should really add StaticRefPtr sugar.
let index = t as usize;
mem::transmute::<
&structs::StaticRefPtr<structs::SharedFontList>,
&RefPtr<structs::SharedFontList>,
>(&structs::SharedFontList_sSingleGenerics[index])
}
},
}
}
}
/// Iterator of FontFamily
#[cfg(feature = "gecko")]
pub struct FontFamilyNameIter<'a> {
names: &'a structs::nsTArray<structs::FontFamilyName>,
cur: usize,
}
#[cfg(feature = "gecko")]
impl<'a> Iterator for FontFamilyNameIter<'a> {
type Item = SingleFontFamily;
fn next(&mut self) -> Option<Self::Item> {
if self.cur < self.names.len() {
let item = SingleFontFamily::from_font_family_name(&self.names[self.cur]);
self.cur += 1;
Some(item)
} else {
None
}
}
}
/// Preserve the readability of text when font fallback occurs
#[derive(
Animate,
Clone,
ComputeSquaredDistance,
Copy,
Debug,
MallocSizeOf,
PartialEq,
ToCss,
ToResolvedValue,
)]
pub enum FontSizeAdjust {
#[animation(error)]
/// None variant
None,
/// Number variant
Number(CSSFloat),
}
pub type FontSizeAdjust = generics::GenericFontSizeAdjust<NonNegativeNumber>;
impl FontSizeAdjust {
#[inline]
@ -648,40 +626,6 @@ impl FontSizeAdjust {
pub fn none() -> Self {
FontSizeAdjust::None
}
/// Get font-size-adjust with float number
pub fn from_gecko_adjust(gecko: f32) -> Self {
if gecko == -1.0 {
FontSizeAdjust::None
} else {
FontSizeAdjust::Number(gecko)
}
}
}
impl ToAnimatedZero for FontSizeAdjust {
#[inline]
// FIXME(emilio): why?
fn to_animated_zero(&self) -> Result<Self, ()> {
Err(())
}
}
impl ToAnimatedValue for FontSizeAdjust {
type AnimatedValue = Self;
#[inline]
fn to_animated_value(self) -> Self {
self
}
#[inline]
fn from_animated_value(animated: Self::AnimatedValue) -> Self {
match animated {
FontSizeAdjust::Number(number) => FontSizeAdjust::Number(number.max(0.)),
_ => animated,
}
}
}
/// Use VariantAlternatesList as computed type of FontVariantAlternates

View file

@ -5,6 +5,7 @@
//! `<length>` computed values, and related ones.
use super::{Context, Number, ToComputedValue};
use crate::computed_value_flags::ComputedValueFlags;
use crate::values::animated::ToAnimatedValue;
use crate::values::computed::NonNegativeNumber;
use crate::values::generics::length as generics;
@ -36,6 +37,7 @@ impl ToComputedValue for specified::NoCalcLength {
length.to_computed_value(context, FontBaseSize::CurrentStyle)
},
specified::NoCalcLength::ViewportPercentage(length) => {
context.builder.add_flags(ComputedValueFlags::USES_VIEWPORT_UNITS);
length.to_computed_value(context.viewport_size_for_viewport_unit_resolution())
},
specified::NoCalcLength::ServoCharacterWidth(length) => {
@ -188,7 +190,8 @@ impl Size {
GenericSize::MinContent |
GenericSize::MaxContent |
GenericSize::MozFitContent |
GenericSize::MozAvailable => false
GenericSize::MozAvailable |
GenericSize::FitContentFunction(_) => false
}
}
}

View file

@ -607,22 +607,11 @@ impl CalcLengthPercentageLeaf {
impl PartialOrd for CalcLengthPercentageLeaf {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
use self::CalcLengthPercentageLeaf::*;
if std::mem::discriminant(self) != std::mem::discriminant(other) {
return None;
}
// NOTE: Percentages can't be compared reasonably here because the
// percentage basis might be negative, see bug 1709018.
match (self, other) {
(&Length(ref one), &Length(ref other)) => one.partial_cmp(other),
(&Percentage(ref one), &Percentage(ref other)) => one.partial_cmp(other),
_ => {
match *self {
Length(..) | Percentage(..) => {},
}
unsafe {
debug_unreachable!("Forgot a branch?");
}
},
_ => None,
}
}
}

View file

@ -50,7 +50,7 @@ pub use self::box_::{Display, Overflow, OverflowAnchor, TransitionProperty};
pub use self::box_::{OverflowClipBox, OverscrollBehavior, Perspective, Resize};
pub use self::box_::{ScrollSnapAlign, ScrollSnapAxis, ScrollSnapStrictness, ScrollSnapType};
pub use self::box_::{TouchAction, VerticalAlign, WillChange};
pub use self::color::{Color, ColorOrAuto, ColorPropertyValue};
pub use self::color::{Color, ColorOrAuto, ColorPropertyValue, ColorScheme};
pub use self::column::ColumnCount;
pub use self::counters::{Content, ContentItem, CounterIncrement, CounterSetOrReset};
pub use self::easing::TimingFunction;
@ -82,7 +82,7 @@ pub use self::position::{
pub use self::ratio::Ratio;
pub use self::rect::NonNegativeLengthOrNumberRect;
pub use self::resolution::Resolution;
pub use self::svg::MozContextProperties;
pub use self::svg::{DProperty, MozContextProperties};
pub use self::svg::{SVGLength, SVGOpacity, SVGPaint, SVGPaintKind};
pub use self::svg::{SVGPaintOrder, SVGStrokeDashArray, SVGWidth};
pub use self::text::TextUnderlinePosition;

View file

@ -5,10 +5,69 @@
//! Computed @page at-rule properties
use crate::values::computed::length::NonNegativeLength;
use crate::values::computed::{Context, ToComputedValue};
use crate::values::generics;
use crate::values::generics::size::Size2D;
use crate::values::specified::page as specified;
pub use generics::page::GenericPageSize;
pub use generics::page::Orientation;
pub use generics::page::PaperSize;
/// Computed value of the @page size descriptor
pub type PageSize = generics::page::GenericPageSize<Size2D<NonNegativeLength>>;
///
/// The spec says that the computed value should be the same as the specified
/// value but with all absolute units, but it's not currently possibly observe
/// the computed value of page-size.
#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, ToCss, ToResolvedValue, ToShmem)]
#[repr(C, u8)]
pub enum PageSize {
/// Specified size, paper size, or paper size and orientation.
Size(Size2D<NonNegativeLength>),
/// `landscape` or `portrait` value, no specified size.
Orientation(Orientation),
/// `auto` value
Auto,
}
impl ToComputedValue for specified::PageSize {
type ComputedValue = PageSize;
fn to_computed_value(&self, ctx: &Context) -> Self::ComputedValue {
match &*self {
Self::Size(s) => PageSize::Size(s.to_computed_value(ctx)),
Self::PaperSize(p, Orientation::Landscape) => PageSize::Size(Size2D {
width: p.long_edge().to_computed_value(ctx),
height: p.short_edge().to_computed_value(ctx),
}),
Self::PaperSize(p, Orientation::Portrait) => PageSize::Size(Size2D {
width: p.short_edge().to_computed_value(ctx),
height: p.long_edge().to_computed_value(ctx),
}),
Self::Orientation(o) => PageSize::Orientation(*o),
Self::Auto => PageSize::Auto,
}
}
fn from_computed_value(computed: &Self::ComputedValue) -> Self {
match *computed {
PageSize::Size(s) => Self::Size(ToComputedValue::from_computed_value(&s)),
PageSize::Orientation(o) => Self::Orientation(o),
PageSize::Auto => Self::Auto,
}
}
}
impl PageSize {
/// `auto` value.
#[inline]
pub fn auto() -> Self {
PageSize::Auto
}
/// Whether this is the `auto` value.
#[inline]
pub fn is_auto(&self) -> bool {
matches!(*self, PageSize::Auto)
}
}

View file

@ -11,7 +11,7 @@ use crate::values::generics::svg as generic;
use crate::values::RGBA;
use crate::Zero;
pub use crate::values::specified::{MozContextProperties, SVGPaintOrder};
pub use crate::values::specified::{DProperty, MozContextProperties, SVGPaintOrder};
/// Computed SVG Paint value
pub type SVGPaint = generic::GenericSVGPaint<Color, ComputedUrl>;

View file

@ -110,6 +110,14 @@ impl ToComputedValue for specified::WordSpacing {
/// A computed value for the `line-height` property.
pub type LineHeight = GenericLineHeight<NonNegativeNumber, NonNegativeLength>;
impl WordSpacing {
/// Return the `normal` computed value, which is just zero.
#[inline]
pub fn normal() -> Self {
LengthPercentage::zero()
}
}
#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToResolvedValue)]
#[repr(C)]
/// text-overflow.

View file

@ -24,6 +24,9 @@ pub type Transform = generic::GenericTransform<TransformOperation>;
pub type TransformOrigin =
generic::GenericTransformOrigin<LengthPercentage, LengthPercentage, Length>;
/// The computed value of the `perspective()` transform function.
pub type PerspectiveFunction = generic::PerspectiveFunction<Length>;
/// A vector to represent the direction vector (rotate axis) for Rotate3D.
pub type DirectionVector = Vector3D<CSSFloat>;
@ -516,8 +519,8 @@ impl ToAnimatedZero for TransformOperation {
generic::TransformOperation::Rotate(_) => {
Ok(generic::TransformOperation::Rotate(Angle::zero()))
},
generic::TransformOperation::Perspective(ref l) => Ok(
generic::TransformOperation::Perspective(l.to_animated_zero()?),
generic::TransformOperation::Perspective(_) => Ok(
generic::TransformOperation::Perspective(generic::PerspectiveFunction::None)
),
generic::TransformOperation::AccumulateMatrix { .. } |
generic::TransformOperation::InterpolateMatrix { .. } => {

View file

@ -147,7 +147,7 @@ impl<L> BorderSpacing<L> {
}
}
/// A generic value for `border-radius`, `outline-radius` and `inset()`.
/// A generic value for `border-radius` and `inset()`.
///
/// <https://drafts.csswg.org/css-backgrounds-3/#border-radius>
#[derive(

View file

@ -202,3 +202,60 @@ pub enum FontStyle<Angle> {
#[value_info(starts_with_keyword)]
Oblique(Angle),
}
/// A generic value for the `font-size-adjust` property.
///
/// https://www.w3.org/TR/css-fonts-4/#font-size-adjust-prop
/// https://github.com/w3c/csswg-drafts/issues/6160
/// https://github.com/w3c/csswg-drafts/issues/6288
#[allow(missing_docs)]
#[repr(u8)]
#[derive(
Animate,
Clone,
ComputeSquaredDistance,
Copy,
Debug,
Hash,
MallocSizeOf,
PartialEq,
SpecifiedValueInfo,
ToAnimatedValue,
ToAnimatedZero,
ToComputedValue,
ToResolvedValue,
ToShmem,
)]
pub enum GenericFontSizeAdjust<Number> {
#[animation(error)]
None,
// 'ex-height' is the implied basis, so the keyword can be omitted
ExHeight(Number),
#[value_info(starts_with_keyword)]
CapHeight(Number),
#[value_info(starts_with_keyword)]
ChWidth(Number),
#[value_info(starts_with_keyword)]
IcWidth(Number),
#[value_info(starts_with_keyword)]
IcHeight(Number),
}
impl<Number: ToCss> ToCss for GenericFontSizeAdjust<Number> {
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
where
W: Write,
{
let (prefix, value) = match self {
Self::None => return dest.write_str("none"),
Self::ExHeight(v) => ("", v),
Self::CapHeight(v) => ("cap-height ", v),
Self::ChWidth(v) => ("ch-width ", v),
Self::IcWidth(v) => ("ic-width ", v),
Self::IcHeight(v) => ("ic-height ", v),
};
dest.write_str(prefix)?;
value.to_css(dest)
}
}

View file

@ -161,6 +161,10 @@ pub enum GenericSize<LengthPercent> {
#[cfg(feature = "gecko")]
#[animation(error)]
MozAvailable,
#[cfg(feature = "gecko")]
#[animation(error)]
#[css(function = "fit-content")]
FitContentFunction(LengthPercent)
}
pub use self::GenericSize as Size;
@ -215,6 +219,10 @@ pub enum GenericMaxSize<LengthPercent> {
#[cfg(feature = "gecko")]
#[animation(error)]
MozAvailable,
#[cfg(feature = "gecko")]
#[animation(error)]
#[css(function = "fit-content")]
FitContentFunction(LengthPercent),
}
pub use self::GenericMaxSize as MaxSize;
@ -227,7 +235,7 @@ impl<LengthPercentage> MaxSize<LengthPercentage> {
}
}
/// A generic `<length>` | `<number>` value for the `-moz-tab-size` property.
/// A generic `<length>` | `<number>` value for the `tab-size` property.
#[derive(
Animate,
Clone,

View file

@ -95,6 +95,21 @@ impl CounterStyle {
pub fn decimal() -> Self {
CounterStyle::Name(CustomIdent(atom!("decimal")))
}
/// Is this a bullet? (i.e. `list-style-type: disc|circle|square|disclosure-closed|disclosure-open`)
#[inline]
pub fn is_bullet(&self) -> bool {
match self {
CounterStyle::Name(CustomIdent(ref name)) => {
name == &atom!("disc") ||
name == &atom!("circle") ||
name == &atom!("square") ||
name == &atom!("disclosure-closed") ||
name == &atom!("disclosure-open")
}
_ => false,
}
}
}
impl Parse for CounterStyle {

View file

@ -4,22 +4,14 @@
//! @page at-rule properties
use crate::values::generics::NonNegative;
use crate::values::specified::length::AbsoluteLength;
/// Page size names.
///
/// https://drafts.csswg.org/css-page-3/#typedef-page-size-page-size
#[derive(
Clone,
Copy,
Debug,
Eq,
MallocSizeOf,
Parse,
PartialEq,
SpecifiedValueInfo,
ToComputedValue,
ToCss,
ToResolvedValue,
ToShmem,
Clone, Copy, Debug, Eq, MallocSizeOf, Parse, PartialEq, SpecifiedValueInfo, ToCss, ToShmem,
)]
#[repr(u8)]
pub enum PaperSize {
@ -45,6 +37,39 @@ pub enum PaperSize {
Ledger,
}
impl PaperSize {
/// Gets the long edge length of the paper size
pub fn long_edge(&self) -> NonNegative<AbsoluteLength> {
NonNegative(match *self {
PaperSize::A5 => AbsoluteLength::Mm(210.0),
PaperSize::A4 => AbsoluteLength::Mm(297.0),
PaperSize::A3 => AbsoluteLength::Mm(420.0),
PaperSize::B5 => AbsoluteLength::Mm(250.0),
PaperSize::B4 => AbsoluteLength::Mm(353.0),
PaperSize::JisB5 => AbsoluteLength::Mm(257.0),
PaperSize::JisB4 => AbsoluteLength::Mm(364.0),
PaperSize::Letter => AbsoluteLength::In(11.0),
PaperSize::Legal => AbsoluteLength::In(14.0),
PaperSize::Ledger => AbsoluteLength::In(17.0),
})
}
/// Gets the short edge length of the paper size
pub fn short_edge(&self) -> NonNegative<AbsoluteLength> {
NonNegative(match *self {
PaperSize::A5 => AbsoluteLength::Mm(148.0),
PaperSize::A4 => AbsoluteLength::Mm(210.0),
PaperSize::A3 => AbsoluteLength::Mm(297.0),
PaperSize::B5 => AbsoluteLength::Mm(176.0),
PaperSize::B4 => AbsoluteLength::Mm(250.0),
PaperSize::JisB5 => AbsoluteLength::Mm(182.0),
PaperSize::JisB4 => AbsoluteLength::Mm(257.0),
PaperSize::Letter => AbsoluteLength::In(8.5),
PaperSize::Legal => AbsoluteLength::In(8.5),
PaperSize::Ledger => AbsoluteLength::In(11.0),
})
}
}
/// Paper orientation
///
/// https://drafts.csswg.org/css-page-3/#page-size-prop
@ -57,7 +82,6 @@ pub enum PaperSize {
Parse,
PartialEq,
SpecifiedValueInfo,
ToComputedValue,
ToCss,
ToResolvedValue,
ToShmem,
@ -70,33 +94,25 @@ pub enum Orientation {
Landscape,
}
#[inline]
fn is_portrait(orientation: &Orientation) -> bool {
*orientation == Orientation::Portrait
}
/// Page size property
///
/// https://drafts.csswg.org/css-page-3/#page-size-prop
#[derive(
Clone,
Copy,
Debug,
MallocSizeOf,
PartialEq,
SpecifiedValueInfo,
ToComputedValue,
ToCss,
ToResolvedValue,
ToShmem,
)]
#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)]
#[repr(C, u8)]
pub enum GenericPageSize<S> {
/// Page dimensions.
Size(S),
/// Paper size with no orientation.
PaperSize(PaperSize),
/// An orientation with no size.
Orientation(Orientation),
/// Paper size by name, with an orientation.
PaperSizeAndOrientation(PaperSize, Orientation),
/// `auto` value.
Auto,
/// Page dimensions.
Size(S),
/// An orientation with no size.
Orientation(Orientation),
/// Paper size by name
PaperSize(PaperSize, #[css(skip_if = "is_portrait")] Orientation),
}
pub use self::GenericPageSize as PageSize;

View file

@ -141,6 +141,41 @@ fn is_same<N: PartialEq>(x: &N, y: &N) -> bool {
x == y
}
/// A value for the `perspective()` transform function, which is either a
/// non-negative `<length>` or `none`.
#[derive(
Clone,
Debug,
Deserialize,
MallocSizeOf,
PartialEq,
Serialize,
SpecifiedValueInfo,
ToComputedValue,
ToCss,
ToResolvedValue,
ToShmem,
)]
#[repr(C, u8)]
pub enum GenericPerspectiveFunction<L> {
/// `none`
None,
/// A `<length>`.
Length(L),
}
impl<L> GenericPerspectiveFunction<L> {
/// Returns `f32::INFINITY` or the result of a function on the length value.
pub fn infinity_or(&self, f: impl FnOnce(&L) -> f32) -> f32 {
match *self {
Self::None => std::f32::INFINITY,
Self::Length(ref l) => f(l),
}
}
}
pub use self::GenericPerspectiveFunction as PerspectiveFunction;
#[derive(
Clone,
Debug,
@ -240,7 +275,7 @@ where
///
/// The value must be greater than or equal to zero.
#[css(function)]
Perspective(Length),
Perspective(GenericPerspectiveFunction<Length>),
/// A intermediate type for interpolation of mismatched transform lists.
#[allow(missing_docs)]
#[css(comma, function = "interpolatematrix")]
@ -443,36 +478,38 @@ where
use self::TransformOperation::*;
use std::f64;
const TWO_PI: f64 = 2.0f64 * f64::consts::PI;
let reference_width = reference_box.map(|v| v.size.width);
let reference_height = reference_box.map(|v| v.size.height);
let matrix = match *self {
Rotate3D(ax, ay, az, theta) => {
let theta = TWO_PI - theta.radians64();
let theta = theta.radians64();
let (ax, ay, az, theta) =
get_normalized_vector_and_angle(ax.into(), ay.into(), az.into(), theta);
Transform3D::rotation(
ax as f64,
ay as f64,
az as f64,
-euclid::Angle::radians(theta),
euclid::Angle::radians(theta),
)
},
RotateX(theta) => {
let theta = euclid::Angle::radians(TWO_PI - theta.radians64());
Transform3D::rotation(1., 0., 0., -theta)
let theta = euclid::Angle::radians(theta.radians64());
Transform3D::rotation(1., 0., 0., theta)
},
RotateY(theta) => {
let theta = euclid::Angle::radians(TWO_PI - theta.radians64());
Transform3D::rotation(0., 1., 0., -theta)
let theta = euclid::Angle::radians(theta.radians64());
Transform3D::rotation(0., 1., 0., theta)
},
RotateZ(theta) | Rotate(theta) => {
let theta = euclid::Angle::radians(TWO_PI - theta.radians64());
Transform3D::rotation(0., 0., 1., -theta)
let theta = euclid::Angle::radians(theta.radians64());
Transform3D::rotation(0., 0., 1., theta)
},
Perspective(ref d) => {
let m = create_perspective_matrix(d.to_pixel_length(None)?);
m.cast()
Perspective(ref p) => {
let px = match p {
PerspectiveFunction::None => std::f32::INFINITY,
PerspectiveFunction::Length(ref p) => p.to_pixel_length(None)?,
};
create_perspective_matrix(px).cast()
},
Scale3D(sx, sy, sz) => Transform3D::scale(sx.into(), sy.into(), sz.into()),
Scale(sx, sy) => Transform3D::scale(sx.into(), sy.into(), 1.),
@ -583,17 +620,10 @@ impl<T: ToMatrix> Transform<T> {
/// Return the transform matrix from a perspective length.
#[inline]
pub fn create_perspective_matrix(d: CSSFloat) -> Transform3D<CSSFloat> {
// TODO(gw): The transforms spec says that perspective length must
// be positive. However, there is some confusion between the spec
// and browser implementations as to handling the case of 0 for the
// perspective value. Until the spec bug is resolved, at least ensure
// that a provided perspective value of <= 0.0 doesn't cause panics
// and behaves as it does in other browsers.
// See https://lists.w3.org/Archives/Public/www-style/2016Jan/0020.html for more details.
if d <= 0.0 {
Transform3D::identity()
if d.is_finite() {
Transform3D::perspective(d.max(1.))
} else {
Transform3D::perspective(d)
Transform3D::identity()
}
}

View file

@ -7,6 +7,14 @@
use super::{Context, ToResolvedValue};
use crate::values::computed;
#[inline]
fn allow_element_content_none() -> bool {
#[cfg(feature = "gecko")]
return static_prefs::pref!("layout.css.element-content-none.enabled");
#[cfg(feature = "servo")]
return false;
}
/// https://drafts.csswg.org/css-content/#content-property
///
/// We implement this at resolved value time because otherwise it causes us to
@ -23,19 +31,19 @@ impl ToResolvedValue for computed::Content {
#[inline]
fn to_resolved_value(self, context: &Context) -> Self {
let is_before_or_after = context
.style
.pseudo()
.map_or(false, |p| p.is_before_or_after());
let (is_pseudo, is_before_or_after, is_marker) =
match context.style.pseudo() {
Some(ref pseudo) => (true, pseudo.is_before_or_after(), pseudo.is_marker()),
None => (false, false, false)
};
match self {
Self::Normal if is_before_or_after => Self::None,
// For now, make `content: none` compute to `normal` on other
// elements, as we don't respect it.
//
// FIXME(emilio, bug 1605473): for marker this should be preserved
// and respected, probably.
Self::None if !is_before_or_after => Self::Normal,
// For now, make `content: none` compute to `normal` for pseudos
// other than ::before, ::after and ::marker, as we don't respect it.
// https://github.com/w3c/csswg-drafts/issues/6124
// Ditto for non-pseudo elements if the pref is disabled.
Self::None if (is_pseudo && !is_before_or_after && !is_marker) ||
(!is_pseudo && !allow_element_content_none()) => Self::Normal,
other => other,
}
}

View file

@ -55,15 +55,6 @@ pub type ShapeRadius = generic::ShapeRadius<NonNegativeLengthPercentage>;
/// The specified value of `Polygon`
pub type Polygon = generic::GenericPolygon<LengthPercentage>;
#[cfg(feature = "gecko")]
fn is_clip_path_path_enabled(context: &ParserContext) -> bool {
context.chrome_rules_enabled() || static_prefs::pref!("layout.css.clip-path-path.enabled")
}
#[cfg(feature = "servo")]
fn is_clip_path_path_enabled(_: &ParserContext) -> bool {
false
}
/// A helper for both clip-path and shape-outside parsing of shapes.
fn parse_shape_or_box<'i, 't, R, ReferenceBox>(
context: &ParserContext,
@ -116,10 +107,8 @@ impl Parse for ClipPath {
return Ok(ClipPath::None);
}
if is_clip_path_path_enabled(context) {
if let Ok(p) = input.try_parse(|i| Path::parse(context, i)) {
return Ok(ClipPath::Path(p));
}
if let Ok(p) = input.try_parse(|i| Path::parse(context, i)) {
return Ok(ClipPath::Path(p));
}
if let Ok(url) = input.try_parse(|i| SpecifiedUrl::parse(context, i)) {

View file

@ -1838,8 +1838,6 @@ pub enum Appearance {
#[parse(condition = "ParserContext::in_ua_or_chrome_sheet")]
MozWindowTitlebarMaximized,
#[parse(condition = "ParserContext::in_ua_or_chrome_sheet")]
MozGtkInfoBar,
#[parse(condition = "ParserContext::in_ua_or_chrome_sheet")]
MozMacActiveSourceListSelection,
#[parse(condition = "ParserContext::in_ua_or_chrome_sheet")]
@ -1896,28 +1894,20 @@ pub enum BreakBetween {
}
impl BreakBetween {
/// Parse a legacy break-between value for `page-break-*`.
/// Parse a legacy break-between value for `page-break-{before,after}`.
///
/// See https://drafts.csswg.org/css-break/#page-break-properties.
#[cfg(feature = "gecko")]
#[inline]
pub fn parse_legacy<'i>(input: &mut Parser<'i, '_>) -> Result<Self, ParseError<'i>> {
let location = input.current_source_location();
let ident = input.expect_ident()?;
let break_value = match BreakBetween::from_ident(ident) {
Ok(v) => v,
Err(()) => {
return Err(location
.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(ident.clone())));
},
};
pub(crate) fn parse_legacy<'i>(_: &ParserContext, input: &mut Parser<'i, '_>) -> Result<Self, ParseError<'i>> {
let break_value = BreakBetween::parse(input)?;
match break_value {
BreakBetween::Always => Ok(BreakBetween::Page),
BreakBetween::Auto | BreakBetween::Avoid | BreakBetween::Left | BreakBetween::Right => {
Ok(break_value)
},
BreakBetween::Page => {
Err(location
.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(ident.clone())))
Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
},
}
}
@ -1925,7 +1915,8 @@ impl BreakBetween {
/// Serialize a legacy break-between value for `page-break-*`.
///
/// See https://drafts.csswg.org/css-break/#page-break-properties.
pub fn to_css_legacy<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
#[cfg(feature = "gecko")]
pub(crate) fn to_css_legacy<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
where
W: Write,
{
@ -1962,6 +1953,39 @@ impl BreakBetween {
pub enum BreakWithin {
Auto,
Avoid,
AvoidPage,
AvoidColumn,
}
impl BreakWithin {
/// Parse a legacy break-between value for `page-break-inside`.
///
/// See https://drafts.csswg.org/css-break/#page-break-properties.
#[cfg(feature = "gecko")]
#[inline]
pub(crate) fn parse_legacy<'i>(_: &ParserContext, input: &mut Parser<'i, '_>) -> Result<Self, ParseError<'i>> {
let break_value = BreakWithin::parse(input)?;
match break_value {
BreakWithin::Auto | BreakWithin::Avoid => Ok(break_value),
BreakWithin::AvoidPage | BreakWithin::AvoidColumn => {
Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
},
}
}
/// Serialize a legacy break-between value for `page-break-inside`.
///
/// See https://drafts.csswg.org/css-break/#page-break-properties.
#[cfg(feature = "gecko")]
pub(crate) fn to_css_legacy<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
where
W: Write,
{
match *self {
BreakWithin::Auto | BreakWithin::Avoid => self.to_css(dest),
BreakWithin::AvoidPage | BreakWithin::AvoidColumn => Ok(()),
}
}
}
/// The value for the `overflow-x` / `overflow-y` properties.

View file

@ -107,8 +107,12 @@ impl PartialOrd for Leaf {
}
match (self, other) {
// NOTE: Percentages can't be compared reasonably here because the
// percentage basis might be negative, see bug 1709018.
// Conveniently, we only use this for <length-percentage> (for raw
// percentages, we go through resolve()).
(&Percentage(..), &Percentage(..)) => None,
(&Length(ref one), &Length(ref other)) => one.partial_cmp(other),
(&Percentage(ref one), &Percentage(ref other)) => one.partial_cmp(other),
(&Angle(ref one), &Angle(ref other)) => one.degrees().partial_cmp(&other.degrees()),
(&Time(ref one), &Time(ref other)) => one.seconds().partial_cmp(&other.seconds()),
(&Number(ref one), &Number(ref other)) => one.partial_cmp(other),

View file

@ -9,9 +9,10 @@ use super::AllowQuirks;
use crate::gecko_bindings::structs::nscolor;
use crate::parser::{Parse, ParserContext};
use crate::values::computed::{Color as ComputedColor, Context, ToComputedValue};
use crate::values::generics::color::{GenericColorOrAuto, GenericCaretColor};
use crate::values::generics::color::{GenericCaretColor, GenericColorOrAuto};
use crate::values::specified::calc::CalcNode;
use crate::values::specified::Percentage;
use crate::values::CustomIdent;
use cssparser::{AngleOrNumber, Color as CSSParserColor, Parser, Token, RGBA};
use cssparser::{BasicParseErrorKind, NumberOrPercentage, ParseErrorKind};
use itoa;
@ -20,29 +21,67 @@ use std::io::Write as IoWrite;
use style_traits::{CssType, CssWriter, KeywordsCollectFn, ParseError, StyleParseErrorKind};
use style_traits::{SpecifiedValueInfo, ToCss, ValueParseErrorKind};
/// A color space as defined in [1].
///
/// [1]: https://drafts.csswg.org/css-color-5/#typedef-colorspace
#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, Parse, PartialEq, ToCss, ToShmem)]
pub enum ColorSpaceKind {
/// The sRGB color space.
Srgb,
/// The CIEXYZ color space.
Xyz,
/// The CIELAB color space.
Lab,
/// The CIELAB color space, expressed in cylindrical coordinates.
Lch,
}
/// A hue adjuster as defined in [1].
///
/// [1]: https://drafts.csswg.org/css-color-5/#typedef-hue-adjuster
#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, Parse, PartialEq, ToCss, ToShmem)]
pub enum HueAdjuster {
/// The "shorter" angle adjustment.
Shorter,
/// The "longer" angle adjustment.
Longer,
/// The "increasing" angle adjustment.
Increasing,
/// The "decreasing" angle adjustment.
Decreasing,
/// The "specified" angle adjustment.
Specified,
}
/// A restricted version of the css `color-mix()` function, which only supports
/// percentages and sRGB color-space interpolation.
/// percentages.
///
/// https://drafts.csswg.org/css-color-5/#color-mix
#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
#[allow(missing_docs)]
pub struct ColorMix {
pub color_space: ColorSpaceKind,
pub left: Color,
pub left_percentage: Percentage,
pub right: Color,
pub right_percentage: Percentage,
pub hue_adjuster: HueAdjuster,
}
#[cfg(feature = "gecko")]
#[inline]
fn allow_color_mix() -> bool {
static_prefs::pref!("layout.css.color-mix.enabled")
#[cfg(feature = "gecko")]
return static_prefs::pref!("layout.css.color-mix.enabled");
#[cfg(feature = "servo")]
return false;
}
#[cfg(feature = "servo")]
#[inline]
fn allow_color_mix() -> bool {
false
fn allow_color_mix_color_spaces() -> bool {
#[cfg(feature = "gecko")]
return static_prefs::pref!("layout.css.color-mix.color-spaces.enabled");
#[cfg(feature = "servo")]
return false;
}
// NOTE(emilio): Syntax is still a bit in-flux, since [1] doesn't seem
@ -61,6 +100,9 @@ impl Parse for ColorMix {
return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
}
let color_spaces_enabled = context.chrome_rules_enabled() ||
allow_color_mix_color_spaces();
input.expect_function_matching("color-mix")?;
// NOTE(emilio): This implements the syntax described here for now,
@ -69,12 +111,18 @@ impl Parse for ColorMix {
// https://github.com/w3c/csswg-drafts/issues/6066#issuecomment-789836765
input.parse_nested_block(|input| {
input.expect_ident_matching("in")?;
// TODO: support multiple interpolation spaces.
input.expect_ident_matching("srgb")?;
let color_space = if color_spaces_enabled {
ColorSpaceKind::parse(input)?
} else {
input.expect_ident_matching("srgb")?;
ColorSpaceKind::Srgb
};
input.expect_comma()?;
let left = Color::parse(context, input)?;
let left_percentage = input.try_parse(|input| Percentage::parse(context, input)).ok();
let left_percentage = input
.try_parse(|input| Percentage::parse(context, input))
.ok();
input.expect_comma()?;
@ -87,11 +135,18 @@ impl Parse for ColorMix {
let left_percentage =
left_percentage.unwrap_or_else(|| Percentage::new(1.0 - right_percentage.get()));
let hue_adjuster = input
.try_parse(|input| HueAdjuster::parse(input))
.unwrap_or(HueAdjuster::Shorter);
Ok(ColorMix {
color_space,
left,
left_percentage,
right,
right_percentage,
hue_adjuster,
})
})
}
@ -115,7 +170,9 @@ impl ToCss for ColorMix {
(1.0 - percent.get() - other.get()).abs() <= f32::EPSILON
}
dest.write_str("color-mix(in srgb, ")?;
dest.write_str("color-mix(in ")?;
self.color_space.to_css(dest)?;
dest.write_str(", ")?;
self.left.to_css(dest)?;
if !can_omit(&self.left_percentage, &self.right_percentage, true) {
dest.write_str(" ")?;
@ -127,10 +184,30 @@ impl ToCss for ColorMix {
dest.write_str(" ")?;
self.right_percentage.to_css(dest)?;
}
if self.hue_adjuster != HueAdjuster::Shorter {
dest.write_str(" ")?;
self.hue_adjuster.to_css(dest)?;
}
dest.write_str(")")
}
}
/// The color scheme for a specific system color.
#[cfg(feature = "gecko")]
#[derive(Clone, Copy, Debug, MallocSizeOf, Parse, PartialEq, ToCss, ToShmem)]
#[repr(u8)]
pub enum SystemColorScheme {
/// The default color-scheme for the document.
#[css(skip)]
Default,
/// A light color scheme.
Light,
/// A dark color scheme.
Dark,
}
/// Specified color value
#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
pub enum Color {
@ -145,9 +222,10 @@ pub enum Color {
},
/// A complex color value from computed value
Complex(ComputedColor),
/// A system color
/// Either a system color, or a `-moz-system-color(<system-color>, light|dark)`
/// function which allows chrome code to choose between color schemes.
#[cfg(feature = "gecko")]
System(SystemColor),
System(SystemColor, SystemColorScheme),
/// A color mix.
ColorMix(Box<ColorMix>),
/// Quirksmode-only rule for inheriting color from the body
@ -181,13 +259,13 @@ pub enum SystemColor {
TextBackground,
#[css(skip)]
TextForeground,
#[css(skip)]
#[parse(condition = "ParserContext::in_ua_or_chrome_sheet")]
TextSelectBackground,
#[css(skip)]
#[parse(condition = "ParserContext::in_ua_or_chrome_sheet")]
TextSelectForeground,
#[css(skip)]
#[parse(condition = "ParserContext::in_ua_or_chrome_sheet")]
TextSelectBackgroundDisabled,
#[css(skip)]
#[parse(condition = "ParserContext::in_ua_or_chrome_sheet")]
TextSelectBackgroundAttention,
#[css(skip)]
TextHighlightBackground,
@ -358,6 +436,10 @@ pub enum SystemColor {
/// colors.
MozNativehyperlinktext,
/// As above, but visited link color.
#[css(skip)]
MozNativevisitedhyperlinktext,
#[parse(aliases = "-moz-hyperlinktext")]
Linktext,
#[parse(aliases = "-moz-activehyperlinktext")]
@ -369,14 +451,20 @@ pub enum SystemColor {
MozComboboxtext,
MozCombobox,
MozGtkInfoBarText,
/// Color of tree column headers
#[parse(condition = "ParserContext::in_ua_or_chrome_sheet")]
MozColheadertext,
#[parse(condition = "ParserContext::in_ua_or_chrome_sheet")]
MozColheaderhovertext,
/// Color of text in the (active) titlebar.
#[parse(condition = "ParserContext::in_ua_or_chrome_sheet")]
MozGtkTitlebarText,
/// Color of text in the (inactive) titlebar.
#[parse(condition = "ParserContext::in_ua_or_chrome_sheet")]
MozGtkTitlebarInactiveText,
#[css(skip)]
End, // Just for array-indexing purposes.
}
@ -384,20 +472,29 @@ pub enum SystemColor {
#[cfg(feature = "gecko")]
impl SystemColor {
#[inline]
fn compute(&self, cx: &Context) -> ComputedColor {
fn compute(&self, cx: &Context, scheme: SystemColorScheme) -> ComputedColor {
use crate::gecko_bindings::bindings;
let prefs = cx.device().pref_sheet_prefs();
let colors = &cx.device().pref_sheet_prefs().mColors;
let style_color_scheme = cx.style().get_inherited_ui().clone_color_scheme();
// TODO: At least Canvas / CanvasText should be color-scheme aware
// (probably the link colors too).
convert_nscolor_to_computedcolor(match *self {
SystemColor::Canvastext => prefs.mDefaultColor,
SystemColor::Canvas => prefs.mDefaultBackgroundColor,
SystemColor::Linktext => prefs.mLinkColor,
SystemColor::Activetext => prefs.mActiveLinkColor,
SystemColor::Visitedtext => prefs.mVisitedLinkColor,
SystemColor::Canvastext => colors.mDefault,
SystemColor::Canvas => colors.mDefaultBackground,
SystemColor::Linktext => colors.mLink,
SystemColor::Activetext => colors.mActiveLink,
SystemColor::Visitedtext => colors.mVisitedLink,
_ => unsafe {
bindings::Gecko_GetLookAndFeelSystemColor(*self as i32, cx.device().document())
_ => {
let color = unsafe {
bindings::Gecko_GetLookAndFeelSystemColor(*self as i32, cx.device().document(), scheme, &style_color_scheme)
};
if color == bindings::NS_SAME_AS_FOREGROUND_COLOR {
return ComputedColor::currentcolor();
}
color
},
})
}
@ -473,6 +570,21 @@ impl<'a, 'b: 'a, 'i: 'a> ::cssparser::ColorComponentParser<'i> for ColorComponen
}
}
#[cfg(feature = "gecko")]
fn parse_moz_system_color<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
) -> Result<(SystemColor, SystemColorScheme), ParseError<'i>> {
debug_assert!(context.chrome_rules_enabled());
input.expect_function_matching("-moz-system-color")?;
input.parse_nested_block(|input| {
let color = SystemColor::parse(context, input)?;
input.expect_comma()?;
let scheme = SystemColorScheme::parse(input)?;
Ok((color, scheme))
})
}
impl Parse for Color {
fn parse<'i, 't>(
context: &ParserContext,
@ -498,7 +610,15 @@ impl Parse for Color {
#[cfg(feature = "gecko")]
{
if let Ok(system) = input.try_parse(|i| SystemColor::parse(context, i)) {
return Ok(Color::System(system));
return Ok(Color::System(system, SystemColorScheme::Default));
}
if context.chrome_rules_enabled() {
if let Ok((color, scheme)) =
input.try_parse(|i| parse_moz_system_color(context, i))
{
return Ok(Color::System(color, scheme));
}
}
}
@ -537,7 +657,17 @@ impl ToCss for Color {
Color::Complex(_) => Ok(()),
Color::ColorMix(ref mix) => mix.to_css(dest),
#[cfg(feature = "gecko")]
Color::System(system) => system.to_css(dest),
Color::System(system, scheme) => {
if scheme == SystemColorScheme::Default {
system.to_css(dest)
} else {
dest.write_str("-moz-system-color(")?;
system.to_css(dest)?;
dest.write_str(", ")?;
scheme.to_css(dest)?;
dest.write_char(')')
}
},
#[cfg(feature = "gecko")]
Color::InheritFromBodyQuirk => Ok(()),
}
@ -696,14 +826,16 @@ impl Color {
let left = mix.left.to_computed_color(context)?.to_animated_value();
let right = mix.right.to_computed_color(context)?.to_animated_value();
ToAnimatedValue::from_animated_value(AnimatedColor::mix(
mix.color_space,
&left,
mix.left_percentage.get(),
&right,
mix.right_percentage.get(),
mix.hue_adjuster,
))
},
#[cfg(feature = "gecko")]
Color::System(system) => system.compute(context?),
Color::System(system, scheme) => system.compute(context?, scheme),
#[cfg(feature = "gecko")]
Color::InheritFromBodyQuirk => ComputedColor::rgba(context?.device().body_text_color()),
})
@ -818,3 +950,123 @@ impl Parse for CaretColor {
ColorOrAuto::parse(context, input).map(GenericCaretColor)
}
}
bitflags! {
/// Various flags to represent the color-scheme property in an efficient
/// way.
#[derive(Default, MallocSizeOf, SpecifiedValueInfo, ToComputedValue, ToResolvedValue, ToShmem)]
#[repr(C)]
#[value_info(other_values = "light,dark,only")]
pub struct ColorSchemeFlags: u8 {
/// Whether the author specified `light`.
const LIGHT = 1 << 0;
/// Whether the author specified `dark`.
const DARK = 1 << 1;
/// Whether the author specified `only`.
const ONLY = 1 << 2;
}
}
/// <https://drafts.csswg.org/css-color-adjust/#color-scheme-prop>
#[derive(
Clone,
Debug,
Default,
MallocSizeOf,
PartialEq,
SpecifiedValueInfo,
ToComputedValue,
ToResolvedValue,
ToShmem,
)]
#[repr(C)]
#[value_info(other_values = "normal")]
pub struct ColorScheme {
#[ignore_malloc_size_of = "Arc"]
idents: crate::ArcSlice<CustomIdent>,
bits: ColorSchemeFlags,
}
impl ColorScheme {
/// Returns the `normal` value.
pub fn normal() -> Self {
Self {
idents: Default::default(),
bits: ColorSchemeFlags::empty(),
}
}
}
impl Parse for ColorScheme {
fn parse<'i, 't>(_: &ParserContext, input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i>> {
let mut idents = vec![];
let mut bits = ColorSchemeFlags::empty();
let mut location = input.current_source_location();
while let Ok(ident) = input.try_parse(|i| i.expect_ident_cloned()) {
let mut is_only = false;
match_ignore_ascii_case! { &ident,
"normal" => {
if idents.is_empty() && bits.is_empty() {
return Ok(Self::normal());
}
return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
},
"light" => bits.insert(ColorSchemeFlags::LIGHT),
"dark" => bits.insert(ColorSchemeFlags::DARK),
"only" => {
if bits.intersects(ColorSchemeFlags::ONLY) {
return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
}
bits.insert(ColorSchemeFlags::ONLY);
is_only = true;
},
_ => {},
};
if is_only {
if !idents.is_empty() {
// Only is allowed either at the beginning or at the end,
// but not in the middle.
break;
}
} else {
idents.push(CustomIdent::from_ident(location, &ident, &[])?);
}
location = input.current_source_location();
}
if idents.is_empty() {
return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
}
Ok(Self {
idents: crate::ArcSlice::from_iter(idents.into_iter()),
bits,
})
}
}
impl ToCss for ColorScheme {
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
where
W: Write,
{
if self.idents.is_empty() {
debug_assert!(self.bits.is_empty());
return dest.write_str("normal");
}
let mut first = true;
for ident in self.idents.iter() {
if !first {
dest.write_char(' ')?;
}
first = false;
ident.to_css(dest)?;
}
if self.bits.intersects(ColorSchemeFlags::ONLY) {
dest.write_str(" only")?;
}
Ok(())
}
}

View file

@ -6,15 +6,14 @@
#[cfg(feature = "gecko")]
use crate::context::QuirksMode;
#[cfg(feature = "gecko")]
use crate::gecko_bindings::bindings;
use crate::parser::{Parse, ParserContext};
use crate::values::computed::font::{FamilyName, FontFamilyList, FontStyleAngle, SingleFontFamily};
use crate::values::computed::{font as computed, Length, NonNegativeLength};
use crate::values::computed::{Angle as ComputedAngle, Percentage as ComputedPercentage};
use crate::values::computed::{CSSPixelLength, Context, ToComputedValue};
use crate::values::computed::FontSizeAdjust as ComputedFontSizeAdjust;
use crate::values::generics::font::VariationValue;
use crate::values::generics::font::{self as generics, FeatureTagValue, FontSettings, FontTag};
use crate::values::generics::font::{self as generics, FeatureTagValue, FontSettings, FontTag, GenericFontSizeAdjust};
use crate::values::generics::NonNegative;
use crate::values::specified::length::{FontBaseSize, PX_PER_PT};
use crate::values::specified::{AllowQuirks, Angle, Integer, LengthPercentage};
@ -23,7 +22,7 @@ use crate::values::CustomIdent;
use crate::Atom;
use cssparser::{Parser, Token};
#[cfg(feature = "gecko")]
use malloc_size_of::{MallocSizeOf, MallocSizeOfOps};
use malloc_size_of::{MallocSizeOf, MallocSizeOfOps, MallocUnconditionalSizeOf};
use std::fmt::{self, Write};
use style_traits::values::SequenceWriter;
use style_traits::{CssWriter, KeywordsCollectFn, ParseError};
@ -682,14 +681,6 @@ pub enum FontFamily {
impl FontFamily {
system_font_methods!(FontFamily, font_family);
/// Parse a specified font-family value
pub fn parse_specified<'i, 't>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i>> {
let values = input.parse_comma_separated(SingleFontFamily::parse)?;
Ok(FontFamily::Values(FontFamilyList::new(
values.into_boxed_slice(),
)))
}
}
impl ToComputedValue for FontFamily {
@ -697,8 +688,8 @@ impl ToComputedValue for FontFamily {
fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
match *self {
FontFamily::Values(ref v) => computed::FontFamily {
families: v.clone(),
FontFamily::Values(ref list) => computed::FontFamily {
families: list.clone(),
is_system_font: false,
},
FontFamily::System(_) => self.compute_system(context),
@ -712,18 +703,12 @@ impl ToComputedValue for FontFamily {
#[cfg(feature = "gecko")]
impl MallocSizeOf for FontFamily {
fn size_of(&self, _ops: &mut MallocSizeOfOps) -> usize {
fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize {
match *self {
FontFamily::Values(ref v) => {
// Although a SharedFontList object is refcounted, we always
// attribute its size to the specified value, as long as it's
// not a value in SharedFontList::sSingleGenerics.
if matches!(v, FontFamilyList::SharedFontList(_)) {
let ptr = v.shared_font_list().get();
unsafe { bindings::Gecko_SharedFontList_SizeOfIncludingThis(ptr) }
} else {
0
}
// Although the family list is refcounted, we always attribute
// its size to the specified value.
v.list.unconditional_size_of(ops)
},
FontFamily::System(_) => 0,
}
@ -735,23 +720,31 @@ impl Parse for FontFamily {
/// <family-name> = <string> | [ <ident>+ ]
/// TODO: <generic-family>
fn parse<'i, 't>(
_: &ParserContext,
context: &ParserContext,
input: &mut Parser<'i, 't>,
) -> Result<FontFamily, ParseError<'i>> {
FontFamily::parse_specified(input)
let values = input.parse_comma_separated(|input| SingleFontFamily::parse(context, input))?;
Ok(FontFamily::Values(FontFamilyList {
#[cfg(feature = "gecko")]
list: crate::ArcSlice::from_iter(values.into_iter()),
#[cfg(feature = "servo")]
list: values.into_boxed_slice(),
#[cfg(feature = "gecko")]
fallback: computed::GenericFontFamily::None,
}))
}
}
impl SpecifiedValueInfo for FontFamily {}
/// `FamilyName::parse` is based on `SingleFontFamily::parse` and not the other way around
/// because we want the former to exclude generic family keywords.
/// `FamilyName::parse` is based on `SingleFontFamily::parse` and not the other
/// way around because we want the former to exclude generic family keywords.
impl Parse for FamilyName {
fn parse<'i, 't>(
_: &ParserContext,
context: &ParserContext,
input: &mut Parser<'i, 't>,
) -> Result<Self, ParseError<'i>> {
match SingleFontFamily::parse(input) {
match SingleFontFamily::parse(context, input) {
Ok(SingleFontFamily::FamilyName(name)) => Ok(name),
Ok(SingleFontFamily::Generic(_)) => {
Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
@ -761,16 +754,13 @@ impl Parse for FamilyName {
}
}
#[derive(
Clone, Copy, Debug, MallocSizeOf, Parse, PartialEq, SpecifiedValueInfo, ToCss, ToShmem,
)]
/// Preserve the readability of text when font fallback occurs
#[derive(
Clone, Copy, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToShmem,
)]
#[allow(missing_docs)]
pub enum FontSizeAdjust {
/// None variant
None,
/// Number variant
Number(NonNegativeNumber),
/// system font
Value(GenericFontSizeAdjust<NonNegativeNumber>),
#[css(skip)]
System(SystemFont),
}
@ -779,34 +769,57 @@ impl FontSizeAdjust {
#[inline]
/// Default value of font-size-adjust
pub fn none() -> Self {
FontSizeAdjust::None
FontSizeAdjust::Value(GenericFontSizeAdjust::None)
}
system_font_methods!(FontSizeAdjust, font_size_adjust);
}
impl Parse for FontSizeAdjust {
fn parse<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
) -> Result<Self, ParseError<'i>> {
let location = input.current_source_location();
if let Ok(ident) = input.try_parse(|i| i.expect_ident_cloned()) {
#[cfg(feature = "gecko")]
let basis_enabled = static_prefs::pref!("layout.css.font-size-adjust.basis.enabled");
#[cfg(feature = "servo")]
let basis_enabled = false;
let basis = match_ignore_ascii_case! { &ident,
"none" => return Ok(FontSizeAdjust::none()),
// Check for size adjustment basis keywords if enabled.
"ex-height" if basis_enabled => GenericFontSizeAdjust::ExHeight,
"cap-height" if basis_enabled => GenericFontSizeAdjust::CapHeight,
"ch-width" if basis_enabled => GenericFontSizeAdjust::ChWidth,
"ic-width" if basis_enabled => GenericFontSizeAdjust::IcWidth,
"ic-height" if basis_enabled => GenericFontSizeAdjust::IcHeight,
// Unknown (or disabled) keyword.
_ => return Err(location.new_custom_error(
::selectors::parser::SelectorParseErrorKind::UnexpectedIdent(ident)
)),
};
let value = NonNegativeNumber::parse(context, input)?;
return Ok(FontSizeAdjust::Value(basis(value)));
}
// Without a basis keyword, the number refers to the 'ex-height' metric.
let value = NonNegativeNumber::parse(context, input)?;
Ok(FontSizeAdjust::Value(GenericFontSizeAdjust::ExHeight(value)))
}
}
impl ToComputedValue for FontSizeAdjust {
type ComputedValue = computed::FontSizeAdjust;
type ComputedValue = ComputedFontSizeAdjust;
fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
match *self {
FontSizeAdjust::None => computed::FontSizeAdjust::None,
FontSizeAdjust::Number(ref n) => {
// The computed version handles clamping of animated values
// itself.
computed::FontSizeAdjust::Number(n.to_computed_value(context).0)
},
FontSizeAdjust::Value(v) => v.to_computed_value(context),
FontSizeAdjust::System(_) => self.compute_system(context),
}
}
fn from_computed_value(computed: &computed::FontSizeAdjust) -> Self {
match *computed {
computed::FontSizeAdjust::None => FontSizeAdjust::None,
computed::FontSizeAdjust::Number(v) => {
FontSizeAdjust::Number(NonNegativeNumber::from_computed_value(&v.into()))
},
}
fn from_computed_value(computed: &ComputedFontSizeAdjust) -> Self {
Self::Value(ToComputedValue::from_computed_value(computed))
}
}
@ -1977,23 +1990,24 @@ impl Parse for FontFeatureSettings {
Debug,
MallocSizeOf,
PartialEq,
SpecifiedValueInfo,
ToComputedValue,
ToResolvedValue,
ToShmem,
)]
/// Whether user agents are allowed to synthesize bold or oblique font faces
/// when a font family lacks bold or italic faces
/// when a font family lacks those faces, or a small-caps variant when this is
/// not supported by the face.
pub struct FontSynthesis {
/// If a `font-weight` is requested that the font family does not contain,
/// the user agent may synthesize the requested weight from the weights
/// that do exist in the font family.
#[css(represents_keyword)]
pub weight: bool,
/// If a font-style is requested that the font family does not contain,
/// the user agent may synthesize the requested style from the normal face in the font family.
#[css(represents_keyword)]
pub style: bool,
/// This bit controls whether the user agent is allowed to synthesize small caps variant
/// when a font face lacks it.
pub small_caps: bool,
}
impl FontSynthesis {
@ -2003,8 +2017,31 @@ impl FontSynthesis {
FontSynthesis {
weight: true,
style: true,
small_caps: true,
}
}
#[inline]
/// Get the 'none' value of font-synthesis
pub fn none() -> Self {
FontSynthesis {
weight: false,
style: false,
small_caps: false,
}
}
#[inline]
/// Return true if this is the 'none' value
pub fn is_none(&self) -> bool {
*self == Self::none()
}
}
#[inline]
fn allow_font_synthesis_small_caps() -> bool {
#[cfg(feature = "gecko")]
return static_prefs::pref!("layout.css.font-synthesis-small-caps.enabled");
#[cfg(feature = "servo")]
return false;
}
impl Parse for FontSynthesis {
@ -2012,26 +2049,22 @@ impl Parse for FontSynthesis {
_: &ParserContext,
input: &mut Parser<'i, 't>,
) -> Result<FontSynthesis, ParseError<'i>> {
let mut result = FontSynthesis {
weight: false,
style: false,
};
try_match_ident_ignore_ascii_case! { input,
"none" => Ok(result),
"weight" => {
result.weight = true;
if input.try_parse(|input| input.expect_ident_matching("style")).is_ok() {
result.style = true;
}
Ok(result)
},
"style" => {
result.style = true;
if input.try_parse(|input| input.expect_ident_matching("weight")).is_ok() {
result.weight = true;
}
Ok(result)
},
use crate::values::SelectorParseErrorKind;
let mut result = Self::none();
while let Ok(ident) = input.try_parse(|i| i.expect_ident_cloned()) {
match_ignore_ascii_case! { &ident,
"none" if result.is_none() => return Ok(result),
"weight" if !result.weight => result.weight = true,
"style" if !result.style => result.style = true,
"small-caps" if !result.small_caps && allow_font_synthesis_small_caps()
=> result.small_caps = true,
_ => return Err(input.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(ident))),
}
}
if !result.is_none() {
Ok(result)
} else {
Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
}
}
}
@ -2041,14 +2074,41 @@ impl ToCss for FontSynthesis {
where
W: Write,
{
if self.weight && self.style {
dest.write_str("weight style")
} else if self.style {
dest.write_str("style")
} else if self.weight {
dest.write_str("weight")
} else {
dest.write_str("none")
if self.is_none() {
return dest.write_str("none");
}
let mut need_space = false;
if self.weight {
dest.write_str("weight")?;
need_space = true;
}
if self.style {
if need_space {
dest.write_str(" ")?;
}
dest.write_str("style")?;
need_space = true;
}
if self.small_caps {
if need_space {
dest.write_str(" ")?;
}
dest.write_str("small-caps")?;
}
Ok(())
}
}
impl SpecifiedValueInfo for FontSynthesis {
fn collect_completion_keywords(f: KeywordsCollectFn) {
f(&[
"none",
"weight",
"style",
]);
if allow_font_synthesis_small_caps() {
f(&["small-caps"]);
}
}
}
@ -2061,6 +2121,7 @@ impl From<u8> for FontSynthesis {
FontSynthesis {
weight: bits & structs::NS_FONT_SYNTHESIS_WEIGHT as u8 != 0,
style: bits & structs::NS_FONT_SYNTHESIS_STYLE as u8 != 0,
small_caps: bits & structs::NS_FONT_SYNTHESIS_SMALL_CAPS as u8 != 0,
}
}
}
@ -2077,6 +2138,9 @@ impl From<FontSynthesis> for u8 {
if v.style {
bits |= structs::NS_FONT_SYNTHESIS_STYLE as u8;
}
if v.small_caps {
bits |= structs::NS_FONT_SYNTHESIS_SMALL_CAPS as u8;
}
bits
}
}

View file

@ -83,7 +83,7 @@ fn cross_fade_enabled() -> bool {
#[cfg(feature = "gecko")]
fn image_set_enabled() -> bool {
static_prefs::pref!("layout.css.image-set.enabled")
true
}
#[cfg(feature = "servo")]
@ -365,7 +365,14 @@ impl ImageSet {
cors_mode: CorsMode,
only_url: bool,
) -> Result<Self, ParseError<'i>> {
input.expect_function_matching("image-set")?;
let function = input.expect_function()?;
match_ignore_ascii_case! { &function,
"-webkit-image-set" | "image-set" => {},
_ => {
let func = function.clone();
return Err(input.new_custom_error(StyleParseErrorKind::UnexpectedFunction(func)));
}
}
let items = input.parse_nested_block(|input| {
input.parse_comma_separated(|input| ImageSetItem::parse(context, input, cors_mode, only_url))
})?;

View file

@ -1247,6 +1247,27 @@ macro_rules! parse_size_non_length {
}};
}
#[cfg(feature = "gecko")]
fn is_fit_content_function_enabled() -> bool {
static_prefs::pref!("layout.css.fit-content-function.enabled")
}
#[cfg(feature = "gecko")]
macro_rules! parse_fit_content_function {
($size:ident, $input:expr, $context:expr, $allow_quirks:expr) => {
if is_fit_content_function_enabled() {
if let Ok(length) = $input.try_parse(|input| {
input.expect_function_matching("fit-content")?;
input.parse_nested_block(|i| {
NonNegativeLengthPercentage::parse_quirky($context, i, $allow_quirks)
})
}) {
return Ok($size::FitContentFunction(length));
}
}
};
}
impl Size {
/// Parses, with quirks.
pub fn parse_quirky<'i, 't>(
@ -1255,6 +1276,8 @@ impl Size {
allow_quirks: AllowQuirks,
) -> Result<Self, ParseError<'i>> {
parse_size_non_length!(Size, input, "auto" => Auto);
#[cfg(feature = "gecko")]
parse_fit_content_function!(Size, input, context, allow_quirks);
let length = NonNegativeLengthPercentage::parse_quirky(context, input, allow_quirks)?;
Ok(GenericSize::LengthPercentage(length))
@ -1287,6 +1310,8 @@ impl MaxSize {
allow_quirks: AllowQuirks,
) -> Result<Self, ParseError<'i>> {
parse_size_non_length!(MaxSize, input, "none" => None);
#[cfg(feature = "gecko")]
parse_fit_content_function!(MaxSize, input, context, allow_quirks);
let length = NonNegativeLengthPercentage::parse_quirky(context, input, allow_quirks)?;
Ok(GenericMaxSize::LengthPercentage(length))

View file

@ -67,6 +67,15 @@ impl ListStyleType {
_ => unreachable!("Unknown counter style keyword value"),
})))
}
/// Is this a bullet? (i.e. `list-style-type: disc|circle|square|disclosure-closed|disclosure-open`)
#[inline]
pub fn is_bullet(&self) -> bool {
match self {
ListStyleType::CounterStyle(ref style) => style.is_bullet(),
_ => false,
}
}
}
#[cfg(feature = "gecko")]

View file

@ -42,7 +42,7 @@ pub use self::box_::{Clear, Float, Overflow, OverflowAnchor};
pub use self::box_::{OverflowClipBox, OverscrollBehavior, Perspective, Resize};
pub use self::box_::{ScrollSnapAlign, ScrollSnapAxis, ScrollSnapStrictness, ScrollSnapType};
pub use self::box_::{TouchAction, TransitionProperty, VerticalAlign, WillChange};
pub use self::color::{Color, ColorOrAuto, ColorPropertyValue};
pub use self::color::{Color, ColorOrAuto, ColorPropertyValue, ColorScheme};
pub use self::column::ColumnCount;
pub use self::counters::{Content, ContentItem, CounterIncrement, CounterSetOrReset};
pub use self::easing::TimingFunction;
@ -80,7 +80,7 @@ pub use self::position::{PositionComponent, ZIndex};
pub use self::ratio::Ratio;
pub use self::rect::NonNegativeLengthOrNumberRect;
pub use self::resolution::Resolution;
pub use self::svg::MozContextProperties;
pub use self::svg::{DProperty, MozContextProperties};
pub use self::svg::{SVGLength, SVGOpacity, SVGPaint};
pub use self::svg::{SVGPaintOrder, SVGStrokeDashArray, SVGWidth};
pub use self::svg_path::SVGPathData;

View file

@ -23,15 +23,15 @@ impl Parse for PageSize {
) -> Result<Self, ParseError<'i>> {
// Try to parse as <page-size> [ <orientation> ]
if let Ok(paper_size) = input.try_parse(PaperSize::parse) {
if let Ok(orientation) = input.try_parse(Orientation::parse) {
return Ok(PageSize::PaperSizeAndOrientation(paper_size, orientation));
}
return Ok(PageSize::PaperSize(paper_size));
let orientation = input
.try_parse(Orientation::parse)
.unwrap_or(Orientation::Portrait);
return Ok(PageSize::PaperSize(paper_size, orientation));
}
// Try to parse as <orientation> [ <page-size> ]
if let Ok(orientation) = input.try_parse(Orientation::parse) {
if let Ok(paper_size) = input.try_parse(PaperSize::parse) {
return Ok(PageSize::PaperSizeAndOrientation(paper_size, orientation));
return Ok(PageSize::PaperSize(paper_size, orientation));
}
return Ok(PageSize::Orientation(orientation));
}

View file

@ -10,6 +10,7 @@ use crate::values::specified::color::Color;
use crate::values::specified::url::SpecifiedUrl;
use crate::values::specified::AllowQuirks;
use crate::values::specified::LengthPercentage;
use crate::values::specified::SVGPathData;
use crate::values::specified::{NonNegativeLengthPercentage, Opacity};
use crate::values::CustomIdent;
use cssparser::{Parser, Token};
@ -334,3 +335,57 @@ impl Parse for MozContextProperties {
})
}
}
/// The svg d property type.
///
/// https://svgwg.org/svg2-draft/paths.html#TheDProperty
#[derive(
Animate,
Clone,
ComputeSquaredDistance,
Debug,
Deserialize,
MallocSizeOf,
PartialEq,
Serialize,
SpecifiedValueInfo,
ToAnimatedZero,
ToComputedValue,
ToCss,
ToResolvedValue,
ToShmem,
)]
#[repr(C, u8)]
pub enum DProperty {
/// Path value for path(<string>) or just a <string>.
#[css(function)]
Path(SVGPathData),
/// None value.
#[animation(error)]
None,
}
impl DProperty {
/// return none.
#[inline]
pub fn none() -> Self {
DProperty::None
}
}
impl Parse for DProperty {
fn parse<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
) -> Result<Self, ParseError<'i>> {
// Parse none.
if input.try_parse(|i| i.expect_ident_matching("none")).is_ok() {
return Ok(DProperty::none());
}
// Parse possible functions.
input.expect_function_matching("path")?;
let path_data = input.parse_nested_block(|i| SVGPathData::parse(context, i))?;
Ok(DProperty::Path(path_data))
}
}

View file

@ -60,6 +60,111 @@ impl SVGPathData {
SVGPathData(crate::ArcSlice::from_iter(result.into_iter()))
}
// FIXME: Bug 1714238, we may drop this once we use the same data structure for both SVG and
// CSS.
/// Decode the svg path raw data from Gecko.
#[cfg(feature = "gecko")]
pub fn decode_from_f32_array(path: &[f32]) -> Result<Self, ()> {
use crate::gecko_bindings::structs::dom::SVGPathSeg_Binding::*;
let mut result: Vec<PathCommand> = Vec::new();
let mut i: usize = 0;
while i < path.len() {
// See EncodeType() and DecodeType() in SVGPathSegUtils.h.
// We are using reinterpret_cast<> to encode and decode between u32 and f32, so here we
// use to_bits() to decode the type.
let seg_type = path[i].to_bits() as u16;
i = i + 1;
match seg_type {
PATHSEG_CLOSEPATH => result.push(PathCommand::ClosePath),
PATHSEG_MOVETO_ABS | PATHSEG_MOVETO_REL => {
debug_assert!(i + 1 < path.len());
result.push(PathCommand::MoveTo {
point: CoordPair::new(path[i], path[i + 1]),
absolute: IsAbsolute::new(seg_type == PATHSEG_MOVETO_ABS),
});
i = i + 2;
}
PATHSEG_LINETO_ABS | PATHSEG_LINETO_REL => {
debug_assert!(i + 1 < path.len());
result.push(PathCommand::LineTo {
point: CoordPair::new(path[i], path[i + 1]),
absolute: IsAbsolute::new(seg_type == PATHSEG_LINETO_ABS),
});
i = i + 2;
}
PATHSEG_CURVETO_CUBIC_ABS | PATHSEG_CURVETO_CUBIC_REL => {
debug_assert!(i + 5 < path.len());
result.push(PathCommand::CurveTo {
control1: CoordPair::new(path[i], path[i + 1]),
control2: CoordPair::new(path[i + 2], path[i + 3]),
point: CoordPair::new(path[i + 4], path[i + 5]),
absolute: IsAbsolute::new(seg_type == PATHSEG_CURVETO_CUBIC_ABS),
});
i = i + 6;
}
PATHSEG_CURVETO_QUADRATIC_ABS | PATHSEG_CURVETO_QUADRATIC_REL => {
debug_assert!(i + 3 < path.len());
result.push(PathCommand::QuadBezierCurveTo {
control1: CoordPair::new(path[i], path[i + 1]),
point: CoordPair::new(path[i + 2], path[i + 3]),
absolute: IsAbsolute::new(seg_type == PATHSEG_CURVETO_QUADRATIC_ABS),
});
i = i + 4;
}
PATHSEG_ARC_ABS | PATHSEG_ARC_REL => {
debug_assert!(i + 6 < path.len());
result.push(PathCommand::EllipticalArc {
rx: path[i],
ry: path[i + 1],
angle: path[i + 2],
large_arc_flag: ArcFlag(path[i + 3] != 0.0f32),
sweep_flag: ArcFlag(path[i + 4] != 0.0f32),
point: CoordPair::new(path[i + 5], path[i + 6]),
absolute: IsAbsolute::new(seg_type == PATHSEG_ARC_ABS),
});
i = i + 7;
}
PATHSEG_LINETO_HORIZONTAL_ABS | PATHSEG_LINETO_HORIZONTAL_REL => {
debug_assert!(i < path.len());
result.push(PathCommand::HorizontalLineTo {
x: path[i],
absolute: IsAbsolute::new(seg_type == PATHSEG_LINETO_HORIZONTAL_ABS),
});
i = i + 1;
}
PATHSEG_LINETO_VERTICAL_ABS | PATHSEG_LINETO_VERTICAL_REL => {
debug_assert!(i < path.len());
result.push(PathCommand::VerticalLineTo {
y: path[i],
absolute: IsAbsolute::new(seg_type == PATHSEG_LINETO_VERTICAL_ABS),
});
i = i + 1;
}
PATHSEG_CURVETO_CUBIC_SMOOTH_ABS | PATHSEG_CURVETO_CUBIC_SMOOTH_REL => {
debug_assert!(i + 3 < path.len());
result.push(PathCommand::SmoothCurveTo {
control2: CoordPair::new(path[i], path[i + 1]),
point: CoordPair::new(path[i + 2], path[i + 3]),
absolute: IsAbsolute::new(seg_type == PATHSEG_CURVETO_CUBIC_SMOOTH_ABS),
});
i = i + 4;
}
PATHSEG_CURVETO_QUADRATIC_SMOOTH_ABS | PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL => {
debug_assert!(i + 1 < path.len());
result.push(PathCommand::SmoothQuadBezierCurveTo {
point: CoordPair::new(path[i], path[i + 1]),
absolute: IsAbsolute::new(seg_type == PATHSEG_CURVETO_QUADRATIC_SMOOTH_ABS),
});
i = i + 2;
}
PATHSEG_UNKNOWN | _ => return Err(()),
}
}
Ok(SVGPathData(crate::ArcSlice::from_iter(result.into_iter())))
}
}
impl ToCss for SVGPathData {
@ -506,6 +611,17 @@ impl IsAbsolute {
pub fn is_yes(&self) -> bool {
*self == IsAbsolute::Yes
}
/// Return Yes if value is true. Otherwise, return No.
#[inline]
#[cfg(feature = "gecko")]
fn new(value: bool) -> Self {
if value {
IsAbsolute::Yes
} else {
IsAbsolute::No
}
}
}
/// The path coord type.

View file

@ -238,8 +238,14 @@ impl Transform {
Ok(generic::TransformOperation::SkewY(theta))
},
"perspective" => {
let d = specified::Length::parse_non_negative(context, input)?;
Ok(generic::TransformOperation::Perspective(d))
let p = match input.try_parse(|input| specified::Length::parse_non_negative(context, input)) {
Ok(p) => generic::PerspectiveFunction::Length(p),
Err(..) => {
input.expect_ident_matching("none")?;
generic::PerspectiveFunction::None
}
};
Ok(generic::TransformOperation::Perspective(p))
},
_ => Err(()),
};

View file

@ -13,6 +13,8 @@ use std::ptr::NonNull;
use std::{iter, mem};
use to_shmem_derive::ToShmem;
use malloc_size_of::{MallocSizeOf, MallocSizeOfOps, MallocUnconditionalSizeOf};
/// A canary that we stash in ArcSlices.
///
/// Given we cannot use a zero-sized-type for the header, since well, C++
@ -137,6 +139,22 @@ impl<T> ArcSlice<T> {
std::mem::forget(empty);
ptr as *mut _
}
/// Returns whether there's only one reference to this ArcSlice.
pub fn is_unique(&self) -> bool {
self.0.with_arc(|arc| arc.is_unique())
}
}
impl<T: MallocSizeOf> MallocUnconditionalSizeOf for ArcSlice<T> {
#[allow(unsafe_code)]
fn unconditional_size_of(&self, ops: &mut MallocSizeOfOps) -> usize {
let mut size = unsafe { ops.malloc_size_of(self.0.heap_ptr()) };
for el in self.iter() {
size += el.size_of(ops);
}
size
}
}
/// The inner pointer of an ArcSlice<T>, to be sent via FFI.

View file

@ -81,10 +81,14 @@ packages = [
files = [
"./components/net/tests/parsable_mime/text",
# These are ignored to avoid diverging from Gecko
"./components/style/counter_style/mod.rs",
"./components/style/properties/helpers.mako.rs",
"./components/style/stylesheets/rule_parser.rs",
"./components/style/stylist.rs",
"./components/style/values/computed/font.rs",
"./components/style/values/computed/image.rs",
"./components/style/values/specified/color.rs",
"./components/style/values/specified/transform.rs",
# Mako does not lend itself easily to splitting long lines
"./components/style/properties/helpers/animated_properties.mako.rs",
"./components/style/properties/shorthands/text.mako.rs",

View file

@ -1,2 +0,0 @@
[background-position-calc-minmax-001.html]
expected: FAIL

View file

@ -1,2 +0,0 @@
[background-position-negative-percentage-comparison.html]
expected: FAIL

View file

@ -926,51 +926,6 @@
[Web Animations: property <transform> from [scaleZ(2)\] to [scaleZ(2) perspective(500px)\] at (2) should be [scaleZ(2) perspective(250px)\]]
expected: FAIL
[CSS Transitions: property <transform> from [perspective(none)\] to [perspective(500px)\] at (-1) should be [perspective(none)\]]
expected: FAIL
[CSS Transitions: property <transform> from [perspective(none)\] to [perspective(500px)\] at (0) should be [perspective(none)\]]
expected: FAIL
[CSS Transitions: property <transform> from [perspective(none)\] to [perspective(500px)\] at (0.5) should be [perspective(1000px)\]]
expected: FAIL
[CSS Transitions: property <transform> from [perspective(none)\] to [perspective(500px)\] at (1) should be [perspective(500px)\]]
expected: FAIL
[CSS Transitions: property <transform> from [perspective(none)\] to [perspective(500px)\] at (2) should be [perspective(250px)\]]
expected: FAIL
[CSS Transitions with transition: all: property <transform> from [perspective(none)\] to [perspective(500px)\] at (-1) should be [perspective(none)\]]
expected: FAIL
[CSS Transitions with transition: all: property <transform> from [perspective(none)\] to [perspective(500px)\] at (0) should be [perspective(none)\]]
expected: FAIL
[CSS Transitions with transition: all: property <transform> from [perspective(none)\] to [perspective(500px)\] at (0.5) should be [perspective(1000px)\]]
expected: FAIL
[CSS Transitions with transition: all: property <transform> from [perspective(none)\] to [perspective(500px)\] at (1) should be [perspective(500px)\]]
expected: FAIL
[CSS Transitions with transition: all: property <transform> from [perspective(none)\] to [perspective(500px)\] at (2) should be [perspective(250px)\]]
expected: FAIL
[CSS Animations: property <transform> from [perspective(none)\] to [perspective(500px)\] at (-1) should be [perspective(none)\]]
expected: FAIL
[CSS Animations: property <transform> from [perspective(none)\] to [perspective(500px)\] at (0) should be [perspective(none)\]]
expected: FAIL
[CSS Animations: property <transform> from [perspective(none)\] to [perspective(500px)\] at (0.5) should be [perspective(1000px)\]]
expected: FAIL
[CSS Animations: property <transform> from [perspective(none)\] to [perspective(500px)\] at (1) should be [perspective(500px)\]]
expected: FAIL
[CSS Animations: property <transform> from [perspective(none)\] to [perspective(500px)\] at (2) should be [perspective(250px)\]]
expected: FAIL
[Web Animations: property <transform> from [perspective(none)\] to [perspective(500px)\] at (-1) should be [perspective(none)\]]
expected: FAIL

View file

@ -203,39 +203,12 @@
[Web Animations: property <transform> from [matrix(1,0,0,1,0,0) rotate(0deg)\] to [matrix(2,0,0,2,0,0) rotate(360deg)\] at (0.5) should be [matrix(1.5,0,0,1.5,0,0) rotate(180deg)\]]
expected: FAIL
[CSS Transitions: property <transform> from [perspective(none) translateZ(15px)\] to [perspective(none) translateZ(15px)\] at (0.5) should be [perspective(none) translateZ(15px)\]]
expected: FAIL
[CSS Transitions with transition: all: property <transform> from [perspective(none) translateZ(15px)\] to [perspective(none) translateZ(15px)\] at (0.5) should be [perspective(none) translateZ(15px)\]]
expected: FAIL
[CSS Animations: property <transform> from [perspective(none) translateZ(15px)\] to [perspective(none) translateZ(15px)\] at (0.5) should be [perspective(none) translateZ(15px)\]]
expected: FAIL
[Web Animations: property <transform> from [perspective(none) translateZ(15px)\] to [perspective(none) translateZ(15px)\] at (0.5) should be [perspective(none) translateZ(15px)\]]
expected: FAIL
[CSS Transitions: property <transform> from [perspective(100px) translateZ(50px)\] to [perspective(none) translateZ(50px)\] at (0.5) should be [perspective(200px) translateZ(50px)\]]
expected: FAIL
[CSS Transitions with transition: all: property <transform> from [perspective(100px) translateZ(50px)\] to [perspective(none) translateZ(50px)\] at (0.5) should be [perspective(200px) translateZ(50px)\]]
expected: FAIL
[CSS Animations: property <transform> from [perspective(100px) translateZ(50px)\] to [perspective(none) translateZ(50px)\] at (0.5) should be [perspective(200px) translateZ(50px)\]]
expected: FAIL
[Web Animations: property <transform> from [perspective(100px) translateZ(50px)\] to [perspective(none) translateZ(50px)\] at (0.5) should be [perspective(200px) translateZ(50px)\]]
expected: FAIL
[CSS Transitions: property <transform> from [perspective(none) translateZ(15px)\] to [perspective(25px) translateZ(15px)\] at (0.5) should be [perspective(50px) translateZ(15px)\]]
expected: FAIL
[CSS Transitions with transition: all: property <transform> from [perspective(none) translateZ(15px)\] to [perspective(25px) translateZ(15px)\] at (0.5) should be [perspective(50px) translateZ(15px)\]]
expected: FAIL
[CSS Animations: property <transform> from [perspective(none) translateZ(15px)\] to [perspective(25px) translateZ(15px)\] at (0.5) should be [perspective(50px) translateZ(15px)\]]
expected: FAIL
[Web Animations: property <transform> from [perspective(none) translateZ(15px)\] to [perspective(25px) translateZ(15px)\] at (0.5) should be [perspective(50px) translateZ(15px)\]]
expected: FAIL
@ -248,9 +221,6 @@
[CSS Transitions with transition: all: property <transform> from [perspective(0.1px) translateZ(0.25px)\] to [perspective(0.1px) translateZ(0.25px)\] at (0.5) should be [perspective(1px) translateZ(0.25px)\]]
expected: FAIL
[CSS Animations: property <transform> from [perspective(0.1px) translateZ(0.25px)\] to [perspective(0.1px) translateZ(0.25px)\] at (0.5) should be [perspective(1px) translateZ(0.25px)\]]
expected: FAIL
[Web Animations: property <transform> from [perspective(0.1px) translateZ(0.25px)\] to [perspective(0.1px) translateZ(0.25px)\] at (0.5) should be [perspective(1px) translateZ(0.25px)\]]
expected: FAIL
@ -260,21 +230,9 @@
[CSS Transitions with transition: all: property <transform> from [perspective(0px) translateZ(0.25px)\] to [perspective(0px) translateZ(0.25px)\] at (0.5) should be [perspective(1px) translateZ(0.25px)\]]
expected: FAIL
[CSS Animations: property <transform> from [perspective(0px) translateZ(0.25px)\] to [perspective(0px) translateZ(0.25px)\] at (0.5) should be [perspective(1px) translateZ(0.25px)\]]
expected: FAIL
[Web Animations: property <transform> from [perspective(0px) translateZ(0.25px)\] to [perspective(0px) translateZ(0.25px)\] at (0.5) should be [perspective(1px) translateZ(0.25px)\]]
expected: FAIL
[CSS Transitions: property <transform> from [perspective(0px) translateZ(0.5px)\] to [perspective(3px) translateZ(0.5px)\] at (0.5) should be [perspective(1.5px) translateZ(0.5px)\]]
expected: FAIL
[CSS Transitions with transition: all: property <transform> from [perspective(0px) translateZ(0.5px)\] to [perspective(3px) translateZ(0.5px)\] at (0.5) should be [perspective(1.5px) translateZ(0.5px)\]]
expected: FAIL
[CSS Animations: property <transform> from [perspective(0px) translateZ(0.5px)\] to [perspective(3px) translateZ(0.5px)\] at (0.5) should be [perspective(1.5px) translateZ(0.5px)\]]
expected: FAIL
[Web Animations: property <transform> from [perspective(0px) translateZ(0.5px)\] to [perspective(3px) translateZ(0.5px)\] at (0.5) should be [perspective(1.5px) translateZ(0.5px)\]]
expected: FAIL
@ -290,14 +248,5 @@
[Web Animations: property <transform> from [perspective(10px) translateZ(0.5px)\] to [perspective(1px) translateZ(0.5px)\] at (-1) should be [translateZ(0.5px)\]]
expected: FAIL
[CSS Transitions: property <transform> from [perspective(1px) translateZ(0.5px)\] to [perspective(10px) translateZ(0.5px)\] at (-1) should be [perspective(1px) translateZ(0.5px)\]]
expected: FAIL
[CSS Transitions with transition: all: property <transform> from [perspective(1px) translateZ(0.5px)\] to [perspective(10px) translateZ(0.5px)\] at (-1) should be [perspective(1px) translateZ(0.5px)\]]
expected: FAIL
[CSS Animations: property <transform> from [perspective(1px) translateZ(0.5px)\] to [perspective(10px) translateZ(0.5px)\] at (-1) should be [perspective(1px) translateZ(0.5px)\]]
expected: FAIL
[Web Animations: property <transform> from [perspective(1px) translateZ(0.5px)\] to [perspective(10px) translateZ(0.5px)\] at (-1) should be [perspective(1px) translateZ(0.5px)\]]
expected: FAIL

View file

@ -1,6 +1,3 @@
[transform-valid.html]
[e.style['transform'\] = "perspective(none)" should set the property value]
expected: FAIL
[e.style['transform'\] = "translate(1px, 0%)" should set the property value]
expected: FAIL

View file

@ -1,2 +0,0 @@
[perspective-zero-2.html]
expected: FAIL

View file

@ -1,2 +0,0 @@
[perspective-zero-3.html]
expected: FAIL

View file

@ -1,2 +0,0 @@
[perspective-zero.html]
expected: FAIL

View file

@ -1,2 +0,0 @@
[transform3d-perspective-005.html]
expected: FAIL

View file

@ -1,36 +1,3 @@
[minmax-percentage-serialize.html]
['min(1%, 2%, 3%)' as a specified value should serialize as 'min(1%, 2%, 3%)'.]
expected: FAIL
['min(1%, 2%, 3%)' as a computed value should serialize as 'min(1%, 2%, 3%)'.]
expected: FAIL
['min(3%, 2%, 1%)' as a specified value should serialize as 'min(3%, 2%, 1%)'.]
expected: FAIL
['min(3%, 2%, 1%)' as a computed value should serialize as 'min(3%, 2%, 1%)'.]
expected: FAIL
['max(1%, 2%, 3%)' as a specified value should serialize as 'max(1%, 2%, 3%)'.]
expected: FAIL
['max(1%, 2%, 3%)' as a computed value should serialize as 'max(1%, 2%, 3%)'.]
expected: FAIL
['max(3%, 2%, 1%)' as a specified value should serialize as 'max(3%, 2%, 1%)'.]
expected: FAIL
['max(3%, 2%, 1%)' as a computed value should serialize as 'max(3%, 2%, 1%)'.]
expected: FAIL
['min(1%, 2%, 3%) 0px' as a specified value should serialize as 'min(1%, 2%, 3%) 0px'.]
expected: FAIL
['min(1%, 2%, 3%) 0px' as a computed value should serialize as 'min(1%, 2%, 3%) 0px'.]
expected: FAIL
['calc(min(1%, 2%) + max(3%, 4%) + 10%)' as a specified value should serialize as 'calc(10% + min(1%, 2%) + max(3%, 4%))'.]
expected: FAIL
['calc(min(1%, 2%) + max(3%, 4%) + 10%)' as a computed value should serialize as 'calc(10% + min(1%, 2%) + max(3%, 4%))'.]
expected: FAIL

View file

@ -7,9 +7,3 @@
[target6]
expected: FAIL
[target9]
expected: FAIL
[target11]
expected: FAIL

Some files were not shown because too many files have changed in this diff Show more